From 0f833b424fe77d3c812dbdcbe0b3d1b896c902cf Mon Sep 17 00:00:00 2001 From: Rahul Yadav <52163880+rahulyadav5524@users.noreply.github.com> Date: Mon, 1 Jul 2024 14:46:46 +0530 Subject: [PATCH 01/11] Update release.js.yml --- .github/workflows/release.js.yml | 72 ++++++++++++++++---------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/.github/workflows/release.js.yml b/.github/workflows/release.js.yml index 4924ac7b0..e49555160 100644 --- a/.github/workflows/release.js.yml +++ b/.github/workflows/release.js.yml @@ -28,39 +28,39 @@ jobs: token: ${{ secrets.NPM_TOKEN }} access: public package: ./packages/core/package.json - publish-cells: - defaults: - run: - working-directory: ./packages/cells - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 20.10.0 - - name: Bootstrap - run: npm install && npm run build - working-directory: . - - uses: JS-DevTools/npm-publish@v1 - with: - token: ${{ secrets.NPM_TOKEN }} - access: public - package: ./packages/cells/package.json - publish-source: - defaults: - run: - working-directory: ./packages/source - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 20.10.0 - - name: Bootstrap - run: npm install && npm run build - working-directory: . - - uses: JS-DevTools/npm-publish@v1 - with: - token: ${{ secrets.NPM_TOKEN }} - access: public - package: ./packages/source/package.json + # publish-cells: + # defaults: + # run: + # working-directory: ./packages/cells + # runs-on: ubuntu-latest + # steps: + # - uses: actions/checkout@v4 + # - uses: actions/setup-node@v4 + # with: + # node-version: 20.10.0 + # - name: Bootstrap + # run: npm install && npm run build + # working-directory: . + # - uses: JS-DevTools/npm-publish@v1 + # with: + # token: ${{ secrets.NPM_TOKEN }} + # access: public + # package: ./packages/cells/package.json + # publish-source: + # defaults: + # run: + # working-directory: ./packages/source + # runs-on: ubuntu-latest + # steps: + # - uses: actions/checkout@v4 + # - uses: actions/setup-node@v4 + # with: + # node-version: 20.10.0 + # - name: Bootstrap + # run: npm install && npm run build + # working-directory: . + # - uses: JS-DevTools/npm-publish@v1 + # with: + # token: ${{ secrets.NPM_TOKEN }} + # access: public + # package: ./packages/source/package.json From 10900740c8748c3bf32f128a8d92e18fe32b135b Mon Sep 17 00:00:00 2001 From: Rahul Yadav Date: Mon, 1 Jul 2024 17:14:26 +0530 Subject: [PATCH 02/11] Added feature to right side freeze columnj --- packages/core/src/data-editor/data-editor.tsx | 69 +++++++++--- .../docs/examples/freeze-columns.stories.tsx | 16 ++- .../core/src/internal/data-grid/data-grid.tsx | 103 ++++++++++++++---- .../image-window-loader-interface.ts | 2 +- .../data-grid/render/data-grid-lib.ts | 81 ++++++++++---- .../data-grid/render/data-grid-render.blit.ts | 52 +++++---- .../render/data-grid-render.cells.ts | 4 + .../render/data-grid-render.header.ts | 5 +- .../render/data-grid-render.lines.ts | 38 ++++++- .../data-grid/render/data-grid-render.ts | 46 ++++++-- .../data-grid/render/data-grid-render.walk.ts | 15 ++- .../render/data-grid.render.rings.ts | 13 ++- .../data-grid/render/draw-grid-arg.ts | 2 +- .../scrolling-data-grid.tsx | 14 +-- 14 files changed, 347 insertions(+), 113 deletions(-) diff --git a/packages/core/src/data-editor/data-editor.tsx b/packages/core/src/data-editor/data-editor.tsx index de75d8bac..fb85dbe74 100644 --- a/packages/core/src/data-editor/data-editor.tsx +++ b/packages/core/src/data-editor/data-editor.tsx @@ -911,6 +911,9 @@ const DataEditorImpl: React.ForwardRefRenderFunction { if (typeof window === "undefined") return { fontSize: "16px" }; return window.getComputedStyle(document.documentElement); @@ -1559,9 +1562,13 @@ const DataEditorImpl: React.ForwardRefRenderFunction= mangledCols.length - freezeRightColumns; i--) { + frozenRightWidth += columns[i].width; } let trailingRowHeight = 0; const freezeTrailingRowsEffective = freezeTrailingRows + (lastRowSticky ? 1 : 0); @@ -1574,8 +1581,8 @@ const DataEditorImpl: React.ForwardRefRenderFunction= mangledCols.length - freezeRightColumns)) + ) { scrollX = 0; } else if ( dir === "horizontal" || @@ -1650,7 +1661,9 @@ const DataEditorImpl: React.ForwardRefRenderFunction 0) { freezeRegions.push({ x: region.x - rowMarkerOffset, @@ -2577,11 +2601,20 @@ const DataEditorImpl: React.ForwardRefRenderFunction 0) { + if (freezeLeftColumns > 0) { freezeRegions.push({ x: 0, y: rows - freezeTrailingRows, - width: freezeColumns, + width: freezeLeftColumns, + height: freezeTrailingRows, + }); + } + + if (freezeRightColumns > 0) { + freezeRegions.push({ + x: columns.length - freezeRightColumns, + y: rows - freezeTrailingRows, + width: freezeRightColumns, height: freezeTrailingRows, }); } @@ -2596,7 +2629,7 @@ const DataEditorImpl: React.ForwardRefRenderFunction = (p: { freezeColumns: number }) => { +export const FreezeColumns: React.VFC = (p: { freezeLeftColumns: number, freezeRightColumns: number }) => { const { cols, getCellContent } = useMockDataGenerator(100); return ( = (p: { freezeColumns: number }) => { ); }; (FreezeColumns as any).argTypes = { - freezeColumns: { + freezeLeftColumns: { + control: { + type: "range", + min: 0, + max: 10, + }, + }, + freezeRightColumns: { control: { type: "range", min: 0, @@ -55,5 +62,6 @@ export const FreezeColumns: React.VFC = (p: { freezeColumns: number }) => { }, }; (FreezeColumns as any).args = { - freezeColumns: 1, + freezeLeftColumns: 1, + freezeRightColumns: 1, }; diff --git a/packages/core/src/internal/data-grid/data-grid.tsx b/packages/core/src/internal/data-grid/data-grid.tsx index 9f942e352..471989eef 100644 --- a/packages/core/src/internal/data-grid/data-grid.tsx +++ b/packages/core/src/internal/data-grid/data-grid.tsx @@ -70,7 +70,7 @@ export interface DataGridProps { readonly accessibilityHeight: number; - readonly freezeColumns: number; + readonly freezeColumns: number | [left: number, right: number]; readonly freezeTrailingRows: number; readonly hasAppendRow: boolean; readonly firstColAccessible: boolean; @@ -320,7 +320,11 @@ export interface DataGridRef { focus: () => void; getBounds: (col?: number, row?: number) => Rectangle | undefined; damage: (cells: DamageUpdateList) => void; - getMouseArgsForPosition: (posX: number, posY: number, ev?: MouseEvent | TouchEvent) => GridMouseEventArgs | undefined; + getMouseArgsForPosition: ( + posX: number, + posY: number, + ev?: MouseEvent | TouchEvent + ) => GridMouseEventArgs | undefined; } const getRowData = (cell: InnerGridCell, getCellRenderer?: GetCellRendererCallback) => { @@ -399,7 +403,9 @@ const DataGrid: React.ForwardRefRenderFunction = (p, } = p; const translateX = p.translateX ?? 0; const translateY = p.translateY ?? 0; - const cellXOffset = Math.max(freezeColumns, Math.min(columns.length - 1, cellXOffsetReal)); + const freezeLeftColumns = typeof freezeColumns === "number" ? freezeColumns : freezeColumns[0]; + const freezeRightColumns = typeof freezeColumns === "number" ? 0 : freezeColumns[1]; + const cellXOffset = Math.max(freezeLeftColumns, Math.min(columns.length - 1, cellXOffsetReal)); const ref = React.useRef(null); const windowEventTargetRef = React.useRef(experimental?.eventTarget ?? window); @@ -446,7 +452,10 @@ const DataGrid: React.ForwardRefRenderFunction = (p, }, [cellYOffset, cellXOffset, translateX, translateY, enableFirefoxRescaling, enableSafariRescaling]); const mappedColumns = useMappedColumns(columns, freezeColumns); - const stickyX = React.useMemo(() => fixedShadowX ? getStickyWidth(mappedColumns, dragAndDropState) : 0,[mappedColumns, dragAndDropState, fixedShadowX]); + const stickyX = React.useMemo( + () => (fixedShadowX ? getStickyWidth(mappedColumns, dragAndDropState) : [0, 0]), + [fixedShadowX, mappedColumns, dragAndDropState] + ); // row: -1 === columnHeader, -2 === groupHeader const getBoundsForItem = React.useCallback( @@ -471,7 +480,7 @@ const DataGrid: React.ForwardRefRenderFunction = (p, translateX, translateY, rows, - freezeColumns, + freezeLeftColumns, freezeTrailingRows, mappedColumns, rowHeight @@ -499,7 +508,7 @@ const DataGrid: React.ForwardRefRenderFunction = (p, translateX, translateY, rows, - freezeColumns, + freezeLeftColumns, freezeTrailingRows, mappedColumns, rowHeight, @@ -519,7 +528,14 @@ const DataGrid: React.ForwardRefRenderFunction = (p, const y = (posY - rect.top) / scale; const edgeDetectionBuffer = 5; - const effectiveCols = getEffectiveColumns(mappedColumns, cellXOffset, width, undefined, translateX); + const effectiveCols = getEffectiveColumns( + mappedColumns, + cellXOffset, + width, + freezeColumns, + undefined, + translateX + ); let button = 0; let buttons = 0; @@ -538,7 +554,7 @@ const DataGrid: React.ForwardRefRenderFunction = (p, } // -1 === off right edge - const col = getColumnIndexForX(x, effectiveCols, translateX); + const col = getColumnIndexForX(x, effectiveCols, freezeColumns, width, translateX); // -1: header or above // undefined: offbottom @@ -705,6 +721,7 @@ const DataGrid: React.ForwardRefRenderFunction = (p, fillHandle, selection, totalHeaderHeight, + freezeColumns, ] ); @@ -1716,7 +1733,7 @@ const DataGrid: React.ForwardRefRenderFunction = (p, } return getMouseArgsForPosition(canvasRef.current, posX, posY, ev); - } + }, }), [canvasRef, damage, getBoundsForItem] ); @@ -1726,7 +1743,14 @@ const DataGrid: React.ForwardRefRenderFunction = (p, const accessibilityTree = useDebouncedMemo( () => { if (width < 50 || experimental?.disableAccessibilityTree === true) return null; - let effectiveCols = getEffectiveColumns(mappedColumns, cellXOffset, width, dragAndDropState, translateX); + let effectiveCols = getEffectiveColumns( + mappedColumns, + cellXOffset, + width, + freezeColumns, + dragAndDropState, + translateX + ); const colOffset = firstColAccessible ? 0 : -1; if (!firstColAccessible && effectiveCols[0]?.sourceIndex === 0) { effectiveCols = effectiveCols.slice(1); @@ -1860,33 +1884,59 @@ const DataGrid: React.ForwardRefRenderFunction = (p, onKeyDown, getBoundsForItem, onCellFocused, + freezeColumns, ], 200 ); - const opacityX = - freezeColumns === 0 || !fixedShadowX ? 0 : cellXOffset > freezeColumns ? 1 : clamp(-translateX / 100, 0, 1); + const opacityXLeft = + freezeLeftColumns === 0 || !fixedShadowX + ? 0 + : cellXOffset > freezeLeftColumns + ? 1 + : clamp(-translateX / 100, 0, 1); + + const opacityXRight = + freezeRightColumns === 0 || !fixedShadowX + ? 0 + : cellXOffset + width < columns.length - freezeRightColumns + ? 1 + : clamp((translateX - (columns.length - freezeRightColumns - width) * 32) / 100, 0, 1); const absoluteOffsetY = -cellYOffset * 32 + translateY; const opacityY = !fixedShadowY ? 0 : clamp(-absoluteOffsetY / 100, 0, 1); const stickyShadow = React.useMemo(() => { - if (!opacityX && !opacityY) { + if (!opacityXLeft && !opacityY && !opacityXRight) { return null; } - const styleX: React.CSSProperties = { + const transition = "opacity 0.2s"; + + const styleXLeft: React.CSSProperties = { position: "absolute", top: 0, - left: stickyX, - width: width - stickyX, + left: stickyX[0], + width: width - stickyX[0], height: height, - opacity: opacityX, + opacity: opacityXLeft, pointerEvents: "none", - transition: !smoothScrollX ? "opacity 0.2s" : undefined, + transition: !smoothScrollX ? transition : undefined, boxShadow: "inset 13px 0 10px -13px rgba(0, 0, 0, 0.2)", }; + const styleXRight: React.CSSProperties = { + position: "absolute", + top: 0, + right: stickyX[1], + width: width - stickyX[1], + height: height, + opacity: opacityXRight, + pointerEvents: "none", + transition: !smoothScrollX ? transition : undefined, + boxShadow: "inset -13px 0 10px -13px rgba(0, 0, 0, 0.2)", + }; + const styleY: React.CSSProperties = { position: "absolute", top: totalHeaderHeight, @@ -1895,17 +1945,28 @@ const DataGrid: React.ForwardRefRenderFunction = (p, height: height, opacity: opacityY, pointerEvents: "none", - transition: !smoothScrollY ? "opacity 0.2s" : undefined, + transition: !smoothScrollY ? transition : undefined, boxShadow: "inset 0 13px 10px -13px rgba(0, 0, 0, 0.2)", }; return ( <> - {opacityX > 0 &&
} + {opacityXLeft > 0 &&
} + {opacityXRight > 0 &&
} {opacityY > 0 &&
} ); - }, [opacityX, opacityY, stickyX, width, smoothScrollX, totalHeaderHeight, height, smoothScrollY]); + }, [ + opacityXLeft, + opacityY, + stickyX, + width, + smoothScrollX, + totalHeaderHeight, + height, + smoothScrollY, + opacityXRight, + ]); const overlayStyle = React.useMemo( () => ({ diff --git a/packages/core/src/internal/data-grid/image-window-loader-interface.ts b/packages/core/src/internal/data-grid/image-window-loader-interface.ts index e32ef93d1..6495a1ca3 100644 --- a/packages/core/src/internal/data-grid/image-window-loader-interface.ts +++ b/packages/core/src/internal/data-grid/image-window-loader-interface.ts @@ -3,7 +3,7 @@ import type { Rectangle } from "./data-grid-types.js"; /** @category Types */ export interface ImageWindowLoader { - setWindow(newWindow: Rectangle, freezeCols: number, freezeRows: number[]): void; + setWindow(newWindow: Rectangle, freezeCols: number | [left: number, right: number], freezeRows: number[]): void; loadOrGetImage(url: string, col: number, row: number): HTMLImageElement | ImageBitmap | undefined; setCallback(imageLoaded: (locations: CellSet) => void): void; } diff --git a/packages/core/src/internal/data-grid/render/data-grid-lib.ts b/packages/core/src/internal/data-grid/render/data-grid-lib.ts index 96eb1d90f..8fd5bb604 100644 --- a/packages/core/src/internal/data-grid/render/data-grid-lib.ts +++ b/packages/core/src/internal/data-grid/render/data-grid-lib.ts @@ -17,12 +17,16 @@ import type { FullyDefined } from "../../../common/support.js"; export interface MappedGridColumn extends FullyDefined { sourceIndex: number; sticky: boolean; + stickyPosition: "left" | "right" | undefined; } export function useMappedColumns( columns: readonly InnerGridColumn[], - freezeColumns: number + freezeColumns: number | [left: number, right: number] ): readonly MappedGridColumn[] { + const freezeColumnsLeft = typeof freezeColumns === "number" ? freezeColumns : freezeColumns[0]; + const freezeColumnsRight = typeof freezeColumns === "number" ? 0 : freezeColumns[1]; + return React.useMemo( () => columns.map( @@ -35,7 +39,9 @@ export function useMappedColumns( menuIcon: c.menuIcon, overlayIcon: c.overlayIcon, sourceIndex: i, - sticky: i < freezeColumns, + sticky: i < freezeColumnsLeft || i >= columns.length - freezeColumnsRight, + stickyPosition: + i < freezeColumnsLeft ? "left" : i >= columns.length - freezeColumnsRight ? "right" : undefined, indicatorIcon: c.indicatorIcon, style: c.style, themeOverride: c.themeOverride, @@ -50,7 +56,7 @@ export function useMappedColumns( headerRowMarkerDisabled: c.headerRowMarkerDisabled, }) ), - [columns, freezeColumns] + [columns, freezeColumnsLeft, freezeColumnsRight] ); } @@ -174,16 +180,25 @@ export function getStickyWidth( src: number; dest: number; } -): number { - let result = 0; +): [left: number, right: number] { + let lWidth = 0; + let rWidth = 0; const remapped = remapForDnDState(columns, dndState); for (let i = 0; i < remapped.length; i++) { const c = remapped[i]; - if (c.sticky) result += c.width; - else break; + if (c.sticky) { + if (c.stickyPosition === "left") lWidth += c.width; + } else break; } - return result; + for (let i = remapped.length - 1; i >= 0; i--) { + const c = remapped[i]; + if (c.sticky) { + if (c.stickyPosition === "right") rWidth += c.width; + } else break; + } + + return [lWidth, rWidth]; } export function getFreezeTrailingHeight( @@ -206,6 +221,7 @@ export function getEffectiveColumns( columns: readonly MappedGridColumn[], cellXOffset: number, width: number, + freezeColumns: number | [left: number, right: number], dndState?: { src: number; dest: number; @@ -214,14 +230,14 @@ export function getEffectiveColumns( ): readonly MappedGridColumn[] { const mappedCols = remapForDnDState(columns, dndState); + const freezeLeftColumns = typeof freezeColumns === "number" ? freezeColumns : freezeColumns[0]; + const freezeRightColumns = typeof freezeColumns === "number" ? 0 : freezeColumns[1]; + const sticky: MappedGridColumn[] = []; - for (const c of mappedCols) { - if (c.sticky) { - sticky.push(c); - } else { - break; - } + for (let i = 0; i < freezeLeftColumns; i++) { + sticky.push(mappedCols[i]); } + if (sticky.length > 0) { for (const c of sticky) { width -= c.width; @@ -242,22 +258,42 @@ export function getEffectiveColumns( } } + for (let i = mappedCols.length - freezeRightColumns; i < mappedCols.length; i++) { + sticky.push(mappedCols[i]); + } + return sticky; } export function getColumnIndexForX( targetX: number, effectiveColumns: readonly MappedGridColumn[], + freezeColumns: number | [left: number, right: number], + width: number, translateX?: number ): number { + const freezeRightColumns = typeof freezeColumns === "number" ? 0 : freezeColumns[1]; + + let y = width; + for (let fc = 0; fc < freezeRightColumns; fc++) { + const colIdx = effectiveColumns.length - 1 - fc; + const col = effectiveColumns[colIdx]; + y -= col.width; + if (targetX <= y) { + return col.sourceIndex; + } + } + let x = 0; - for (const c of effectiveColumns) { + for (let i = 0; i < effectiveColumns.length - freezeRightColumns; i++) { + const c = effectiveColumns[i]; const cx = c.sticky ? x : x + (translateX ?? 0); if (targetX <= cx + c.width) { return c.sourceIndex; } x += c.width; } + return -1; } @@ -768,7 +804,7 @@ export function computeBounds( translateX: number, translateY: number, rows: number, - freezeColumns: number, + freezeColumns: number | [left: number, right: number], freezeTrailingRows: number, mappedColumns: readonly MappedGridColumn[], rowHeight: number | ((index: number) => number) @@ -780,16 +816,19 @@ export function computeBounds( height: 0, }; + const freezeLeftColumns = typeof freezeColumns === "number" ? freezeColumns : freezeColumns[0]; + const freezeRightColumns = typeof freezeColumns === "number" ? 0 : freezeColumns[1]; + if (col >= mappedColumns.length || row >= rows || row < -2 || col < 0) { return result; } const headerHeight = totalHeaderHeight - groupHeaderHeight; - if (col >= freezeColumns) { + if (col >= freezeLeftColumns) { const dir = cellXOffset > col ? -1 : 1; - const freezeWidth = getStickyWidth(mappedColumns); - result.x += freezeWidth + translateX; + const [freezeLeftWidth, freezeRightWidth] = getStickyWidth(mappedColumns); + result.x += freezeLeftWidth + translateX; for (let i = cellXOffset; i !== col; i += dir) { result.x += mappedColumns[dir === 1 ? i : i - 1].width * dir; } @@ -832,8 +871,8 @@ export function computeBounds( end++; } if (!sticky) { - const freezeWidth = getStickyWidth(mappedColumns); - const clip = result.x - freezeWidth; + const [freezeLeftWidth, freezeRightWidth] = getStickyWidth(mappedColumns); + const clip = result.x - freezeLeftWidth; if (clip < 0) { result.x -= clip; result.width += clip; diff --git a/packages/core/src/internal/data-grid/render/data-grid-render.blit.ts b/packages/core/src/internal/data-grid/render/data-grid-render.blit.ts index 4f5600837..a6c9806d2 100644 --- a/packages/core/src/internal/data-grid/render/data-grid-render.blit.ts +++ b/packages/core/src/internal/data-grid/render/data-grid-render.blit.ts @@ -70,7 +70,7 @@ export function blitLastFrame( } deltaX += translateX - last.translateX; - const stickyWidth = getStickyWidth(effectiveCols); + const [stickyLeftWidth, stickyRightWidth] = getStickyWidth(effectiveCols); if (deltaX !== 0 && deltaY !== 0) { return { @@ -81,7 +81,7 @@ export function blitLastFrame( const freezeTrailingRowsHeight = freezeTrailingRows > 0 ? getFreezeTrailingHeight(rows, freezeTrailingRows, getRowHeight) : 0; - const blitWidth = width - stickyWidth - Math.abs(deltaX); + const blitWidth = width - stickyLeftWidth - Math.abs(deltaX) - stickyRightWidth; const blitHeight = height - totalHeaderHeight - freezeTrailingRowsHeight - Math.abs(deltaY) - 1; if (blitWidth > 150 && blitHeight > 150) { @@ -128,22 +128,22 @@ export function blitLastFrame( // blit X if (deltaX > 0) { // pixels moving right - args.sx = stickyWidth * dpr; + args.sx = stickyLeftWidth * dpr; args.sw = blitWidth * dpr; - args.dx = (deltaX + stickyWidth) * dpr; + args.dx = (deltaX + stickyLeftWidth) * dpr; args.dw = blitWidth * dpr; drawRegions.push({ - x: stickyWidth - 1, + x: stickyLeftWidth - 1, y: 0, width: deltaX + 2, // extra width to account for first col not drawing a left side border height: height, }); } else if (deltaX < 0) { // pixels moving left - args.sx = (stickyWidth - deltaX) * dpr; + args.sx = (stickyLeftWidth - deltaX) * dpr; args.sw = blitWidth * dpr; - args.dx = stickyWidth * dpr; + args.dx = stickyLeftWidth * dpr; args.dw = blitWidth * dpr; drawRegions.push({ @@ -157,14 +157,14 @@ export function blitLastFrame( ctx.setTransform(1, 0, 0, 1, 0, 0); if (doubleBuffer) { if ( - stickyWidth > 0 && + stickyLeftWidth > 0 && deltaX !== 0 && deltaY === 0 && (targetScroll === undefined || blitSourceScroll?.[1] !== false) ) { // When double buffering the freeze columns can be offset by a couple pixels vertically between the two // buffers. We don't want to redraw them so we need to make sure to copy them between the buffers. - const w = stickyWidth * dpr; + const w = stickyLeftWidth * dpr; const h = height * dpr; ctx.drawImage(blitSource, 0, 0, w, h, 0, 0, w, h); } @@ -200,7 +200,8 @@ export function blitResizedCol( height: number, totalHeaderHeight: number, effectiveCols: readonly MappedGridColumn[], - resizedIndex: number + resizedIndex: number, + freezeTrailingColumns: number ) { const drawRegions: Rectangle[] = []; @@ -215,18 +216,27 @@ export function blitResizedCol( return drawRegions; } - walkColumns(effectiveCols, cellYOffset, translateX, translateY, totalHeaderHeight, (c, drawX, _drawY, clipX) => { - if (c.sourceIndex === resizedIndex) { - const x = Math.max(drawX, clipX) + 1; - drawRegions.push({ - x, - y: 0, - width: width - x, - height, - }); - return true; + walkColumns( + effectiveCols, + width, + cellYOffset, + translateX, + translateY, + totalHeaderHeight, + freezeTrailingColumns, + (c, drawX, _drawY, clipX) => { + if (c.sourceIndex === resizedIndex) { + const x = Math.max(drawX, clipX) + 1; + drawRegions.push({ + x, + y: 0, + width: width - x, + height, + }); + return true; + } } - }); + ); return drawRegions; } diff --git a/packages/core/src/internal/data-grid/render/data-grid-render.cells.ts b/packages/core/src/internal/data-grid/render/data-grid-render.cells.ts index dc84326c2..fed1f21f4 100644 --- a/packages/core/src/internal/data-grid/render/data-grid-render.cells.ts +++ b/packages/core/src/internal/data-grid/render/data-grid-render.cells.ts @@ -77,6 +77,7 @@ export function drawCells( effectiveColumns: readonly MappedGridColumn[], allColumns: readonly MappedGridColumn[], height: number, + width: number, totalHeaderHeight: number, translateX: number, translateY: number, @@ -90,6 +91,7 @@ export function drawCells( isFocused: boolean, drawFocus: boolean, freezeTrailingRows: number, + freezeTrailingColumns: number, hasAppendRow: boolean, drawRegions: readonly Rectangle[], damage: CellSet | undefined, @@ -124,10 +126,12 @@ export function drawCells( walkColumns( effectiveColumns, + width, cellYOffset, translateX, translateY, totalHeaderHeight, + freezeTrailingColumns, (c, drawX, colDrawStartY, clipX, startRow) => { const diff = Math.max(0, clipX - drawX); diff --git a/packages/core/src/internal/data-grid/render/data-grid-render.header.ts b/packages/core/src/internal/data-grid/render/data-grid-render.header.ts index 47117f530..daf594b85 100644 --- a/packages/core/src/internal/data-grid/render/data-grid-render.header.ts +++ b/packages/core/src/internal/data-grid/render/data-grid-render.header.ts @@ -38,7 +38,8 @@ export function drawGridHeaders( getGroupDetails: GroupDetailsCallback, damage: CellSet | undefined, drawHeaderCallback: DrawHeaderCallback | undefined, - touchMode: boolean + touchMode: boolean, + freezeTrailingColumns: number ) { const totalHeaderHeight = headerHeight + groupHeaderHeight; if (totalHeaderHeight <= 0) return; @@ -54,7 +55,7 @@ export function drawGridHeaders( const font = outerTheme.headerFontFull; // Assinging the context font too much can be expensive, it can be worth it to minimze this ctx.font = font; - walkColumns(effectiveCols, 0, translateX, 0, totalHeaderHeight, (c, x, _y, clipX) => { + walkColumns(effectiveCols, width, 0, translateX, 0, totalHeaderHeight, freezeTrailingColumns, (c, x, _y, clipX) => { if (damage !== undefined && !damage.has([c.sourceIndex, -1])) return; const diff = Math.max(0, clipX - x); ctx.save(); diff --git a/packages/core/src/internal/data-grid/render/data-grid-render.lines.ts b/packages/core/src/internal/data-grid/render/data-grid-render.lines.ts index cd4b4cf8d..c1a58191e 100644 --- a/packages/core/src/internal/data-grid/render/data-grid-render.lines.ts +++ b/packages/core/src/internal/data-grid/render/data-grid-render.lines.ts @@ -26,6 +26,7 @@ export function drawBlanks( selectedRows: CompactSelection, disabledRows: CompactSelection, freezeTrailingRows: number, + freezeTrailingColumns: number, hasAppendRow: boolean, drawRegions: readonly Rectangle[], damage: CellSet | undefined, @@ -41,10 +42,12 @@ export function drawBlanks( walkColumns( effectiveColumns, + width, cellYOffset, translateX, translateY, totalHeaderHeight, + freezeTrailingColumns, (c, drawX, colDrawY, clipX, startRow) => { if (c !== effectiveColumns[effectiveColumns.length - 1]) return; drawX += c.width; @@ -123,18 +126,27 @@ export function overdrawStickyBoundaries( } const hColor = theme.horizontalBorderColor ?? theme.borderColor; const vColor = theme.borderColor; - const drawX = drawFreezeBorder ? getStickyWidth(effectiveCols) : 0; + const [drawXLeft, drawXRight] = drawFreezeBorder ? getStickyWidth(effectiveCols) : [0, 0]; let vStroke: string | undefined; - if (drawX !== 0) { + if (drawXLeft !== 0) { vStroke = blendCache(vColor, theme.bgCell); ctx.beginPath(); - ctx.moveTo(drawX + 0.5, 0); - ctx.lineTo(drawX + 0.5, height); + ctx.moveTo(drawXLeft + 0.5, 0); + ctx.lineTo(drawXLeft + 0.5, height); ctx.strokeStyle = vStroke; ctx.stroke(); } + if (drawXRight !== 0) { + const hStroke = vColor === hColor && vStroke !== undefined ? vStroke : blendCache(hColor, theme.bgCell); + ctx.beginPath(); + ctx.moveTo(width - drawXRight + 0.5, 0); + ctx.lineTo(width - drawXRight + 0.5, height); + ctx.strokeStyle = hStroke; + ctx.stroke(); + } + if (freezeTrailingRows > 0) { const hStroke = vColor === hColor && vStroke !== undefined ? vStroke : blendCache(hColor, theme.bgCell); const h = getFreezeTrailingHeight(rows, freezeTrailingRows, getRowHeight); @@ -332,6 +344,24 @@ export function drawGridLines( } } + // let rightX = 0.5; + // for (let index = effectiveCols.length - 1; index >= 0; index--) { + // const c = effectiveCols[index]; + // if (c.width === 0) continue; + // if (!c.sticky) break; + // rightX += c.width; + // const tx = c.sticky ? rightX : rightX + translateX; + // if (tx >= minX && tx <= maxX && verticalBorder(index + 1)) { + // toDraw.push({ + // x1: tx, + // y1: Math.max(groupHeaderHeight, minY), + // x2: tx, + // y2: Math.min(height, maxY), + // color: vColor, + // }); + // } + // } + let freezeY = height + 0.5; for (let i = rows - freezeTrailingRows; i < rows; i++) { const rh = getRowHeight(i); diff --git a/packages/core/src/internal/data-grid/render/data-grid-render.ts b/packages/core/src/internal/data-grid/render/data-grid-render.ts index 4946b1557..754d09c6a 100644 --- a/packages/core/src/internal/data-grid/render/data-grid-render.ts +++ b/packages/core/src/internal/data-grid/render/data-grid-render.ts @@ -33,6 +33,7 @@ function clipHeaderDamage( translateX: number, translateY: number, cellYOffset: number, + freezeTrailingColumns: number, damage: CellSet | undefined ): void { if (damage === undefined || damage.size === 0) return; @@ -53,10 +54,12 @@ function clipHeaderDamage( walkColumns( effectiveColumns, + width, cellYOffset, translateX, translateY, totalHeaderHeight, + freezeTrailingColumns, (c, drawX, _colDrawY, clipX) => { const diff = Math.max(0, clipX - drawX); @@ -73,6 +76,7 @@ function clipHeaderDamage( function getLastRow( effectiveColumns: readonly MappedGridColumn[], height: number, + width: number, totalHeaderHeight: number, translateX: number, translateY: number, @@ -80,15 +84,18 @@ function getLastRow( rows: number, getRowHeight: (row: number) => number, freezeTrailingRows: number, - hasAppendRow: boolean + hasAppendRow: boolean, + freezeTrailingColumns: number ): number { let result = 0; walkColumns( effectiveColumns, + width, cellYOffset, translateX, translateY, totalHeaderHeight, + freezeTrailingColumns, (_c, __drawX, colDrawY, _clipX, startRow) => { walkRowsInCol( startRow, @@ -171,6 +178,9 @@ export function drawGrid(arg: DrawGridArg, lastArg: DrawGridArg | undefined) { const doubleBuffer = renderStrategy === "double-buffer"; const dpr = Math.min(maxScaleFactor, Math.ceil(window.devicePixelRatio ?? 1)); + const freezeLeftColumns = typeof freezeColumns === "number" ? freezeColumns : freezeColumns[0]; + const freezeRightColumns = typeof freezeColumns === "number" ? 0 : freezeColumns[1]; + // if we are double buffering we need to make sure we can blit. If we can't we need to redraw the whole thing const canBlit = renderStrategy !== "direct" && computeCanBlit(arg, lastArg); @@ -253,7 +263,14 @@ export function drawGrid(arg: DrawGridArg, lastArg: DrawGridArg | undefined) { targetCtx.scale(dpr, dpr); } - const effectiveCols = getEffectiveColumns(mappedColumns, cellXOffset, width, dragAndDropState, translateX); + const effectiveCols = getEffectiveColumns( + mappedColumns, + cellXOffset, + width, + freezeColumns, + dragAndDropState, + translateX + ); let drawRegions: Rectangle[] = []; @@ -287,7 +304,8 @@ export function drawGrid(arg: DrawGridArg, lastArg: DrawGridArg | undefined) { getGroupDetails, damage, drawHeaderCallback, - touchMode + touchMode, + freezeRightColumns ); drawGridLines( @@ -357,6 +375,7 @@ export function drawGrid(arg: DrawGridArg, lastArg: DrawGridArg | undefined) { getRowHeight, getCellContent, freezeTrailingRows, + freezeRightColumns, hasAppendRow, fillHandle, rows @@ -383,13 +402,13 @@ export function drawGrid(arg: DrawGridArg, lastArg: DrawGridArg | undefined) { { x: 0, y: cellYOffset, - width: freezeColumns, + width: freezeLeftColumns, height: 300, }, { x: 0, y: -2, - width: freezeColumns, + width: freezeLeftColumns, height: 2, }, { @@ -407,6 +426,7 @@ export function drawGrid(arg: DrawGridArg, lastArg: DrawGridArg | undefined) { effectiveCols, mappedColumns, height, + width, totalHeaderHeight, translateX, translateY, @@ -420,6 +440,7 @@ export function drawGrid(arg: DrawGridArg, lastArg: DrawGridArg | undefined) { isFocused, drawFocus, freezeTrailingRows, + freezeRightColumns, hasAppendRow, drawRegions, damage, @@ -463,6 +484,7 @@ export function drawGrid(arg: DrawGridArg, lastArg: DrawGridArg | undefined) { getRowHeight, getCellContent, freezeTrailingRows, + freezeRightColumns, hasAppendRow, fillHandle, rows @@ -491,6 +513,7 @@ export function drawGrid(arg: DrawGridArg, lastArg: DrawGridArg | undefined) { translateX, translateY, cellYOffset, + freezeRightColumns, damage ); drawHeaderTexture(); @@ -550,7 +573,8 @@ export function drawGrid(arg: DrawGridArg, lastArg: DrawGridArg | undefined) { height, totalHeaderHeight, effectiveCols, - resizedCol + resizedCol, + freezeRightColumns ); } @@ -602,6 +626,7 @@ export function drawGrid(arg: DrawGridArg, lastArg: DrawGridArg | undefined) { getRowHeight, getCellContent, freezeTrailingRows, + freezeRightColumns, hasAppendRow, fillHandle, rows @@ -626,6 +651,7 @@ export function drawGrid(arg: DrawGridArg, lastArg: DrawGridArg | undefined) { effectiveCols, mappedColumns, height, + width, totalHeaderHeight, translateX, translateY, @@ -639,6 +665,7 @@ export function drawGrid(arg: DrawGridArg, lastArg: DrawGridArg | undefined) { isFocused, drawFocus, freezeTrailingRows, + freezeRightColumns, hasAppendRow, drawRegions, damage, @@ -675,6 +702,7 @@ export function drawGrid(arg: DrawGridArg, lastArg: DrawGridArg | undefined) { selection.rows, disabledRows, freezeTrailingRows, + freezeRightColumns, hasAppendRow, drawRegions, damage, @@ -723,7 +751,7 @@ export function drawGrid(arg: DrawGridArg, lastArg: DrawGridArg | undefined) { focusRedraw?.(); if (isResizing && resizeIndicator !== "none") { - walkColumns(effectiveCols, 0, translateX, 0, totalHeaderHeight, (c, x) => { + walkColumns(effectiveCols, width, 0, translateX, 0, totalHeaderHeight, freezeRightColumns, (c, x) => { if (c.sourceIndex === resizeCol) { drawColumnResizeOutline( overlayCtx, @@ -756,6 +784,7 @@ export function drawGrid(arg: DrawGridArg, lastArg: DrawGridArg | undefined) { const lastRowDrawn = getLastRow( effectiveCols, height, + width, totalHeaderHeight, translateX, translateY, @@ -763,7 +792,8 @@ export function drawGrid(arg: DrawGridArg, lastArg: DrawGridArg | undefined) { rows, getRowHeight, freezeTrailingRows, - hasAppendRow + hasAppendRow, + freezeRightColumns ); imageLoader?.setWindow( diff --git a/packages/core/src/internal/data-grid/render/data-grid-render.walk.ts b/packages/core/src/internal/data-grid/render/data-grid-render.walk.ts index dbb3c8867..e62721d67 100644 --- a/packages/core/src/internal/data-grid/render/data-grid-render.walk.ts +++ b/packages/core/src/internal/data-grid/render/data-grid-render.walk.ts @@ -64,16 +64,20 @@ export type WalkColsCallback = ( export function walkColumns( effectiveCols: readonly MappedGridColumn[], + width: number, cellYOffset: number, translateX: number, translateY: number, totalHeaderHeight: number, + freezeTrailingColumns: number, cb: WalkColsCallback ): void { let x = 0; let clipX = 0; // this tracks the total width of sticky cols const drawY = totalHeaderHeight + translateY; - for (const c of effectiveCols) { + + for (let i = 0; i < effectiveCols.length - freezeTrailingColumns; i++) { + const c = effectiveCols[i]; const drawX = c.sticky ? clipX : x + translateX; if (cb(c, drawX, drawY, c.sticky ? 0 : clipX, cellYOffset) === true) { break; @@ -82,6 +86,15 @@ export function walkColumns( x += c.width; clipX += c.sticky ? c.width : 0; } + + x = width; + for (let fc = 0; fc < freezeTrailingColumns; fc++) { + const c = effectiveCols[effectiveCols.length - 1 - fc]; + const drawX = x - c.width; + + x -= c.width; + cb(c, drawX, drawY, clipX, cellYOffset); + } } // this should not be item, it is [startInclusive, endInclusive] diff --git a/packages/core/src/internal/data-grid/render/data-grid.render.rings.ts b/packages/core/src/internal/data-grid/render/data-grid.render.rings.ts index 864dd4f4d..f59d99966 100644 --- a/packages/core/src/internal/data-grid/render/data-grid.render.rings.ts +++ b/packages/core/src/internal/data-grid/render/data-grid.render.rings.ts @@ -17,7 +17,7 @@ export function drawHighlightRings( translateX: number, translateY: number, mappedColumns: readonly MappedGridColumn[], - freezeColumns: number, + freezeColumns: number | [left: number, right: number], headerHeight: number, groupHeaderHeight: number, rowHeight: number | ((index: number) => number), @@ -27,19 +27,21 @@ export function drawHighlightRings( theme: FullTheme ): (() => void) | undefined { const highlightRegions = allHighlightRegions?.filter(x => x.style !== "no-outline"); + const freezeLeftColumns = typeof freezeColumns === "number" ? freezeColumns : freezeColumns[0]; + const freezeRightColumns = typeof freezeColumns === "number" ? 0 : freezeColumns[1]; if (highlightRegions === undefined || highlightRegions.length === 0) return undefined; - const freezeLeft = getStickyWidth(mappedColumns); + const [freezeLeft, freezeRight] = getStickyWidth(mappedColumns); const freezeBottom = getFreezeTrailingHeight(rows, freezeTrailingRows, rowHeight); - const splitIndicies = [freezeColumns, 0, mappedColumns.length, rows - freezeTrailingRows] as const; + const splitIndices = [freezeLeftColumns, 0, mappedColumns.length, rows - freezeTrailingRows] as const; const splitLocations = [freezeLeft, 0, width, height - freezeBottom] as const; const drawRects = highlightRegions.map(h => { const r = h.range; const style = h.style ?? "dashed"; - return splitRectIntoRegions(r, splitIndicies, width, height, splitLocations).map(arg => { + return splitRectIntoRegions(r, splitIndices, width, height, splitLocations).map(arg => { const rect = arg.rect; const topLeftBounds = computeBounds( rect.x, @@ -187,6 +189,7 @@ export function drawFillHandle( getRowHeight: (row: number) => number, getCellContent: (cell: Item) => InnerGridCell, freezeTrailingRows: number, + freezeTrailingColumns: number, hasAppendRow: boolean, fillHandle: boolean, rows: number @@ -218,10 +221,12 @@ export function drawFillHandle( walkColumns( effectiveCols, + width, cellYOffset, translateX, translateY, totalHeaderHeight, + freezeTrailingColumns, (col, drawX, colDrawY, clipX, startRow) => { clipX; if (col.sticky && targetCol > col.sourceIndex) return; diff --git a/packages/core/src/internal/data-grid/render/draw-grid-arg.ts b/packages/core/src/internal/data-grid/render/draw-grid-arg.ts index 9221f91f7..fbb45eeb2 100644 --- a/packages/core/src/internal/data-grid/render/draw-grid-arg.ts +++ b/packages/core/src/internal/data-grid/render/draw-grid-arg.ts @@ -39,7 +39,7 @@ export interface DrawGridArg { readonly translateY: number; readonly mappedColumns: readonly MappedGridColumn[]; readonly enableGroups: boolean; - readonly freezeColumns: number; + readonly freezeColumns: number | [left: number, right: number]; readonly dragAndDropState: DragAndDropState | undefined; readonly theme: FullTheme; readonly headerHeight: number; diff --git a/packages/core/src/internal/scrolling-data-grid/scrolling-data-grid.tsx b/packages/core/src/internal/scrolling-data-grid/scrolling-data-grid.tsx index 841eeefbe..d9ce19ad9 100644 --- a/packages/core/src/internal/scrolling-data-grid/scrolling-data-grid.tsx +++ b/packages/core/src/internal/scrolling-data-grid/scrolling-data-grid.tsx @@ -102,6 +102,9 @@ const GridScroller: React.FunctionComponent = p => { const lastY = React.useRef(); const lastSize = React.useRef(); + const freezeLeftColumns = typeof freezeColumns === "number" ? freezeColumns : freezeColumns[0]; + const freezeRightColumns = typeof freezeColumns === "number"? 0 : freezeColumns[1]; + const width = nonGrowWidth + Math.max(0, overscrollX ?? 0); let height = enableGroups ? headerHeight + groupHeaderHeight : headerHeight; @@ -130,7 +133,7 @@ const GridScroller: React.FunctionComponent = p => { args.x = args.x < 0 ? 0 : args.x; let stickyColWidth = 0; - for (let i = 0; i < freezeColumns; i++) { + for (let i = 0; i < freezeLeftColumns; i++) { stickyColWidth += columns[i].width; } @@ -220,12 +223,7 @@ const GridScroller: React.FunctionComponent = p => { args.height !== lastSize.current?.[1] ) { onVisibleRegionChanged?.( - { - x: cellX, - y: cellY, - width: cellRight - cellX, - height: cellBottom - cellY, - }, + rect, args.width, args.height, args.paddingRight ?? 0, @@ -237,7 +235,7 @@ const GridScroller: React.FunctionComponent = p => { lastY.current = ty; lastSize.current = [args.width, args.height]; } - }, [columns, rowHeight, rows, onVisibleRegionChanged, freezeColumns, smoothScrollX, smoothScrollY]); + }, [columns, rowHeight, rows, onVisibleRegionChanged, freezeLeftColumns, smoothScrollX, smoothScrollY]); const onScrollUpdate = React.useCallback( (args: Rectangle & { paddingRight: number }) => { From 002d5311fcf1801ffd822ab73bf722eb92c7bf49 Mon Sep 17 00:00:00 2001 From: Rahul Yadav Date: Mon, 1 Jul 2024 17:17:09 +0530 Subject: [PATCH 03/11] revert workflow file --- .github/workflows/release.js.yml | 72 ++++++++++++++++---------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/.github/workflows/release.js.yml b/.github/workflows/release.js.yml index e49555160..4924ac7b0 100644 --- a/.github/workflows/release.js.yml +++ b/.github/workflows/release.js.yml @@ -28,39 +28,39 @@ jobs: token: ${{ secrets.NPM_TOKEN }} access: public package: ./packages/core/package.json - # publish-cells: - # defaults: - # run: - # working-directory: ./packages/cells - # runs-on: ubuntu-latest - # steps: - # - uses: actions/checkout@v4 - # - uses: actions/setup-node@v4 - # with: - # node-version: 20.10.0 - # - name: Bootstrap - # run: npm install && npm run build - # working-directory: . - # - uses: JS-DevTools/npm-publish@v1 - # with: - # token: ${{ secrets.NPM_TOKEN }} - # access: public - # package: ./packages/cells/package.json - # publish-source: - # defaults: - # run: - # working-directory: ./packages/source - # runs-on: ubuntu-latest - # steps: - # - uses: actions/checkout@v4 - # - uses: actions/setup-node@v4 - # with: - # node-version: 20.10.0 - # - name: Bootstrap - # run: npm install && npm run build - # working-directory: . - # - uses: JS-DevTools/npm-publish@v1 - # with: - # token: ${{ secrets.NPM_TOKEN }} - # access: public - # package: ./packages/source/package.json + publish-cells: + defaults: + run: + working-directory: ./packages/cells + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20.10.0 + - name: Bootstrap + run: npm install && npm run build + working-directory: . + - uses: JS-DevTools/npm-publish@v1 + with: + token: ${{ secrets.NPM_TOKEN }} + access: public + package: ./packages/cells/package.json + publish-source: + defaults: + run: + working-directory: ./packages/source + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20.10.0 + - name: Bootstrap + run: npm install && npm run build + working-directory: . + - uses: JS-DevTools/npm-publish@v1 + with: + token: ${{ secrets.NPM_TOKEN }} + access: public + package: ./packages/source/package.json From 7167e4615e00225b952285d2e283ec9849ddcde0 Mon Sep 17 00:00:00 2001 From: Rahul Yadav Date: Tue, 2 Jul 2024 15:27:12 +0530 Subject: [PATCH 04/11] fixed selection issue and highlight fixes --- packages/core/src/data-editor/data-editor.tsx | 5 +-- .../core/src/internal/data-grid/data-grid.tsx | 7 +++- .../data-grid/render/data-grid-lib.ts | 16 ++++++--- .../data-grid/render/data-grid-render.blit.ts | 15 ++++++-- .../render/data-grid-render.cells.ts | 2 +- .../render/data-grid-render.header.ts | 5 +++ .../render/data-grid-render.lines.ts | 35 ++++++++++--------- .../data-grid/render/data-grid-render.ts | 12 +++++++ .../render/data-grid.render.rings.ts | 9 +++-- .../scrolling-data-grid.tsx | 10 +----- 10 files changed, 77 insertions(+), 39 deletions(-) diff --git a/packages/core/src/data-editor/data-editor.tsx b/packages/core/src/data-editor/data-editor.tsx index fb85dbe74..0fb36f8c0 100644 --- a/packages/core/src/data-editor/data-editor.tsx +++ b/packages/core/src/data-editor/data-editor.tsx @@ -911,7 +911,7 @@ const DataEditorImpl: React.ForwardRefRenderFunction { @@ -1581,7 +1581,8 @@ const DataEditorImpl: React.ForwardRefRenderFunction = (p, let isEdge = bounds !== undefined && bounds.x + bounds.width - posX <= edgeDetectionBuffer; const previousCol = col - 1; - if (posX - bounds.x <= edgeDetectionBuffer && previousCol >= 0) { + if ( + posX - bounds.x <= edgeDetectionBuffer && + previousCol >= 0 && + col < mappedColumns.length - freezeRightColumns + ) { isEdge = true; bounds = getBoundsForItem(canvas, previousCol, row); assert(bounds !== undefined); @@ -722,6 +726,7 @@ const DataGrid: React.ForwardRefRenderFunction = (p, selection, totalHeaderHeight, freezeColumns, + freezeRightColumns, ] ); diff --git a/packages/core/src/internal/data-grid/render/data-grid-lib.ts b/packages/core/src/internal/data-grid/render/data-grid-lib.ts index 8fd5bb604..5b075231f 100644 --- a/packages/core/src/internal/data-grid/render/data-grid-lib.ts +++ b/packages/core/src/internal/data-grid/render/data-grid-lib.ts @@ -279,7 +279,7 @@ export function getColumnIndexForX( const colIdx = effectiveColumns.length - 1 - fc; const col = effectiveColumns[colIdx]; y -= col.width; - if (targetX <= y) { + if (targetX >= y) { return col.sourceIndex; } } @@ -818,6 +818,7 @@ export function computeBounds( const freezeLeftColumns = typeof freezeColumns === "number" ? freezeColumns : freezeColumns[0]; const freezeRightColumns = typeof freezeColumns === "number" ? 0 : freezeColumns[1]; + const column = mappedColumns[col]; if (col >= mappedColumns.length || row >= rows || row < -2 || col < 0) { return result; @@ -825,17 +826,22 @@ export function computeBounds( const headerHeight = totalHeaderHeight - groupHeaderHeight; - if (col >= freezeLeftColumns) { + if (col >= freezeLeftColumns && col < mappedColumns.length - freezeRightColumns) { const dir = cellXOffset > col ? -1 : 1; - const [freezeLeftWidth, freezeRightWidth] = getStickyWidth(mappedColumns); + const [freezeLeftWidth] = getStickyWidth(mappedColumns); result.x += freezeLeftWidth + translateX; for (let i = cellXOffset; i !== col; i += dir) { result.x += mappedColumns[dir === 1 ? i : i - 1].width * dir; } - } else { + } else if (column.stickyPosition === "left") { for (let i = 0; i < col; i++) { result.x += mappedColumns[i].width; } + } else if (column.stickyPosition === "right") { + result.x = width; + for (let i = col; i < mappedColumns.length; i++) { + result.x -= mappedColumns[i].width; + } } result.width = mappedColumns[col].width + 1; @@ -871,7 +877,7 @@ export function computeBounds( end++; } if (!sticky) { - const [freezeLeftWidth, freezeRightWidth] = getStickyWidth(mappedColumns); + const [freezeLeftWidth] = getStickyWidth(mappedColumns); const clip = result.x - freezeLeftWidth; if (clip < 0) { result.x -= clip; diff --git a/packages/core/src/internal/data-grid/render/data-grid-render.blit.ts b/packages/core/src/internal/data-grid/render/data-grid-render.blit.ts index a6c9806d2..dbf777cfd 100644 --- a/packages/core/src/internal/data-grid/render/data-grid-render.blit.ts +++ b/packages/core/src/internal/data-grid/render/data-grid-render.blit.ts @@ -147,9 +147,9 @@ export function blitLastFrame( args.dw = blitWidth * dpr; drawRegions.push({ - x: width + deltaX, + x: width + deltaX - stickyRightWidth, y: 0, - width: -deltaX, + width: -deltaX + stickyRightWidth, height: height, }); } @@ -168,6 +168,17 @@ export function blitLastFrame( const h = height * dpr; ctx.drawImage(blitSource, 0, 0, w, h, 0, 0, w, h); } + if ( + stickyRightWidth > 0 && + deltaX !== 0 && + deltaY === 0 && + (targetScroll === undefined || blitSourceScroll?.[1] !== false) + ) { + const x = (width - stickyRightWidth) * dpr; + const w = stickyRightWidth * dpr; + const h = height * dpr; + ctx.drawImage(blitSource, x, 0, w, h, x, 0, w, h); + } if ( freezeTrailingRowsHeight > 0 && deltaX === 0 && diff --git a/packages/core/src/internal/data-grid/render/data-grid-render.cells.ts b/packages/core/src/internal/data-grid/render/data-grid-render.cells.ts index fed1f21f4..a5cd1a46d 100644 --- a/packages/core/src/internal/data-grid/render/data-grid-render.cells.ts +++ b/packages/core/src/internal/data-grid/render/data-grid-render.cells.ts @@ -299,7 +299,7 @@ export function drawCells( const bgCell = cell.kind === GridCellKind.Protected ? theme.bgCellMedium : theme.bgCell; let fill: string | undefined; - if (isSticky || bgCell !== outerTheme.bgCell) { + if (isSticky || bgCell !== outerTheme.bgCell || c.sticky) { fill = blend(bgCell, fill); } diff --git a/packages/core/src/internal/data-grid/render/data-grid-render.header.ts b/packages/core/src/internal/data-grid/render/data-grid-render.header.ts index daf594b85..f1b2c62c0 100644 --- a/packages/core/src/internal/data-grid/render/data-grid-render.header.ts +++ b/packages/core/src/internal/data-grid/render/data-grid-render.header.ts @@ -69,6 +69,11 @@ export function drawGridHeaders( ? outerTheme : mergeAndRealizeTheme(outerTheme, groupTheme, c.themeOverride); + if (c.sticky) { + ctx.fillStyle = theme.bgHeader; + ctx.fill(); + } + if (theme.bgHeader !== outerTheme.bgHeader) { ctx.fillStyle = theme.bgHeader; ctx.fill(); diff --git a/packages/core/src/internal/data-grid/render/data-grid-render.lines.ts b/packages/core/src/internal/data-grid/render/data-grid-render.lines.ts index c1a58191e..a4e0fb8aa 100644 --- a/packages/core/src/internal/data-grid/render/data-grid-render.lines.ts +++ b/packages/core/src/internal/data-grid/render/data-grid-render.lines.ts @@ -331,6 +331,7 @@ export function drawGridLines( for (let index = 0; index < effectiveCols.length; index++) { const c = effectiveCols[index]; if (c.width === 0) continue; + if (c.sticky && c.stickyPosition !== "left") break; x += c.width; const tx = c.sticky ? x : x + translateX; if (tx >= minX && tx <= maxX && verticalBorder(index + 1)) { @@ -344,23 +345,23 @@ export function drawGridLines( } } - // let rightX = 0.5; - // for (let index = effectiveCols.length - 1; index >= 0; index--) { - // const c = effectiveCols[index]; - // if (c.width === 0) continue; - // if (!c.sticky) break; - // rightX += c.width; - // const tx = c.sticky ? rightX : rightX + translateX; - // if (tx >= minX && tx <= maxX && verticalBorder(index + 1)) { - // toDraw.push({ - // x1: tx, - // y1: Math.max(groupHeaderHeight, minY), - // x2: tx, - // y2: Math.min(height, maxY), - // color: vColor, - // }); - // } - // } + let rightX = width + 0.5; + for (let index = effectiveCols.length - 1; index >= 0; index--) { + const c = effectiveCols[index]; + if (c.width === 0) continue; + if (!c.sticky) break; + rightX -= c.width; + const tx = rightX; + if (tx >= minX && tx <= maxX && verticalBorder(index + 1)) { + toDraw.push({ + x1: tx, + y1: Math.max(groupHeaderHeight, minY), + x2: tx, + y2: Math.min(height, maxY), + color: vColor, + }); + } + } let freezeY = height + 0.5; for (let i = rows - freezeTrailingRows; i < rows; i++) { diff --git a/packages/core/src/internal/data-grid/render/data-grid-render.ts b/packages/core/src/internal/data-grid/render/data-grid-render.ts index 754d09c6a..752b62a4e 100644 --- a/packages/core/src/internal/data-grid/render/data-grid-render.ts +++ b/packages/core/src/internal/data-grid/render/data-grid-render.ts @@ -418,6 +418,18 @@ export function drawGrid(arg: DrawGridArg, lastArg: DrawGridArg | undefined) { height: freezeTrailingRows, when: freezeTrailingRows > 0, }, + { + x: viewRegionWidth - freezeRightColumns, + y: cellYOffset, + width: freezeRightColumns, + height: 300, + }, + { + x: viewRegionWidth - freezeRightColumns, + y: -2, + width: freezeRightColumns, + height: 2, + }, ]); const doDamage = (ctx: CanvasRenderingContext2D) => { diff --git a/packages/core/src/internal/data-grid/render/data-grid.render.rings.ts b/packages/core/src/internal/data-grid/render/data-grid.render.rings.ts index f59d99966..12715b54f 100644 --- a/packages/core/src/internal/data-grid/render/data-grid.render.rings.ts +++ b/packages/core/src/internal/data-grid/render/data-grid.render.rings.ts @@ -34,8 +34,13 @@ export function drawHighlightRings( const [freezeLeft, freezeRight] = getStickyWidth(mappedColumns); const freezeBottom = getFreezeTrailingHeight(rows, freezeTrailingRows, rowHeight); - const splitIndices = [freezeLeftColumns, 0, mappedColumns.length, rows - freezeTrailingRows] as const; - const splitLocations = [freezeLeft, 0, width, height - freezeBottom] as const; + const splitIndices = [ + freezeLeftColumns, + 0, + mappedColumns.length - freezeRightColumns, + rows - freezeTrailingRows, + ] as const; + const splitLocations = [freezeLeft, 0, width - freezeRight, height - freezeBottom] as const; const drawRects = highlightRegions.map(h => { const r = h.range; diff --git a/packages/core/src/internal/scrolling-data-grid/scrolling-data-grid.tsx b/packages/core/src/internal/scrolling-data-grid/scrolling-data-grid.tsx index d9ce19ad9..63af6dfdf 100644 --- a/packages/core/src/internal/scrolling-data-grid/scrolling-data-grid.tsx +++ b/packages/core/src/internal/scrolling-data-grid/scrolling-data-grid.tsx @@ -103,7 +103,6 @@ const GridScroller: React.FunctionComponent = p => { const lastSize = React.useRef(); const freezeLeftColumns = typeof freezeColumns === "number" ? freezeColumns : freezeColumns[0]; - const freezeRightColumns = typeof freezeColumns === "number"? 0 : freezeColumns[1]; const width = nonGrowWidth + Math.max(0, overscrollX ?? 0); @@ -222,14 +221,7 @@ const GridScroller: React.FunctionComponent = p => { args.width !== lastSize.current?.[0] || args.height !== lastSize.current?.[1] ) { - onVisibleRegionChanged?.( - rect, - args.width, - args.height, - args.paddingRight ?? 0, - tx, - ty - ); + onVisibleRegionChanged?.(rect, args.width, args.height, args.paddingRight ?? 0, tx, ty); last.current = rect; lastX.current = tx; lastY.current = ty; From c7a826cf7477e37d577c5c6d2eece0ad40ebfae4 Mon Sep 17 00:00:00 2001 From: Mihran Margaryan Date: Sun, 22 Jun 2025 17:16:26 +0400 Subject: [PATCH 05/11] freeze right columns: calculations, hovers, groupings and shadows --- .../core/src/internal/data-grid/data-grid.tsx | 23 +- .../data-grid/render/data-grid-lib.ts | 17 +- .../render/data-grid-render.cells.ts | 14 +- .../render/data-grid-render.header.ts | 367 ++++++++++-------- .../render/data-grid-render.lines.ts | 4 +- .../data-grid/render/data-grid-render.ts | 71 ++-- .../data-grid/render/data-grid-render.walk.ts | 50 ++- .../render/data-grid.render.rings.ts | 35 +- packages/core/test/data-editor.test.tsx | 47 +++ packages/core/test/data-grid.test.tsx | 67 ++++ packages/source/src/use-collapsing-groups.ts | 11 +- 11 files changed, 465 insertions(+), 241 deletions(-) diff --git a/packages/core/src/internal/data-grid/data-grid.tsx b/packages/core/src/internal/data-grid/data-grid.tsx index 3ec3e26a6..6524fa537 100644 --- a/packages/core/src/internal/data-grid/data-grid.tsx +++ b/packages/core/src/internal/data-grid/data-grid.tsx @@ -480,7 +480,7 @@ const DataGrid: React.ForwardRefRenderFunction = (p, translateX, translateY, rows, - freezeLeftColumns, + freezeColumns, freezeTrailingRows, mappedColumns, rowHeight @@ -508,7 +508,7 @@ const DataGrid: React.ForwardRefRenderFunction = (p, translateX, translateY, rows, - freezeLeftColumns, + freezeColumns, freezeTrailingRows, mappedColumns, rowHeight, @@ -1901,12 +1901,27 @@ const DataGrid: React.ForwardRefRenderFunction = (p, ? 1 : clamp(-translateX / 100, 0, 1); + let translateXRight = 0; + + if (eventTargetRef?.current) { + translateXRight = eventTargetRef?.current?.scrollLeft + width - eventTargetRef?.current?.scrollWidth; + } + const opacityXRight = freezeRightColumns === 0 || !fixedShadowX ? 0 - : cellXOffset + width < columns.length - freezeRightColumns + : cellXOffset + + getEffectiveColumns( + mappedColumns, + cellXOffset, + width, + freezeColumns, + dragAndDropState, + translateX + ).filter(column => !column.sticky).length < + columns.length - freezeRightColumns ? 1 - : clamp((translateX - (columns.length - freezeRightColumns - width) * 32) / 100, 0, 1); + : clamp(-translateXRight / 100, 0, 1); const absoluteOffsetY = -cellYOffset * 32 + translateY; const opacityY = !fixedShadowY ? 0 : clamp(-absoluteOffsetY / 100, 0, 1); diff --git a/packages/core/src/internal/data-grid/render/data-grid-lib.ts b/packages/core/src/internal/data-grid/render/data-grid-lib.ts index 5b075231f..eef07c677 100644 --- a/packages/core/src/internal/data-grid/render/data-grid-lib.ts +++ b/packages/core/src/internal/data-grid/render/data-grid-lib.ts @@ -243,6 +243,19 @@ export function getEffectiveColumns( width -= c.width; } } + + const stickyRight: MappedGridColumn[] = []; + + for (let i = mappedCols.length - freezeRightColumns; i < mappedCols.length; i++) { + stickyRight.push(mappedCols[i]); + } + + if (stickyRight.length > 0) { + for (const c of stickyRight) { + width -= c.width; + } + } + let endIndex = cellXOffset; let curX = tx ?? 0; @@ -258,9 +271,7 @@ export function getEffectiveColumns( } } - for (let i = mappedCols.length - freezeRightColumns; i < mappedCols.length; i++) { - sticky.push(mappedCols[i]); - } + sticky.push(...stickyRight); return sticky; } diff --git a/packages/core/src/internal/data-grid/render/data-grid-render.cells.ts b/packages/core/src/internal/data-grid/render/data-grid-render.cells.ts index a5cd1a46d..36cb36300 100644 --- a/packages/core/src/internal/data-grid/render/data-grid-render.cells.ts +++ b/packages/core/src/internal/data-grid/render/data-grid-render.cells.ts @@ -132,12 +132,12 @@ export function drawCells( translateY, totalHeaderHeight, freezeTrailingColumns, - (c, drawX, colDrawStartY, clipX, startRow) => { + (c, drawX, colDrawStartY, clipX, clipXRight, startRow) => { const diff = Math.max(0, clipX - drawX); const colDrawX = drawX + diff; const colDrawY = totalHeaderHeight + 1; - const colWidth = c.width - diff; + const colWidth = c.stickyPosition === "right" ? c.width - diff : Math.min(c.width - diff, width - drawX - clipXRight); const colHeight = height - totalHeaderHeight - 1; if (drawRegions.length > 0) { let found = false; @@ -555,11 +555,11 @@ export function drawCell( partialPrepResult === undefined ? undefined : { - deprep: partialPrepResult?.deprep, - fillStyle: partialPrepResult?.fillStyle, - font: partialPrepResult?.font, - renderer: r, - }; + deprep: partialPrepResult?.deprep, + fillStyle: partialPrepResult?.fillStyle, + font: partialPrepResult?.font, + renderer: r, + }; } if (needsAnim || animationFrameRequested) enqueue?.(allocatedItem); diff --git a/packages/core/src/internal/data-grid/render/data-grid-render.header.ts b/packages/core/src/internal/data-grid/render/data-grid-render.header.ts index f1b2c62c0..9e053452d 100644 --- a/packages/core/src/internal/data-grid/render/data-grid-render.header.ts +++ b/packages/core/src/internal/data-grid/render/data-grid-render.header.ts @@ -55,85 +55,107 @@ export function drawGridHeaders( const font = outerTheme.headerFontFull; // Assinging the context font too much can be expensive, it can be worth it to minimze this ctx.font = font; - walkColumns(effectiveCols, width, 0, translateX, 0, totalHeaderHeight, freezeTrailingColumns, (c, x, _y, clipX) => { - if (damage !== undefined && !damage.has([c.sourceIndex, -1])) return; - const diff = Math.max(0, clipX - x); - ctx.save(); - ctx.beginPath(); - ctx.rect(x + diff, groupHeaderHeight, c.width - diff, headerHeight); - ctx.clip(); - - const groupTheme = getGroupDetails(c.group ?? "").overrideTheme; - const theme = - c.themeOverride === undefined && groupTheme === undefined - ? outerTheme - : mergeAndRealizeTheme(outerTheme, groupTheme, c.themeOverride); - - if (c.sticky) { - ctx.fillStyle = theme.bgHeader; - ctx.fill(); - } - - if (theme.bgHeader !== outerTheme.bgHeader) { - ctx.fillStyle = theme.bgHeader; - ctx.fill(); - } - - if (theme !== outerTheme) { - ctx.font = theme.headerFontFull; - } - const selected = selection.columns.hasIndex(c.sourceIndex); - const noHover = dragAndDropState !== undefined || isResizing || c.headerRowMarkerDisabled === true; - const hoveredBoolean = !noHover && hRow === -1 && hCol === c.sourceIndex; - const hover = noHover - ? 0 - : (hoverValues.find(s => s.item[0] === c.sourceIndex && s.item[1] === -1)?.hoverAmount ?? 0); - - const hasSelectedCell = selection?.current !== undefined && selection.current.cell[0] === c.sourceIndex; - - const bgFillStyle = selected ? theme.accentColor : hasSelectedCell ? theme.bgHeaderHasFocus : theme.bgHeader; + walkColumns( + effectiveCols, + width, + 0, + translateX, + 0, + totalHeaderHeight, + freezeTrailingColumns, + (c, x, _y, clipX, clipXRight) => { + if (damage !== undefined && !damage.has([c.sourceIndex, -1])) return; + const diff = Math.max(0, clipX - x); + + let rectWidth = + c.stickyPosition === "right" ? c.width - diff : Math.min(c.width - diff, width - x - clipXRight); + + ctx.save(); + ctx.beginPath(); + ctx.rect(x + diff, groupHeaderHeight, rectWidth, headerHeight); + ctx.clip(); - const y = enableGroups ? groupHeaderHeight : 0; - const xOffset = c.sourceIndex === 0 ? 0 : 1; + const groupTheme = getGroupDetails(c.group ?? "").overrideTheme; + const theme = + c.themeOverride === undefined && groupTheme === undefined + ? outerTheme + : mergeAndRealizeTheme(outerTheme, groupTheme, c.themeOverride); - if (selected) { - ctx.fillStyle = bgFillStyle; - ctx.fillRect(x + xOffset, y, c.width - xOffset, headerHeight); - } else if (hasSelectedCell || hover > 0) { - ctx.beginPath(); - ctx.rect(x + xOffset, y, c.width - xOffset, headerHeight); - if (hasSelectedCell) { - ctx.fillStyle = theme.bgHeaderHasFocus; + if (c.sticky === true) { + ctx.fillStyle = theme.bgHeader; ctx.fill(); } - if (hover > 0) { - ctx.globalAlpha = hover; - ctx.fillStyle = theme.bgHeaderHovered; + + if (theme.bgHeader !== outerTheme.bgHeader) { + ctx.fillStyle = theme.bgHeader; ctx.fill(); - ctx.globalAlpha = 1; } - } - drawHeader( - ctx, - x, - y, - c.width, - headerHeight, - c, - selected, - theme, - hoveredBoolean, - hoveredBoolean ? hPosX : undefined, - hoveredBoolean ? hPosY : undefined, - hasSelectedCell, - hover, - spriteManager, - drawHeaderCallback, - touchMode - ); - ctx.restore(); - }); + if (theme !== outerTheme) { + ctx.font = theme.headerFontFull; + } + const selected = selection.columns.hasIndex(c.sourceIndex); + const noHover = dragAndDropState !== undefined || isResizing || c.headerRowMarkerDisabled === true; + const hoveredBoolean = !noHover && hRow === -1 && hCol === c.sourceIndex; + const hover = noHover + ? 0 + : (hoverValues.find(s => s.item[0] === c.sourceIndex && s.item[1] === -1)?.hoverAmount ?? 0); + + const hasSelectedCell = selection?.current !== undefined && selection.current.cell[0] === c.sourceIndex; + + const bgFillStyle = selected + ? theme.accentColor + : hasSelectedCell + ? theme.bgHeaderHasFocus + : theme.bgHeader; + + const y = enableGroups ? groupHeaderHeight : 0; + const xOffset = c.sourceIndex === 0 ? 0 : 1; + + if (selected) { + ctx.fillStyle = bgFillStyle; + ctx.fillRect(x + xOffset, y, c.width - xOffset, headerHeight); + } else if (hasSelectedCell || hover > 0) { + rectWidth = + c.stickyPosition === "right" + ? c.width - xOffset + : Math.min(c.width - xOffset, width - x - clipXRight); + + ctx.beginPath(); + ctx.rect(x + xOffset, y, rectWidth, headerHeight); + if (hasSelectedCell) { + ctx.fillStyle = theme.bgHeaderHasFocus; + ctx.fill(); + } + if (hover > 0) { + ctx.globalAlpha = hover; + ctx.fillStyle = theme.bgHeaderHovered; + ctx.fill(); + ctx.globalAlpha = 1; + } + } + + drawHeader( + ctx, + x, + y, + c.width, + headerHeight, + c, + selected, + theme, + hoveredBoolean, + hoveredBoolean ? hPosX : undefined, + hoveredBoolean ? hPosY : undefined, + hasSelectedCell, + hover, + spriteManager, + drawHeaderCallback, + touchMode + ); + ctx.restore(); + } + ); if (enableGroups) { drawGroups( @@ -148,7 +170,8 @@ export function drawGridHeaders( hoverValues, verticalBorder, getGroupDetails, - damage + damage, + freezeTrailingColumns ); } } @@ -165,120 +188,128 @@ export function drawGroups( _hoverValues: HoverValues, verticalBorder: (col: number) => boolean, getGroupDetails: GroupDetailsCallback, - damage: CellSet | undefined + damage: CellSet | undefined, + freezeTrailingColumns: number ) { const xPad = 8; const [hCol, hRow] = hovered?.[0] ?? []; let finalX = 0; - walkGroups(effectiveCols, width, translateX, groupHeaderHeight, (span, groupName, x, y, w, h) => { - if ( - damage !== undefined && - !damage.hasItemInRectangle({ - x: span[0], - y: -2, - width: span[1] - span[0] + 1, - height: 1, - }) - ) - return; - ctx.save(); - ctx.beginPath(); - ctx.rect(x, y, w, h); - ctx.clip(); - - const group = getGroupDetails(groupName); - const groupTheme = - group?.overrideTheme === undefined ? theme : mergeAndRealizeTheme(theme, group.overrideTheme); - const isHovered = hRow === -2 && hCol !== undefined && hCol >= span[0] && hCol <= span[1]; - const fillColor = isHovered - ? (groupTheme.bgGroupHeaderHovered ?? groupTheme.bgHeaderHovered) - : (groupTheme.bgGroupHeader ?? groupTheme.bgHeader); - - if (fillColor !== theme.bgHeader) { - ctx.fillStyle = fillColor; - ctx.fill(); - } - - ctx.fillStyle = groupTheme.textGroupHeader ?? groupTheme.textHeader; - if (group !== undefined) { - let drawX = x; - if (group.icon !== undefined) { - spriteManager.drawSprite( - group.icon, - "normal", - ctx, - drawX + xPad, - (groupHeaderHeight - 20) / 2, - 20, - groupTheme - ); - drawX += 26; - } - ctx.fillText( - group.name, - drawX + xPad, - groupHeaderHeight / 2 + getMiddleCenterBias(ctx, theme.headerFontFull) - ); - - if (group.actions !== undefined && isHovered) { - const actionBoxes = getActionBoundsForGroup({ x, y, width: w, height: h }, group.actions); - - ctx.beginPath(); - const fadeStartX = actionBoxes[0].x - 10; - const fadeWidth = x + w - fadeStartX; - ctx.rect(fadeStartX, 0, fadeWidth, groupHeaderHeight); - const grad = ctx.createLinearGradient(fadeStartX, 0, fadeStartX + fadeWidth, 0); - const trans = withAlpha(fillColor, 0); - grad.addColorStop(0, trans); - grad.addColorStop(10 / fadeWidth, fillColor); - grad.addColorStop(1, fillColor); - ctx.fillStyle = grad; - + walkGroups( + effectiveCols, + width, + translateX, + groupHeaderHeight, + freezeTrailingColumns, + (span, groupName, x, y, w, h) => { + if ( + damage !== undefined && + !damage.hasItemInRectangle({ + x: span[0], + y: -2, + width: span[1] - span[0] + 1, + height: 1, + }) + ) + return; + ctx.save(); + ctx.beginPath(); + ctx.rect(x, y, w, h); + ctx.clip(); + + const group = getGroupDetails(groupName); + const groupTheme = + group?.overrideTheme === undefined ? theme : mergeAndRealizeTheme(theme, group.overrideTheme); + const isHovered = hRow === -2 && hCol !== undefined && hCol >= span[0] && hCol <= span[1]; + const fillColor = isHovered + ? (groupTheme.bgGroupHeaderHovered ?? groupTheme.bgHeaderHovered) + : (groupTheme.bgGroupHeader ?? groupTheme.bgHeader); + + if (fillColor !== theme.bgHeader) { + ctx.fillStyle = fillColor; ctx.fill(); + } - ctx.globalAlpha = 0.6; - - // eslint-disable-next-line prefer-const - const [mouseX, mouseY] = hovered?.[1] ?? [-1, -1]; - for (let i = 0; i < group.actions.length; i++) { - const action = group.actions[i]; - const box = actionBoxes[i]; - const actionHovered = pointInRect(box, mouseX + x, mouseY); - if (actionHovered) { - ctx.globalAlpha = 1; - } + ctx.fillStyle = groupTheme.textGroupHeader ?? groupTheme.textHeader; + if (group !== undefined) { + let drawX = x; + if (group.icon !== undefined) { spriteManager.drawSprite( - action.icon, + group.icon, "normal", ctx, - box.x + box.width / 2 - 10, - box.y + box.height / 2 - 10, + drawX + xPad, + (groupHeaderHeight - 20) / 2, 20, groupTheme ); - if (actionHovered) { - ctx.globalAlpha = 0.6; - } + drawX += 26; } + ctx.fillText( + group.name, + drawX + xPad, + groupHeaderHeight / 2 + getMiddleCenterBias(ctx, theme.headerFontFull) + ); + + if (group.actions !== undefined && isHovered) { + const actionBoxes = getActionBoundsForGroup({ x, y, width: w, height: h }, group.actions); + + ctx.beginPath(); + const fadeStartX = actionBoxes[0].x - 10; + const fadeWidth = x + w - fadeStartX; + ctx.rect(fadeStartX, 0, fadeWidth, groupHeaderHeight); + const grad = ctx.createLinearGradient(fadeStartX, 0, fadeStartX + fadeWidth, 0); + const trans = withAlpha(fillColor, 0); + grad.addColorStop(0, trans); + grad.addColorStop(10 / fadeWidth, fillColor); + grad.addColorStop(1, fillColor); + ctx.fillStyle = grad; + + ctx.fill(); + + ctx.globalAlpha = 0.6; + + // eslint-disable-next-line prefer-const + const [mouseX, mouseY] = hovered?.[1] ?? [-1, -1]; + for (let i = 0; i < group.actions.length; i++) { + const action = group.actions[i]; + const box = actionBoxes[i]; + const actionHovered = pointInRect(box, mouseX + x, mouseY); + if (actionHovered) { + ctx.globalAlpha = 1; + } + spriteManager.drawSprite( + action.icon, + "normal", + ctx, + box.x + box.width / 2 - 10, + box.y + box.height / 2 - 10, + 20, + groupTheme + ); + if (actionHovered) { + ctx.globalAlpha = 0.6; + } + } - ctx.globalAlpha = 1; + ctx.globalAlpha = 1; + } } - } - if (x !== 0 && verticalBorder(span[0])) { - ctx.beginPath(); - ctx.moveTo(x + 0.5, 0); - ctx.lineTo(x + 0.5, groupHeaderHeight); - ctx.strokeStyle = theme.borderColor; - ctx.lineWidth = 1; - ctx.stroke(); - } + if (x !== 0 && verticalBorder(span[0])) { + ctx.beginPath(); + ctx.moveTo(x + 0.5, 0); + ctx.lineTo(x + 0.5, groupHeaderHeight); + ctx.strokeStyle = theme.borderColor; + ctx.lineWidth = 1; + ctx.stroke(); + } - ctx.restore(); + ctx.restore(); - finalX = x + w; - }); + finalX = x + w; + } + ); ctx.beginPath(); ctx.moveTo(finalX + 0.5, 0); diff --git a/packages/core/src/internal/data-grid/render/data-grid-render.lines.ts b/packages/core/src/internal/data-grid/render/data-grid-render.lines.ts index a4e0fb8aa..d142cd023 100644 --- a/packages/core/src/internal/data-grid/render/data-grid-render.lines.ts +++ b/packages/core/src/internal/data-grid/render/data-grid-render.lines.ts @@ -48,7 +48,7 @@ export function drawBlanks( translateY, totalHeaderHeight, freezeTrailingColumns, - (c, drawX, colDrawY, clipX, startRow) => { + (c, drawX, colDrawY, clipX, _clipXRight, startRow) => { if (c !== effectiveColumns[effectiveColumns.length - 1]) return; drawX += c.width; const x = Math.max(drawX, clipX); @@ -331,7 +331,7 @@ export function drawGridLines( for (let index = 0; index < effectiveCols.length; index++) { const c = effectiveCols[index]; if (c.width === 0) continue; - if (c.sticky && c.stickyPosition !== "left") break; + if (effectiveCols[index + 1]?.sticky && effectiveCols[index + 1].stickyPosition !== "left") break; x += c.width; const tx = c.sticky ? x : x + translateX; if (tx >= minX && tx <= maxX && verticalBorder(index + 1)) { diff --git a/packages/core/src/internal/data-grid/render/data-grid-render.ts b/packages/core/src/internal/data-grid/render/data-grid-render.ts index 752b62a4e..11e286645 100644 --- a/packages/core/src/internal/data-grid/render/data-grid-render.ts +++ b/packages/core/src/internal/data-grid/render/data-grid-render.ts @@ -40,17 +40,24 @@ function clipHeaderDamage( ctx.beginPath(); - walkGroups(effectiveColumns, width, translateX, groupHeaderHeight, (span, _group, x, y, w, h) => { - const hasItemInSpan = damage.hasItemInRectangle({ - x: span[0], - y: -2, - width: span[1] - span[0] + 1, - height: 1, - }); - if (hasItemInSpan) { - ctx.rect(x, y, w, h); + walkGroups( + effectiveColumns, + width, + translateX, + groupHeaderHeight, + freezeTrailingColumns, + (span, _group, x, y, w, h) => { + const hasItemInSpan = damage.hasItemInRectangle({ + x: span[0], + y: -2, + width: span[1] - span[0] + 1, + height: 1, + }); + if (hasItemInSpan) { + ctx.rect(x, y, w, h); + } } - }); + ); walkColumns( effectiveColumns, @@ -60,11 +67,11 @@ function clipHeaderDamage( translateY, totalHeaderHeight, freezeTrailingColumns, - (c, drawX, _colDrawY, clipX) => { + (c, drawX, _colDrawY, clipX, clipXRight) => { const diff = Math.max(0, clipX - drawX); const finalX = drawX + diff + 1; - const finalWidth = c.width - diff - 1; + const finalWidth = c.stickyPosition === "right" ? c.width - diff : Math.min(c.width - diff - 1, width - drawX - clipXRight); // c.width - diff - 1; if (damage.has([c.sourceIndex, -1])) { ctx.rect(finalX, groupHeaderHeight, finalWidth, totalHeaderHeight - groupHeaderHeight); } @@ -96,7 +103,7 @@ function getLastRow( translateY, totalHeaderHeight, freezeTrailingColumns, - (_c, __drawX, colDrawY, _clipX, startRow) => { + (_c, __drawX, colDrawY, _clipX, _clipXRight, startRow) => { walkRowsInCol( startRow, colDrawY, @@ -624,25 +631,25 @@ export function drawGrid(arg: DrawGridArg, lastArg: DrawGridArg | undefined) { // the overdraw may have nuked out our focus ring right edge. const focusRedraw = drawFocus ? drawFillHandle( - targetCtx, - width, - height, - cellYOffset, - translateX, - translateY, - effectiveCols, - mappedColumns, - theme, - totalHeaderHeight, - selection, - getRowHeight, - getCellContent, - freezeTrailingRows, - freezeRightColumns, - hasAppendRow, - fillHandle, - rows - ) + targetCtx, + width, + height, + cellYOffset, + translateX, + translateY, + effectiveCols, + mappedColumns, + theme, + totalHeaderHeight, + selection, + getRowHeight, + getCellContent, + freezeTrailingRows, + freezeRightColumns, + hasAppendRow, + fillHandle, + rows + ) : undefined; targetCtx.fillStyle = theme.bgCell; diff --git a/packages/core/src/internal/data-grid/render/data-grid-render.walk.ts b/packages/core/src/internal/data-grid/render/data-grid-render.walk.ts index e62721d67..d9c3dd3f6 100644 --- a/packages/core/src/internal/data-grid/render/data-grid-render.walk.ts +++ b/packages/core/src/internal/data-grid/render/data-grid-render.walk.ts @@ -59,6 +59,7 @@ export type WalkColsCallback = ( drawX: number, drawY: number, clipX: number, + clipXRight: number, startRow: number ) => boolean | void; @@ -75,11 +76,12 @@ export function walkColumns( let x = 0; let clipX = 0; // this tracks the total width of sticky cols const drawY = totalHeaderHeight + translateY; + const clipXRight = freezeTrailingColumns === 0 ? 0 : effectiveCols.slice(-freezeTrailingColumns).reduce((acc, col) => acc + col.width, 0); for (let i = 0; i < effectiveCols.length - freezeTrailingColumns; i++) { const c = effectiveCols[i]; const drawX = c.sticky ? clipX : x + translateX; - if (cb(c, drawX, drawY, c.sticky ? 0 : clipX, cellYOffset) === true) { + if (cb(c, drawX, drawY, c.sticky ? 0 : clipX, clipXRight, cellYOffset) === true) { break; } @@ -93,7 +95,7 @@ export function walkColumns( const drawX = x - c.width; x -= c.width; - cb(c, drawX, drawY, clipX, cellYOffset); + cb(c, drawX, drawY, clipX, clipXRight, cellYOffset); } } @@ -112,11 +114,17 @@ export function walkGroups( width: number, translateX: number, groupHeaderHeight: number, + freezeTrailingColumns: number, cb: WalkGroupsCallback ): void { let x = 0; let clipX = 0; - for (let index = 0; index < effectiveCols.length; index++) { + + const effectiveColsRight = freezeTrailingColumns === 0 ? [] : effectiveCols.slice(-freezeTrailingColumns); + const widthRight = effectiveColsRight.reduce((acc, col) => acc + col.width, 0); + width -= widthRight; + + for (let index = 0; index < effectiveCols.length - freezeTrailingColumns; index++) { const startCol = effectiveCols[index]; let end = index + 1; @@ -153,6 +161,42 @@ export function walkGroups( x += boxWidth; } + + for (let index = 0; index < effectiveColsRight.length; index++) { + const startCol = effectiveColsRight[index]; + + let end = index + 1; + let boxWidth = startCol.width; + + while ( + end < effectiveColsRight.length && + isGroupEqual(effectiveColsRight[end].group, startCol.group) && + effectiveColsRight[end].sticky === effectiveColsRight[index].sticky + ) { + const endCol = effectiveColsRight[end]; + boxWidth += endCol.width; + end++; + index++; + if (endCol.sticky) { + clipX += endCol.width; + } + } + + const t = width + boxWidth; + const localX = t - boxWidth; + const delta = 0; + const w = boxWidth - delta; + cb( + [startCol.sourceIndex, effectiveColsRight[end - 1].sourceIndex], + startCol.group ?? "", + localX + delta, + 0, + w, + groupHeaderHeight + ); + + width += w; + } } export function getSpanBounds( diff --git a/packages/core/src/internal/data-grid/render/data-grid.render.rings.ts b/packages/core/src/internal/data-grid/render/data-grid.render.rings.ts index 12715b54f..b6144845f 100644 --- a/packages/core/src/internal/data-grid/render/data-grid.render.rings.ts +++ b/packages/core/src/internal/data-grid/render/data-grid.render.rings.ts @@ -69,22 +69,22 @@ export function drawHighlightRings( rect.width === 1 && rect.height === 1 ? topLeftBounds : computeBounds( - rect.x + rect.width - 1, - rect.y + rect.height - 1, - width, - height, - groupHeaderHeight, - headerHeight + groupHeaderHeight, - cellXOffset, - cellYOffset, - translateX, - translateY, - rows, - freezeColumns, - freezeTrailingRows, - mappedColumns, - rowHeight - ); + rect.x + rect.width - 1, + rect.y + rect.height - 1, + width, + height, + groupHeaderHeight, + headerHeight + groupHeaderHeight, + cellXOffset, + cellYOffset, + translateX, + translateY, + rows, + freezeColumns, + freezeTrailingRows, + mappedColumns, + rowHeight + ); if (rect.x + rect.width >= mappedColumns.length) { bottomRightBounds.width -= 1; @@ -232,8 +232,7 @@ export function drawFillHandle( translateY, totalHeaderHeight, freezeTrailingColumns, - (col, drawX, colDrawY, clipX, startRow) => { - clipX; + (col, drawX, colDrawY, clipX, _clipXRight, startRow) => { if (col.sticky && targetCol > col.sourceIndex) return; const isBeforeTarget = col.sourceIndex < targetColSpan[0]; diff --git a/packages/core/test/data-editor.test.tsx b/packages/core/test/data-editor.test.tsx index a9b0a0507..b4ec1e954 100644 --- a/packages/core/test/data-editor.test.tsx +++ b/packages/core/test/data-editor.test.tsx @@ -2230,6 +2230,53 @@ describe("data-editor", () => { ); }); + + test("Freeze area reported with right freeze included", async () => { + const spy = vi.fn(); + vi.useFakeTimers(); + render( + , + { + wrapper: Context, + } + ); + prep(); + + expect(spy).toBeCalledWith( + expect.objectContaining({ + height: 32, + width: 9, + x: 2, + y: 0, + }), + 0, + 0, + expect.objectContaining({ + freezeRegion: { + height: 32, + width: 2, + x: 0, + y: 0, + }, + freezeRegions: [ + { + height: 32, + width: 2, + x: 0, + y: 0, + }, + { + height: 32, + width: 2, + x: 9, + y: 0, + }, + ], + selected: undefined, + }) + ); + }); + test("Search close", async () => { const spy = vi.fn(); vi.useFakeTimers(); diff --git a/packages/core/test/data-grid.test.tsx b/packages/core/test/data-grid.test.tsx index 33f3c86c2..4f19fa648 100644 --- a/packages/core/test/data-grid.test.tsx +++ b/packages/core/test/data-grid.test.tsx @@ -395,4 +395,71 @@ describe("data-grid", () => { false ); }); + + test("Freeze column simple check with trailing", () => { + const spy = vi.fn(); + + const basicPropsWithMoreColumns = { + ...basicProps, + columns: [ + ...basicProps.columns, + { + title: "F", + width: 150, + }, + { + title: "G", + width: 150, + }, + { + title: "H", + width: 150, + }, + { + title: "I", + width: 150, + }, + { + title: "J", + width: 150, + }, + { + title: "K", + width: 150, + }, + { + title: "L", + width: 150, + }, + ], + }; + + render(); + + + fireEvent.mouseDown(screen.getByTestId(dataGridCanvasId), { + clientX: 950, // Col A + clientY: 36 + 32 * 5 + 16, // Row 5 (0 indexed) + }); + + fireEvent.mouseUp(screen.getByTestId(dataGridCanvasId), { + clientX: 950, // Col A + clientY: 36 + 32 * 5 + 16, // Row 5 (0 indexed) + }); + + fireEvent.click(screen.getByTestId(dataGridCanvasId), { + clientX: 950, // Col A + clientY: 36 + 32 * 5 + 16, // Row 5 (0 indexed) + }); + + expect(spy).toHaveBeenCalledWith( + expect.objectContaining({ + location: [11, 5], + kind: "cell", + localEventX: 100, + localEventY: 16, + }), + false + ); + }); }); diff --git a/packages/source/src/use-collapsing-groups.ts b/packages/source/src/use-collapsing-groups.ts index 3b748a506..1b3df6d96 100644 --- a/packages/source/src/use-collapsing-groups.ts +++ b/packages/source/src/use-collapsing-groups.ts @@ -25,13 +25,16 @@ export function useCollapsingGroups(props: Props): Result { theme, } = props; + const freezeColumnsLeft = typeof freezeColumns === "number" ? freezeColumns : freezeColumns[0]; + const freezeColumnsRight = typeof freezeColumns === "number" ? 0 : freezeColumns[1]; + const gridSelection = gridSelectionIn ?? gridSelectionInner; const spans = React.useMemo(() => { const result: [number, number][] = []; let current: [number, number] = [-1, -1]; let lastGroup: string | undefined; - for (let i = freezeColumns; i < columnsIn.length; i++) { + for (let i = freezeColumnsLeft as number; i < columnsIn.length - freezeColumnsRight; i++) { const c = columnsIn[i]; const group = c.group ?? ""; const isCollapsed = collapsed.includes(group); @@ -53,7 +56,7 @@ export function useCollapsingGroups(props: Props): Result { } if (current[0] !== -1) result.push(current); return result; - }, [collapsed, columnsIn, freezeColumns]); + }, [collapsed, columnsIn, freezeColumnsLeft, freezeColumnsRight]); const columns = React.useMemo(() => { if (spans.length === 0) return columnsIn; @@ -118,8 +121,8 @@ export function useCollapsingGroups(props: Props): Result { name: group, overrideTheme: collapsed.includes(group ?? "") ? { - bgHeader: theme.bgHeaderHasFocus, - } + bgHeader: theme.bgHeaderHasFocus, + } : undefined, }; }, From 5ad6d899d6686abe7abaa79442b4b7680dc81da9 Mon Sep 17 00:00:00 2001 From: Mihran Margaryan Date: Thu, 3 Jul 2025 18:09:39 +0400 Subject: [PATCH 06/11] added an inline comment explaining freezeColumnsLeft and freezeColumnsRight --- packages/core/src/internal/data-grid/render/data-grid-lib.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/core/src/internal/data-grid/render/data-grid-lib.ts b/packages/core/src/internal/data-grid/render/data-grid-lib.ts index eef07c677..ebf4e063c 100644 --- a/packages/core/src/internal/data-grid/render/data-grid-lib.ts +++ b/packages/core/src/internal/data-grid/render/data-grid-lib.ts @@ -24,6 +24,8 @@ export function useMappedColumns( columns: readonly InnerGridColumn[], freezeColumns: number | [left: number, right: number] ): readonly MappedGridColumn[] { + // Extract freeze column counts from the union type parameter. freezeColumnsLeft and freezeColumnsRight + // determine which columns should remain sticky at the left and right sides respectively during horizontal scrolling. const freezeColumnsLeft = typeof freezeColumns === "number" ? freezeColumns : freezeColumns[0]; const freezeColumnsRight = typeof freezeColumns === "number" ? 0 : freezeColumns[1]; From 5d28364d81849cb5dd96ff545103fa547d095f89 Mon Sep 17 00:00:00 2001 From: Mihran Margaryan Date: Thu, 3 Jul 2025 18:11:04 +0400 Subject: [PATCH 07/11] added readonly flag to freezeColumns tuple prop --- .../core/src/internal/data-grid/data-grid.tsx | 46 +++++++++---------- .../image-window-loader-interface.ts | 6 ++- .../data-grid/render/data-grid-lib.ts | 8 ++-- .../render/data-grid.render.rings.ts | 34 +++++++------- .../data-grid/render/draw-grid-arg.ts | 2 +- 5 files changed, 50 insertions(+), 46 deletions(-) diff --git a/packages/core/src/internal/data-grid/data-grid.tsx b/packages/core/src/internal/data-grid/data-grid.tsx index 6524fa537..fb4e8c548 100644 --- a/packages/core/src/internal/data-grid/data-grid.tsx +++ b/packages/core/src/internal/data-grid/data-grid.tsx @@ -70,7 +70,7 @@ export interface DataGridProps { readonly accessibilityHeight: number; - readonly freezeColumns: number | [left: number, right: number]; + readonly freezeColumns: number | readonly [left: number, right: number]; readonly freezeTrailingRows: number; readonly hasAppendRow: boolean; readonly firstColAccessible: boolean; @@ -973,15 +973,15 @@ const DataGrid: React.ForwardRefRenderFunction = (p, const cursor = isDragging ? "grabbing" : canDrag || isResizing - ? "col-resize" - : overFill || isFilling - ? "crosshair" - : cursorOverride !== undefined - ? cursorOverride - : headerHovered || clickableInnerCellHovered || editableBoolHovered || groupHeaderHovered - ? "pointer" - : "default"; - + ? "col-resize" + : overFill || isFilling + ? "crosshair" + : cursorOverride !== undefined + ? cursorOverride + : headerHovered || clickableInnerCellHovered || editableBoolHovered || groupHeaderHovered + ? "pointer" + : "default"; + const style = React.useMemo( () => ({ // width, @@ -1898,8 +1898,8 @@ const DataGrid: React.ForwardRefRenderFunction = (p, freezeLeftColumns === 0 || !fixedShadowX ? 0 : cellXOffset > freezeLeftColumns - ? 1 - : clamp(-translateX / 100, 0, 1); + ? 1 + : clamp(-translateX / 100, 0, 1); let translateXRight = 0; @@ -1911,17 +1911,17 @@ const DataGrid: React.ForwardRefRenderFunction = (p, freezeRightColumns === 0 || !fixedShadowX ? 0 : cellXOffset + - getEffectiveColumns( - mappedColumns, - cellXOffset, - width, - freezeColumns, - dragAndDropState, - translateX - ).filter(column => !column.sticky).length < - columns.length - freezeRightColumns - ? 1 - : clamp(-translateXRight / 100, 0, 1); + getEffectiveColumns( + mappedColumns, + cellXOffset, + width, + freezeColumns, + dragAndDropState, + translateX + ).filter(column => !column.sticky).length < + columns.length - freezeRightColumns + ? 1 + : clamp(-translateXRight / 100, 0, 1); const absoluteOffsetY = -cellYOffset * 32 + translateY; const opacityY = !fixedShadowY ? 0 : clamp(-absoluteOffsetY / 100, 0, 1); diff --git a/packages/core/src/internal/data-grid/image-window-loader-interface.ts b/packages/core/src/internal/data-grid/image-window-loader-interface.ts index 6495a1ca3..a06c1033f 100644 --- a/packages/core/src/internal/data-grid/image-window-loader-interface.ts +++ b/packages/core/src/internal/data-grid/image-window-loader-interface.ts @@ -3,7 +3,11 @@ import type { Rectangle } from "./data-grid-types.js"; /** @category Types */ export interface ImageWindowLoader { - setWindow(newWindow: Rectangle, freezeCols: number | [left: number, right: number], freezeRows: number[]): void; + setWindow( + newWindow: Rectangle, + freezeCols: number | readonly [left: number, right: number], + freezeRows: number[] + ): void; loadOrGetImage(url: string, col: number, row: number): HTMLImageElement | ImageBitmap | undefined; setCallback(imageLoaded: (locations: CellSet) => void): void; } diff --git a/packages/core/src/internal/data-grid/render/data-grid-lib.ts b/packages/core/src/internal/data-grid/render/data-grid-lib.ts index ebf4e063c..bf10cf4dd 100644 --- a/packages/core/src/internal/data-grid/render/data-grid-lib.ts +++ b/packages/core/src/internal/data-grid/render/data-grid-lib.ts @@ -22,7 +22,7 @@ export interface MappedGridColumn extends FullyDefined { export function useMappedColumns( columns: readonly InnerGridColumn[], - freezeColumns: number | [left: number, right: number] + freezeColumns: number | readonly [left: number, right: number] ): readonly MappedGridColumn[] { // Extract freeze column counts from the union type parameter. freezeColumnsLeft and freezeColumnsRight // determine which columns should remain sticky at the left and right sides respectively during horizontal scrolling. @@ -223,7 +223,7 @@ export function getEffectiveColumns( columns: readonly MappedGridColumn[], cellXOffset: number, width: number, - freezeColumns: number | [left: number, right: number], + freezeColumns: number | readonly [left: number, right: number], dndState?: { src: number; dest: number; @@ -281,7 +281,7 @@ export function getEffectiveColumns( export function getColumnIndexForX( targetX: number, effectiveColumns: readonly MappedGridColumn[], - freezeColumns: number | [left: number, right: number], + freezeColumns: number | readonly [left: number, right: number], width: number, translateX?: number ): number { @@ -817,7 +817,7 @@ export function computeBounds( translateX: number, translateY: number, rows: number, - freezeColumns: number | [left: number, right: number], + freezeColumns: number | readonly [left: number, right: number], freezeTrailingRows: number, mappedColumns: readonly MappedGridColumn[], rowHeight: number | ((index: number) => number) diff --git a/packages/core/src/internal/data-grid/render/data-grid.render.rings.ts b/packages/core/src/internal/data-grid/render/data-grid.render.rings.ts index b6144845f..2203965a9 100644 --- a/packages/core/src/internal/data-grid/render/data-grid.render.rings.ts +++ b/packages/core/src/internal/data-grid/render/data-grid.render.rings.ts @@ -17,7 +17,7 @@ export function drawHighlightRings( translateX: number, translateY: number, mappedColumns: readonly MappedGridColumn[], - freezeColumns: number | [left: number, right: number], + freezeColumns: number | readonly [left: number, right: number], headerHeight: number, groupHeaderHeight: number, rowHeight: number | ((index: number) => number), @@ -69,22 +69,22 @@ export function drawHighlightRings( rect.width === 1 && rect.height === 1 ? topLeftBounds : computeBounds( - rect.x + rect.width - 1, - rect.y + rect.height - 1, - width, - height, - groupHeaderHeight, - headerHeight + groupHeaderHeight, - cellXOffset, - cellYOffset, - translateX, - translateY, - rows, - freezeColumns, - freezeTrailingRows, - mappedColumns, - rowHeight - ); + rect.x + rect.width - 1, + rect.y + rect.height - 1, + width, + height, + groupHeaderHeight, + headerHeight + groupHeaderHeight, + cellXOffset, + cellYOffset, + translateX, + translateY, + rows, + freezeColumns, + freezeTrailingRows, + mappedColumns, + rowHeight + ); if (rect.x + rect.width >= mappedColumns.length) { bottomRightBounds.width -= 1; diff --git a/packages/core/src/internal/data-grid/render/draw-grid-arg.ts b/packages/core/src/internal/data-grid/render/draw-grid-arg.ts index fbb45eeb2..8c848ee8f 100644 --- a/packages/core/src/internal/data-grid/render/draw-grid-arg.ts +++ b/packages/core/src/internal/data-grid/render/draw-grid-arg.ts @@ -39,7 +39,7 @@ export interface DrawGridArg { readonly translateY: number; readonly mappedColumns: readonly MappedGridColumn[]; readonly enableGroups: boolean; - readonly freezeColumns: number | [left: number, right: number]; + readonly freezeColumns: number | readonly [left: number, right: number]; readonly dragAndDropState: DragAndDropState | undefined; readonly theme: FullTheme; readonly headerHeight: number; From 1026249670be8b0a515f4b967db3281e73e04b72 Mon Sep 17 00:00:00 2001 From: Mihran Margaryan Date: Thu, 3 Jul 2025 18:12:54 +0400 Subject: [PATCH 08/11] fix scrollTo issue not finding a column --- packages/core/src/data-editor/data-editor.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/core/src/data-editor/data-editor.tsx b/packages/core/src/data-editor/data-editor.tsx index 0fb36f8c0..23a8020d7 100644 --- a/packages/core/src/data-editor/data-editor.tsx +++ b/packages/core/src/data-editor/data-editor.tsx @@ -1321,7 +1321,7 @@ const DataEditorImpl: React.ForwardRefRenderFunction= mangledCols.length - freezeRightColumns; i--) { + for (let i = columns.length - 1; i >= columns.length - 1 - freezeRightColumns; i--) { frozenRightWidth += columns[i].width; } let trailingRowHeight = 0; @@ -3579,7 +3579,7 @@ const DataEditorImpl: React.ForwardRefRenderFunction { return typeof verticalBorder === "boolean" ? verticalBorder - : verticalBorder?.(col - rowMarkerOffset) ?? true; + : (verticalBorder?.(col - rowMarkerOffset) ?? true); }, [rowMarkerOffset, verticalBorder] ); From fd360fb49da52afd3c4f8ee8611a7d519c237334 Mon Sep 17 00:00:00 2001 From: Mihran Margaryan Date: Tue, 8 Jul 2025 16:39:02 +0400 Subject: [PATCH 09/11] fix image loader doesn't take into account freezeColumns new api --- .../core/src/common/render-state-provider.ts | 15 ++++-- .../image-window-loader-interface.ts | 3 +- .../data-grid/render/data-grid-render.ts | 46 ++++++++++--------- .../core/test/image-window-loader.test.ts | 27 ++++++++--- .../core/test/render-state-provider.test.ts | 2 +- 5 files changed, 61 insertions(+), 32 deletions(-) diff --git a/packages/core/src/common/render-state-provider.ts b/packages/core/src/common/render-state-provider.ts index 55e54b4f1..3366e34a6 100644 --- a/packages/core/src/common/render-state-provider.ts +++ b/packages/core/src/common/render-state-provider.ts @@ -35,33 +35,42 @@ export abstract class WindowingTrackerBase { height: 0, }; - public freezeCols: number = 0; + public columnsLength: number = 0; + public freezeCols: number | readonly [number, number] = 0; public freezeRows: number[] = []; protected isInWindow = (packed: number) => { + const freezeColumnsLeft = typeof this.freezeCols === "number" ? this.freezeCols : this.freezeCols[0]; + const freezeColumnsRight = typeof this.freezeCols === "number" ? 0 : this.freezeCols[1]; const col = unpackCol(packed); const row = unpackRow(packed); const w = this.visibleWindow; - const colInWindow = (col >= w.x && col <= w.x + w.width) || col < this.freezeCols; + const colInWindow = + (col >= w.x && col <= w.x + w.width) || + col < freezeColumnsLeft || + col > this.columnsLength - freezeColumnsRight - 1; + const rowInWindow = (row >= w.y && row <= w.y + w.height) || this.freezeRows.includes(row); return colInWindow && rowInWindow; }; protected abstract clearOutOfWindow: () => void; - public setWindow(newWindow: Rectangle, freezeCols: number, freezeRows: number[]): void { + public setWindow(newWindow: Rectangle, freezeCols: number, freezeRows: number[], columnsLength: number): void { if ( this.visibleWindow.x === newWindow.x && this.visibleWindow.y === newWindow.y && this.visibleWindow.width === newWindow.width && this.visibleWindow.height === newWindow.height && this.freezeCols === freezeCols && + this.columnsLength === columnsLength && deepEqual(this.freezeRows, freezeRows) ) return; this.visibleWindow = newWindow; this.freezeCols = freezeCols; this.freezeRows = freezeRows; + this.columnsLength = columnsLength; this.clearOutOfWindow(); } } diff --git a/packages/core/src/internal/data-grid/image-window-loader-interface.ts b/packages/core/src/internal/data-grid/image-window-loader-interface.ts index a06c1033f..7cadb0fca 100644 --- a/packages/core/src/internal/data-grid/image-window-loader-interface.ts +++ b/packages/core/src/internal/data-grid/image-window-loader-interface.ts @@ -6,7 +6,8 @@ export interface ImageWindowLoader { setWindow( newWindow: Rectangle, freezeCols: number | readonly [left: number, right: number], - freezeRows: number[] + freezeRows: number[], + columnsLength: number ): void; loadOrGetImage(url: string, col: number, row: number): HTMLImageElement | ImageBitmap | undefined; setCallback(imageLoaded: (locations: CellSet) => void): void; diff --git a/packages/core/src/internal/data-grid/render/data-grid-render.ts b/packages/core/src/internal/data-grid/render/data-grid-render.ts index 11e286645..51acc4c9c 100644 --- a/packages/core/src/internal/data-grid/render/data-grid-render.ts +++ b/packages/core/src/internal/data-grid/render/data-grid-render.ts @@ -71,7 +71,10 @@ function clipHeaderDamage( const diff = Math.max(0, clipX - drawX); const finalX = drawX + diff + 1; - const finalWidth = c.stickyPosition === "right" ? c.width - diff : Math.min(c.width - diff - 1, width - drawX - clipXRight); // c.width - diff - 1; + const finalWidth = + c.stickyPosition === "right" + ? c.width - diff + : Math.min(c.width - diff - 1, width - drawX - clipXRight); if (damage.has([c.sourceIndex, -1])) { ctx.rect(finalX, groupHeaderHeight, finalWidth, totalHeaderHeight - groupHeaderHeight); } @@ -631,25 +634,25 @@ export function drawGrid(arg: DrawGridArg, lastArg: DrawGridArg | undefined) { // the overdraw may have nuked out our focus ring right edge. const focusRedraw = drawFocus ? drawFillHandle( - targetCtx, - width, - height, - cellYOffset, - translateX, - translateY, - effectiveCols, - mappedColumns, - theme, - totalHeaderHeight, - selection, - getRowHeight, - getCellContent, - freezeTrailingRows, - freezeRightColumns, - hasAppendRow, - fillHandle, - rows - ) + targetCtx, + width, + height, + cellYOffset, + translateX, + translateY, + effectiveCols, + mappedColumns, + theme, + totalHeaderHeight, + selection, + getRowHeight, + getCellContent, + freezeTrailingRows, + freezeRightColumns, + hasAppendRow, + fillHandle, + rows + ) : undefined; targetCtx.fillStyle = theme.bgCell; @@ -823,7 +826,8 @@ export function drawGrid(arg: DrawGridArg, lastArg: DrawGridArg | undefined) { height: lastRowDrawn - cellYOffset, }, freezeColumns, - Array.from({ length: freezeTrailingRows }, (_, i) => rows - 1 - i) + Array.from({ length: freezeTrailingRows }, (_, i) => rows - 1 - i), + mappedColumns.length ); const scrollX = last !== undefined && (cellXOffset !== last.cellXOffset || translateX !== last.translateX); diff --git a/packages/core/test/image-window-loader.test.ts b/packages/core/test/image-window-loader.test.ts index 398d0fd33..2abd120ab 100644 --- a/packages/core/test/image-window-loader.test.ts +++ b/packages/core/test/image-window-loader.test.ts @@ -19,13 +19,28 @@ describe("ImageWindowLoaderImpl", () => { }; const freezeCols = 5; - loader.setWindow(newWindow, freezeCols, []); + loader.setWindow(newWindow, freezeCols, [], 10); // Assuming you modify your class to expose `visibleWindow` and `freezeCols` for testing expect(loader.visibleWindow).toEqual(newWindow); expect(loader.freezeCols).toBe(freezeCols); }); + it("should set the new columnsLength", () => { + const newWindow = { + x: 10, + y: 10, + width: 100, + height: 100, + }; + const freezeCols = 5; + const columnsLength = 10; + + loader.setWindow(newWindow, freezeCols, [], columnsLength); + + expect(loader.columnsLength).toBe(columnsLength); + }); + it("should call clearOutOfWindow() if the window or freezeCols changes", () => { const spyClearOutOfWindow = vi.spyOn(loader, "clearOutOfWindow" as any); // Private method, so using 'as any' @@ -44,13 +59,13 @@ describe("ImageWindowLoaderImpl", () => { const freezeCols1 = 5; const freezeCols2 = 10; - loader.setWindow(window1, freezeCols1, []); + loader.setWindow(window1, freezeCols1, [], 10); expect(spyClearOutOfWindow).toHaveBeenCalledTimes(1); - loader.setWindow(window2, freezeCols1, []); + loader.setWindow(window2, freezeCols1, [], 10); expect(spyClearOutOfWindow).toHaveBeenCalledTimes(2); - loader.setWindow(window2, freezeCols2, []); + loader.setWindow(window2, freezeCols2, [], 10); expect(spyClearOutOfWindow).toHaveBeenCalledTimes(3); // Cleanup @@ -68,8 +83,8 @@ describe("ImageWindowLoaderImpl", () => { }; const freezeCols = 5; - loader.setWindow(newWindow, freezeCols, []); - loader.setWindow(newWindow, freezeCols, []); + loader.setWindow(newWindow, freezeCols, [], 10); + loader.setWindow(newWindow, freezeCols, [], 10); expect(spyClearOutOfWindow).toHaveBeenCalledTimes(1); diff --git a/packages/core/test/render-state-provider.test.ts b/packages/core/test/render-state-provider.test.ts index 82e772bb1..634fa48ce 100644 --- a/packages/core/test/render-state-provider.test.ts +++ b/packages/core/test/render-state-provider.test.ts @@ -78,7 +78,7 @@ describe("Data Grid Utility Functions", () => { it("should update visible window and freeze columns correctly", () => { renderStateProvider.setValue([0, 30], "state"); renderStateProvider.setValue([1, 0], "state"); - renderStateProvider.setWindow(testRectangle, 1, []); + renderStateProvider.setWindow(testRectangle, 1, [], 10); expect(renderStateProvider.getValue([0, 30])).to.equal("state"); expect(renderStateProvider.getValue([1, 0])).to.equal(undefined); }); From 387ad5184773196645dd15b3de40543a2a25be39 Mon Sep 17 00:00:00 2001 From: Mihran Margaryan Date: Tue, 15 Jul 2025 10:49:28 +0400 Subject: [PATCH 10/11] fix vertical lines calculation issue --- .../src/internal/data-grid/render/data-grid-render.lines.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/core/src/internal/data-grid/render/data-grid-render.lines.ts b/packages/core/src/internal/data-grid/render/data-grid-render.lines.ts index d142cd023..ccd598f3a 100644 --- a/packages/core/src/internal/data-grid/render/data-grid-render.lines.ts +++ b/packages/core/src/internal/data-grid/render/data-grid-render.lines.ts @@ -317,6 +317,8 @@ export function drawGridLines( } ctx.clip("evenodd"); } + + const effectiveWidth = effectiveCols.reduce((acc, col) => acc + col.width, 0); const hColor = theme.horizontalBorderColor ?? theme.borderColor; const vColor = theme.borderColor; @@ -345,6 +347,7 @@ export function drawGridLines( } } + width = Math.min(width, effectiveWidth); let rightX = width + 0.5; for (let index = effectiveCols.length - 1; index >= 0; index--) { const c = effectiveCols[index]; From 67a8c55f27d212094f84f88bac0841493fda0c90 Mon Sep 17 00:00:00 2001 From: Mihran Margaryan Date: Fri, 8 Aug 2025 12:18:27 +0400 Subject: [PATCH 11/11] add utility function to extract left and right freeze, minor fixes --- packages/core/src/common/utils.tsx | 7 +++++++ packages/core/src/data-editor/data-editor.tsx | 7 +++---- packages/core/src/internal/data-grid/data-grid.tsx | 11 ++++++++--- .../src/internal/data-grid/render/data-grid-lib.ts | 8 +++----- .../src/internal/data-grid/render/data-grid-render.ts | 4 ++-- .../data-grid/render/data-grid.render.rings.ts | 4 ++-- .../scrolling-data-grid/scrolling-data-grid.tsx | 3 ++- packages/source/src/use-collapsing-groups.ts | 6 +++--- 8 files changed, 30 insertions(+), 20 deletions(-) diff --git a/packages/core/src/common/utils.tsx b/packages/core/src/common/utils.tsx index aad1b77e2..5f4c026de 100644 --- a/packages/core/src/common/utils.tsx +++ b/packages/core/src/common/utils.tsx @@ -282,3 +282,10 @@ export function useDeepMemo(value: T): T { return ref.current; } + +export function normalizeFreezeColumns(freezeColumns: number | readonly [number, number]): readonly [number, number] { + const freezeLeftColumns = typeof freezeColumns === "number" ? freezeColumns : freezeColumns[0]; + const freezeRightColumns = typeof freezeColumns === "number" ? 0 : freezeColumns[1]; + + return [freezeLeftColumns, freezeRightColumns]; +} diff --git a/packages/core/src/data-editor/data-editor.tsx b/packages/core/src/data-editor/data-editor.tsx index 23a8020d7..60d4d7f10 100644 --- a/packages/core/src/data-editor/data-editor.tsx +++ b/packages/core/src/data-editor/data-editor.tsx @@ -44,7 +44,7 @@ import { mergeAndRealizeTheme, } from "../common/styles.js"; import type { DataGridRef } from "../internal/data-grid/data-grid.js"; -import { getScrollBarWidth, useEventListener, whenDefined } from "../common/utils.js"; +import { getScrollBarWidth, useEventListener, normalizeFreezeColumns, whenDefined } from "../common/utils.js"; import { isGroupEqual, itemsAreEqual, @@ -911,8 +911,7 @@ const DataEditorImpl: React.ForwardRefRenderFunction { if (typeof window === "undefined") return { fontSize: "16px" }; @@ -1567,7 +1566,7 @@ const DataEditorImpl: React.ForwardRefRenderFunction= columns.length - 1 - freezeRightColumns; i--) { + for (let i = columns.length - 1; i >= columns.length - freezeRightColumns; i--) { frozenRightWidth += columns[i].width; } let trailingRowHeight = 0; diff --git a/packages/core/src/internal/data-grid/data-grid.tsx b/packages/core/src/internal/data-grid/data-grid.tsx index fb4e8c548..58d00f4d7 100644 --- a/packages/core/src/internal/data-grid/data-grid.tsx +++ b/packages/core/src/internal/data-grid/data-grid.tsx @@ -26,7 +26,13 @@ import { } from "./data-grid-types.js"; import { CellSet } from "./cell-set.js"; import { SpriteManager, type SpriteMap } from "./data-grid-sprites.js"; -import { direction, getScrollBarWidth, useDebouncedMemo, useEventListener } from "../../common/utils.js"; +import { + direction, + getScrollBarWidth, + useDebouncedMemo, + useEventListener, + normalizeFreezeColumns, +} from "../../common/utils.js"; import clamp from "lodash/clamp.js"; import makeRange from "lodash/range.js"; import { drawGrid } from "./render/data-grid-render.js"; @@ -403,8 +409,7 @@ const DataGrid: React.ForwardRefRenderFunction = (p, } = p; const translateX = p.translateX ?? 0; const translateY = p.translateY ?? 0; - const freezeLeftColumns = typeof freezeColumns === "number" ? freezeColumns : freezeColumns[0]; - const freezeRightColumns = typeof freezeColumns === "number" ? 0 : freezeColumns[1]; + const [freezeLeftColumns, freezeRightColumns] = normalizeFreezeColumns(freezeColumns); const cellXOffset = Math.max(freezeLeftColumns, Math.min(columns.length - 1, cellXOffsetReal)); const ref = React.useRef(null); diff --git a/packages/core/src/internal/data-grid/render/data-grid-lib.ts b/packages/core/src/internal/data-grid/render/data-grid-lib.ts index bf10cf4dd..95497f955 100644 --- a/packages/core/src/internal/data-grid/render/data-grid-lib.ts +++ b/packages/core/src/internal/data-grid/render/data-grid-lib.ts @@ -8,7 +8,7 @@ import { type Rectangle, type BaseGridCell, } from "../data-grid-types.js"; -import { direction } from "../../../common/utils.js"; +import { direction, normalizeFreezeColumns } from "../../../common/utils.js"; import React from "react"; import type { BaseDrawArgs, PrepResult } from "../../../cells/cell-types.js"; import { split as splitText, clearCache } from "canvas-hypertxt"; @@ -232,8 +232,7 @@ export function getEffectiveColumns( ): readonly MappedGridColumn[] { const mappedCols = remapForDnDState(columns, dndState); - const freezeLeftColumns = typeof freezeColumns === "number" ? freezeColumns : freezeColumns[0]; - const freezeRightColumns = typeof freezeColumns === "number" ? 0 : freezeColumns[1]; + const [freezeLeftColumns, freezeRightColumns] = normalizeFreezeColumns(freezeColumns); const sticky: MappedGridColumn[] = []; for (let i = 0; i < freezeLeftColumns; i++) { @@ -829,8 +828,7 @@ export function computeBounds( height: 0, }; - const freezeLeftColumns = typeof freezeColumns === "number" ? freezeColumns : freezeColumns[0]; - const freezeRightColumns = typeof freezeColumns === "number" ? 0 : freezeColumns[1]; + const [freezeLeftColumns, freezeRightColumns] = normalizeFreezeColumns(freezeColumns); const column = mappedColumns[col]; if (col >= mappedColumns.length || row >= rows || row < -2 || col < 0) { diff --git a/packages/core/src/internal/data-grid/render/data-grid-render.ts b/packages/core/src/internal/data-grid/render/data-grid-render.ts index 51acc4c9c..5cf56bbc3 100644 --- a/packages/core/src/internal/data-grid/render/data-grid-render.ts +++ b/packages/core/src/internal/data-grid/render/data-grid-render.ts @@ -12,6 +12,7 @@ import { drawGridHeaders } from "./data-grid-render.header.js"; import { drawGridLines, overdrawStickyBoundaries, drawBlanks, drawExtraRowThemes } from "./data-grid-render.lines.js"; import { blitLastFrame, blitResizedCol, computeCanBlit } from "./data-grid-render.blit.js"; import { drawHighlightRings, drawFillHandle, drawColumnResizeOutline } from "./data-grid.render.rings.js"; +import { normalizeFreezeColumns } from "../../../common/utils.js"; // Future optimization opportunities // - Create a cache of a buffer used to render the full view of a partially displayed column so that when @@ -188,8 +189,7 @@ export function drawGrid(arg: DrawGridArg, lastArg: DrawGridArg | undefined) { const doubleBuffer = renderStrategy === "double-buffer"; const dpr = Math.min(maxScaleFactor, Math.ceil(window.devicePixelRatio ?? 1)); - const freezeLeftColumns = typeof freezeColumns === "number" ? freezeColumns : freezeColumns[0]; - const freezeRightColumns = typeof freezeColumns === "number" ? 0 : freezeColumns[1]; + const [freezeLeftColumns, freezeRightColumns] = normalizeFreezeColumns(freezeColumns); // if we are double buffering we need to make sure we can blit. If we can't we need to redraw the whole thing const canBlit = renderStrategy !== "direct" && computeCanBlit(arg, lastArg); diff --git a/packages/core/src/internal/data-grid/render/data-grid.render.rings.ts b/packages/core/src/internal/data-grid/render/data-grid.render.rings.ts index 2203965a9..49a7f80e0 100644 --- a/packages/core/src/internal/data-grid/render/data-grid.render.rings.ts +++ b/packages/core/src/internal/data-grid/render/data-grid.render.rings.ts @@ -7,6 +7,7 @@ import { blend, withAlpha } from "../color-parser.js"; import { hugRectToTarget, intersectRect, rectContains, splitRectIntoRegions } from "../../../common/math.js"; import { getSpanBounds, walkColumns, walkRowsInCol } from "./data-grid-render.walk.js"; import { type Highlight } from "./data-grid-render.cells.js"; +import { normalizeFreezeColumns } from "../../../common/utils.js"; export function drawHighlightRings( ctx: CanvasRenderingContext2D, @@ -27,8 +28,7 @@ export function drawHighlightRings( theme: FullTheme ): (() => void) | undefined { const highlightRegions = allHighlightRegions?.filter(x => x.style !== "no-outline"); - const freezeLeftColumns = typeof freezeColumns === "number" ? freezeColumns : freezeColumns[0]; - const freezeRightColumns = typeof freezeColumns === "number" ? 0 : freezeColumns[1]; + const [freezeLeftColumns, freezeRightColumns] = normalizeFreezeColumns(freezeColumns); if (highlightRegions === undefined || highlightRegions.length === 0) return undefined; diff --git a/packages/core/src/internal/scrolling-data-grid/scrolling-data-grid.tsx b/packages/core/src/internal/scrolling-data-grid/scrolling-data-grid.tsx index 63af6dfdf..2ba1bfb45 100644 --- a/packages/core/src/internal/scrolling-data-grid/scrolling-data-grid.tsx +++ b/packages/core/src/internal/scrolling-data-grid/scrolling-data-grid.tsx @@ -2,6 +2,7 @@ import * as React from "react"; import DataGridDnd, { type DataGridDndProps } from "../data-grid-dnd/data-grid-dnd.js"; import type { Rectangle } from "../data-grid/data-grid-types.js"; import { InfiniteScroller } from "./infinite-scroller.js"; +import { normalizeFreezeColumns } from "../../common/utils.js"; type Props = Omit; @@ -102,7 +103,7 @@ const GridScroller: React.FunctionComponent = p => { const lastY = React.useRef(); const lastSize = React.useRef(); - const freezeLeftColumns = typeof freezeColumns === "number" ? freezeColumns : freezeColumns[0]; + const [freezeLeftColumns] = normalizeFreezeColumns(freezeColumns); const width = nonGrowWidth + Math.max(0, overscrollX ?? 0); diff --git a/packages/source/src/use-collapsing-groups.ts b/packages/source/src/use-collapsing-groups.ts index 1b3df6d96..6a0a245ac 100644 --- a/packages/source/src/use-collapsing-groups.ts +++ b/packages/source/src/use-collapsing-groups.ts @@ -34,7 +34,7 @@ export function useCollapsingGroups(props: Props): Result { const result: [number, number][] = []; let current: [number, number] = [-1, -1]; let lastGroup: string | undefined; - for (let i = freezeColumnsLeft as number; i < columnsIn.length - freezeColumnsRight; i++) { + for (let i = freezeColumnsLeft; i < columnsIn.length - freezeColumnsRight; i++) { const c = columnsIn[i]; const group = c.group ?? ""; const isCollapsed = collapsed.includes(group); @@ -121,8 +121,8 @@ export function useCollapsingGroups(props: Props): Result { name: group, overrideTheme: collapsed.includes(group ?? "") ? { - bgHeader: theme.bgHeaderHasFocus, - } + bgHeader: theme.bgHeaderHasFocus, + } : undefined, }; },