Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement right frozen #3671

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/DataGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,9 @@ function DataGrid<R, SR, K extends Key>(
colOverscanEndIdx,
templateColumns,
layoutCssVars,
totalFrozenColumnWidth
totalFrozenColumnWidth,
rightFrozenColumnCount,
totalRightFrozenColumnWidth
} = useCalculatedColumns({
rawColumns,
defaultColumnOptions,
Expand Down Expand Up @@ -418,6 +420,7 @@ function DataGrid<R, SR, K extends Key>(
colOverscanStartIdx,
colOverscanEndIdx,
lastFrozenColumnIndex,
rightFrozenColumnCount,
rowOverscanStartIdx,
rowOverscanEndIdx,
rows,
Expand Down
55 changes: 45 additions & 10 deletions src/hooks/useCalculatedColumns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,15 @@ export function useCalculatedColumns<R, SR>({
const defaultResizable = defaultColumnOptions?.resizable ?? false;
const defaultDraggable = defaultColumnOptions?.draggable ?? false;

const { columns, colSpanColumns, lastFrozenColumnIndex, headerRowsCount } = useMemo((): {
const { columns, colSpanColumns, lastFrozenColumnIndex, rightFrozenColumnCount, headerRowsCount } = useMemo((): {
readonly columns: readonly CalculatedColumn<R, SR>[];
readonly colSpanColumns: readonly CalculatedColumn<R, SR>[];
readonly lastFrozenColumnIndex: number;
readonly rightFrozenColumnCount: number;
readonly headerRowsCount: number;
} => {
let lastFrozenColumnIndex = -1;
let rightFrozenColumnCount = 0;
let headerRowsCount = 1;
const columns: MutableCalculatedColumn<R, SR>[] = [];

Expand All @@ -85,13 +87,15 @@ export function useCalculatedColumns<R, SR>({
}

const frozen = rawColumn.frozen ?? false;
const rightFrozen = rawColumn.rightFrozen ?? false;

const column: MutableCalculatedColumn<R, SR> = {
...rawColumn,
parent,
idx: 0,
level: 0,
frozen,
rightFrozen,
width: rawColumn.width ?? defaultWidth,
minWidth: rawColumn.minWidth ?? defaultMinWidth,
maxWidth: rawColumn.maxWidth ?? defaultMaxWidth,
Expand All @@ -107,13 +111,17 @@ export function useCalculatedColumns<R, SR>({
lastFrozenColumnIndex++;
}

if(rightFrozen) {
rightFrozenColumnCount++;
}

if (level > headerRowsCount) {
headerRowsCount = level;
}
}
}

columns.sort(({ key: aKey, frozen: frozenA }, { key: bKey, frozen: frozenB }) => {
columns.sort(({ key: aKey, frozen: frozenA, rightFrozen: rightFrozenA }, { key: bKey, frozen: frozenB, rightFrozen: rightFrozenB }) => {
// Sort select column first:
if (aKey === SELECT_COLUMN_KEY) return -1;
if (bKey === SELECT_COLUMN_KEY) return 1;
Expand All @@ -125,6 +133,13 @@ export function useCalculatedColumns<R, SR>({
}
if (frozenB) return 1;

// Sort right frozen columns second:
if (rightFrozenA) {
if (rightFrozenB) return 0;
return 1;
}
if (rightFrozenB) return -1;

// TODO: sort columns to keep them grouped if they have a parent

// Sort other columns last:
Expand All @@ -145,6 +160,7 @@ export function useCalculatedColumns<R, SR>({
columns,
colSpanColumns,
lastFrozenColumnIndex,
rightFrozenColumnCount,
headerRowsCount
};
}, [
Expand All @@ -158,15 +174,17 @@ export function useCalculatedColumns<R, SR>({
defaultDraggable
]);

const { templateColumns, layoutCssVars, totalFrozenColumnWidth, columnMetrics } = useMemo((): {
const { templateColumns, layoutCssVars, totalFrozenColumnWidth, totalRightFrozenColumnWidth, columnMetrics } = useMemo((): {
templateColumns: readonly string[];
layoutCssVars: Readonly<Record<string, string>>;
totalFrozenColumnWidth: number;
totalRightFrozenColumnWidth: number;
columnMetrics: ReadonlyMap<CalculatedColumn<R, SR>, ColumnMetric>;
} => {
const columnMetrics = new Map<CalculatedColumn<R, SR>, ColumnMetric>();
let left = 0;
let totalFrozenColumnWidth = 0;
let totalRightFrozenColumnWidth = 0;
const templateColumns: string[] = [];

for (const column of columns) {
Expand All @@ -191,24 +209,38 @@ export function useCalculatedColumns<R, SR>({

const layoutCssVars: Record<string, string> = {};

if(rightFrozenColumnCount !== 0) {
let rightEnd = 0;
for(let i=columns.length-1;i>=columns.length-rightFrozenColumnCount;i--) {
const column = columns[i];
const columnMetric = columnMetrics.get(column)!;
totalRightFrozenColumnWidth+=columnMetric.width;
layoutCssVars[`--rdg-frozen-right-${column.idx}`] = `${rightEnd}px`;
rightEnd+=columnMetric.width;
}
}



for (let i = 0; i <= lastFrozenColumnIndex; i++) {
const column = columns[i];
layoutCssVars[`--rdg-frozen-left-${column.idx}`] = `${columnMetrics.get(column)!.left}px`;
}

return { templateColumns, layoutCssVars, totalFrozenColumnWidth, columnMetrics };
}, [getColumnWidth, columns, lastFrozenColumnIndex]);
return { templateColumns, layoutCssVars, totalFrozenColumnWidth, totalRightFrozenColumnWidth, columnMetrics };
}, [getColumnWidth, columns, lastFrozenColumnIndex, rightFrozenColumnCount]);

const [colOverscanStartIdx, colOverscanEndIdx] = useMemo((): [number, number] => {
if (!enableVirtualization) {
return [0, columns.length - 1];
}
// get the viewport's left side and right side positions for non-frozen columns
const viewportLeft = scrollLeft + totalFrozenColumnWidth;
const viewportRight = scrollLeft + viewportWidth;
const viewportRight = scrollLeft + viewportWidth - totalRightFrozenColumnWidth;
// get first and last non-frozen column indexes
const lastColIdx = columns.length - 1;
const firstUnfrozenColumnIdx = min(lastFrozenColumnIndex + 1, lastColIdx);
const lastUnfrozonColumnIdx = min(columns.length - rightFrozenColumnCount -1, lastColIdx);

// skip rendering non-frozen columns if the frozen columns cover the entire viewport
if (viewportLeft >= viewportRight) {
Expand All @@ -217,7 +249,7 @@ export function useCalculatedColumns<R, SR>({

// get the first visible non-frozen column index
let colVisibleStartIdx = firstUnfrozenColumnIdx;
while (colVisibleStartIdx < lastColIdx) {
while (colVisibleStartIdx < lastUnfrozonColumnIdx) {
const { left, width } = columnMetrics.get(columns[colVisibleStartIdx])!;
// if the right side of the columnn is beyond the left side of the available viewport,
// then it is the first column that's at least partially visible
Expand All @@ -229,7 +261,7 @@ export function useCalculatedColumns<R, SR>({

// get the last visible non-frozen column index
let colVisibleEndIdx = colVisibleStartIdx;
while (colVisibleEndIdx < lastColIdx) {
while (colVisibleEndIdx < lastUnfrozonColumnIdx) {
const { left, width } = columnMetrics.get(columns[colVisibleEndIdx])!;
// if the right side of the column is beyond or equal to the right side of the available viewport,
// then it the last column that's at least partially visible, as the previous column's right side is not beyond the viewport.
Expand All @@ -240,7 +272,7 @@ export function useCalculatedColumns<R, SR>({
}

const colOverscanStartIdx = max(firstUnfrozenColumnIdx, colVisibleStartIdx - 1);
const colOverscanEndIdx = min(lastColIdx, colVisibleEndIdx + 1);
const colOverscanEndIdx = min(lastUnfrozonColumnIdx, colVisibleEndIdx + 1);

return [colOverscanStartIdx, colOverscanEndIdx];
}, [
Expand All @@ -249,6 +281,7 @@ export function useCalculatedColumns<R, SR>({
lastFrozenColumnIndex,
scrollLeft,
totalFrozenColumnWidth,
totalRightFrozenColumnWidth,
viewportWidth,
enableVirtualization
]);
Expand All @@ -262,7 +295,9 @@ export function useCalculatedColumns<R, SR>({
layoutCssVars,
headerRowsCount,
lastFrozenColumnIndex,
totalFrozenColumnWidth
totalFrozenColumnWidth,
rightFrozenColumnCount,
totalRightFrozenColumnWidth
};
}

Expand Down
7 changes: 7 additions & 0 deletions src/hooks/useViewportColumns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ interface ViewportColumnsArgs<R, SR> {
colOverscanStartIdx: number;
colOverscanEndIdx: number;
lastFrozenColumnIndex: number;
rightFrozenColumnCount: number;
rowOverscanStartIdx: number;
rowOverscanEndIdx: number;
}
Expand All @@ -25,6 +26,7 @@ export function useViewportColumns<R, SR>({
colOverscanStartIdx,
colOverscanEndIdx,
lastFrozenColumnIndex,
rightFrozenColumnCount,
rowOverscanStartIdx,
rowOverscanEndIdx
}: ViewportColumnsArgs<R, SR>) {
Expand Down Expand Up @@ -110,6 +112,11 @@ export function useViewportColumns<R, SR>({
viewportColumns.push(column);
}

for(let colIdx = columns.length-rightFrozenColumnCount; colIdx<columns.length; colIdx++) {
const column = columns[colIdx];
viewportColumns.push(column);
}

return viewportColumns;
}, [startIdx, colOverscanEndIdx, columns]);
}
16 changes: 16 additions & 0 deletions src/style/cell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,19 @@ export const cellFrozen = css`
`;

export const cellFrozenClassname = `rdg-cell-frozen ${cellFrozen}`;


export const cellRightFrozen = css`
@layer rdg.Cell {
position: sticky;
/* Should have a higher value than 0 to show up above unfrozen cells */
z-index: 1;
right: 0;
/* Add box-shadow on the last frozen cell */
&:nth-child(1 of &) {
box-shadow: var(--rdg-cell-right-frozen-box-shadow);
}
}
`;

export const cellRightFrozenClassname = `rdg-cell-frozen ${cellRightFrozen}`;
2 changes: 2 additions & 0 deletions src/style/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,11 @@ const root = css`
--rdg-selection-color: #66afe9;
--rdg-font-size: 14px;
--rdg-cell-frozen-box-shadow: 2px 0 5px -2px rgba(136, 136, 136, 0.3);
--rdg-cell-right-frozen-box-shadow: -2px 0 5px -2px rgba(136, 136, 136, 0.3);

&:dir(rtl) {
--rdg-cell-frozen-box-shadow: -2px 0 5px -2px rgba(136, 136, 136, 0.3);
--rdg-cell-right-frozen-box-shadow: 2px 0 5px -2px rgba(136, 136, 136, 0.3);
}

display: grid;
Expand Down
2 changes: 2 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ export interface Column<TRow, TSummaryRow = unknown> {
readonly colSpan?: Maybe<(args: ColSpanArgs<TRow, TSummaryRow>) => Maybe<number>>;
/** Determines whether column is frozen or not */
readonly frozen?: Maybe<boolean>;
/** Determines whether column is right frozen or not */
readonly rightFrozen?: Maybe<boolean>;
/** Enable resizing of a column */
readonly resizable?: Maybe<boolean>;
/** Enable sorting of a column */
Expand Down
8 changes: 5 additions & 3 deletions src/utils/styleUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { CSSProperties } from 'react';
import clsx from 'clsx';

import type { CalculatedColumn, CalculatedColumnOrColumnGroup } from '../types';
import { cellClassname, cellFrozenClassname } from '../style/cell';
import { cellClassname, cellFrozenClassname, cellRightFrozenClassname } from '../style/cell';

export function getRowStyle(rowIdx: number): CSSProperties {
return { '--rdg-grid-row-start': rowIdx } as unknown as CSSProperties;
Expand Down Expand Up @@ -41,7 +41,8 @@ export function getCellStyle<R, SR>(
return {
gridColumnStart: index,
gridColumnEnd: index + colSpan,
insetInlineStart: column.frozen ? `var(--rdg-frozen-left-${column.idx})` : undefined
insetInlineStart: column.frozen ? `var(--rdg-frozen-left-${column.idx})` : undefined,
insetInlineEnd: column.rightFrozen? `var(--rdg-frozen-right-${column.idx})` : undefined,
};
}

Expand All @@ -52,7 +53,8 @@ export function getCellClassname<R, SR>(
return clsx(
cellClassname,
{
[cellFrozenClassname]: column.frozen
[cellFrozenClassname]: column.frozen,
[cellRightFrozenClassname]: column.rightFrozen,
},
...extraClasses
);
Expand Down
6 changes: 4 additions & 2 deletions website/routes/CommonFeatures.lazy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -214,16 +214,18 @@ function getColumns(
},
{
key: 'account',
name: 'Account'
name: 'Account',
rightFrozen: true,
},
{
key: 'version',
name: 'Version',
renderEditCell: textEditor
renderEditCell: textEditor,
},
{
key: 'available',
name: 'Available',
rightFrozen: true,
renderCell({ row, onRowChange, tabIndex }) {
return (
<SelectCellFormatter
Expand Down