Skip to content
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
26 changes: 14 additions & 12 deletions packages/core/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,9 @@ or you can create a portal element yourself using the `createPortal` function fr
```jsx
const portalRef = useRef(null);
<>
{
createPortal(
<div ref={portalRef} style="position: fixed; left: 0; top: 0; z-index: 9999;" />,
document.body
)
}
<DataEditor width={500} height={300} portalElementRef={portalRef} {...props} />
</>
{createPortal(<div ref={portalRef} style="position: fixed; left: 0; top: 0; z-index: 9999;" />, document.body)}
<DataEditor width={500} height={300} portalElementRef={portalRef} {...props} />
</>;
```

Once you've got that done, the easiest way to use the Data Grid is to give it a fixed size:
Expand Down Expand Up @@ -87,10 +82,10 @@ All data grids must set these props. These props are the bare minimum required t
Most data grids will want to set the majority of these props one way or another.

| Name | Description |
|---------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [fixedShadowX](#fixedshadow) | Enable/disable a shadow behind fixed columns on the X axis. |
| [fixedShadowY](#fixedshadow) | Enable/disable a shadow behind the header(s) on the Y axis. |
| [freezeColumns](#freezecolumns) | The number of columns which should remain in place when scrolling horizontally. The row marker column, if enabled is always frozen and is not included in this count. |
| [freezeColumns](#freezecolumns) | The number of columns which should remain in place when scrolling horizontally, or a tuple `[left, right]` to freeze columns on both sides. The row marker column, if enabled is always frozen and is not included in this count. |
| [getCellsForSelection](#getcellsforselection) | Used to fetch large amounts of cells at once. Used for copy/paste, if unset copy will not work. |
| [markdownDivCreateNode](#markdowndivcreatenode) | If specified, it will be used to render Markdown, instead of the default Markdown renderer used by the Grid. You'll want to use this if you need to process your Markdown for security purposes, or if you want to use a renderer with different Markdown features. |
| [onVisibleRegionChanged](#onvisibleregionchanged) | Emits whenever the visible rows/columns changes. |
Expand Down Expand Up @@ -192,7 +187,7 @@ Most data grids will want to set the majority of these props one way or another.
| [onRowMoved](#onrowmoved) | Emitted when a row has been dragged to a new location. |
| [preventDiagonalScrolling](#preventdiagonalscrolling) | Prevents diagonal scrolling |
| [rowSelectionMode](#rowselectionmode) | Determines if row selection requires a modifier key to enable multi-selection or not. |
| [columnSelectionMode](#columnselectionmode) | Determines if column selection requires a modifier key to enable multi-selection or not. |
| [columnSelectionMode](#columnselectionmode) | Determines if column selection requires a modifier key to enable multi-selection or not. |
| [scrollToEnd](#scrolltoend) | When set to true, the grid will scroll to the end. The ref has a better method to do this and this prop should not be used but it will remain supported for the foreseeable future. |
| [showMinimap](#showminimap) | Shows the interactive minimap of the grid. |
| [validateCell](#validatecell) | When returns false indicates to the user the value will not be accepted. When returns a new GridCell the value is coerced to match. |
Expand Down Expand Up @@ -550,11 +545,18 @@ getCellContent: (cell: Item) => GridCell;
## freezeColumns

```ts
freezeColumns?: number;
freezeColumns?: number | readonly [left: number, right: number];
```

Set to a positive number to freeze columns on the left side of the grid during horizontal scrolling.

Alternatively, pass a tuple `[left, right]` where:

- `left` is the number of columns to freeze on the left side
- `right` is the number of columns to freeze on the right side

Note: The row marker column, if enabled, is always frozen and is not included in the left freeze count.

---

## getCellsForSelection
Expand Down
15 changes: 12 additions & 3 deletions packages/core/src/common/render-state-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
Expand Down
7 changes: 7 additions & 0 deletions packages/core/src/common/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -282,3 +282,10 @@ export function useDeepMemo<T>(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];
}
74 changes: 57 additions & 17 deletions packages/core/src/data-editor/data-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -923,6 +923,8 @@ const DataEditorImpl: React.ForwardRefRenderFunction<DataEditorRef, DataEditorPr
const maxColumnWidth = Math.max(maxColumnWidthIn, minColumnWidth);
const maxColumnAutoWidth = Math.max(maxColumnAutoWidthIn ?? maxColumnWidth, minColumnWidth);

const [freezeLeftColumns, freezeRightColumns] = normalizeFreezeColumns(freezeColumns);

const docStyle = React.useMemo(() => {
if (typeof window === "undefined") return { fontSize: "16px" };
return window.getComputedStyle(document.documentElement);
Expand Down Expand Up @@ -1573,9 +1575,13 @@ const DataEditorImpl: React.ForwardRefRenderFunction<DataEditorRef, DataEditorPr
height: targetRect.height + 2 * paddingY,
};

let frozenWidth = 0;
for (let i = 0; i < freezeColumns; i++) {
frozenWidth += columns[i].width;
let frozenLeftWidth = 0;
for (let i = 0; i < freezeLeftColumns; i++) {
frozenLeftWidth += columns[i].width;
}
let frozenRightWidth = 0;
for (let i = columns.length - 1; i >= columns.length - freezeRightColumns; i--) {
frozenRightWidth += columns[i].width;
}
let trailingRowHeight = 0;
const freezeTrailingRowsEffective = freezeTrailingRows + (lastRowSticky ? 1 : 0);
Expand All @@ -1588,8 +1594,9 @@ const DataEditorImpl: React.ForwardRefRenderFunction<DataEditorRef, DataEditorPr
}

// scrollBounds is already scaled
let sLeft = frozenWidth * scale + scrollBounds.left + rowMarkerOffset * rowMarkerWidth * scale;
let sRight = scrollBounds.right;
let sLeft =
frozenLeftWidth * scale + scrollBounds.left + rowMarkerOffset * rowMarkerWidth * scale;
let sRight = scrollBounds.right - frozenRightWidth * scale;
let sTop = scrollBounds.top + totalHeaderHeight * scale;
let sBottom = scrollBounds.bottom - trailingRowHeight * scale;

Expand Down Expand Up @@ -1633,7 +1640,11 @@ const DataEditorImpl: React.ForwardRefRenderFunction<DataEditorRef, DataEditorPr
scrollY = bounds.y + bounds.height - sBottom;
}

if (dir === "vertical" || (typeof col === "number" && col < freezeColumns)) {
if (
dir === "vertical" ||
(typeof col === "number" &&
(col < freezeLeftColumns || col >= mangledCols.length - freezeRightColumns))
) {
scrollX = 0;
} else if (
dir === "horizontal" ||
Expand Down Expand Up @@ -1664,7 +1675,9 @@ const DataEditorImpl: React.ForwardRefRenderFunction<DataEditorRef, DataEditorPr
rowMarkerWidth,
scrollRef,
totalHeaderHeight,
freezeColumns,
freezeLeftColumns,
freezeRightColumns,
mangledCols.length,
columns,
mangledRows,
lastRowSticky,
Expand Down Expand Up @@ -2596,18 +2609,29 @@ const DataEditorImpl: React.ForwardRefRenderFunction<DataEditorRef, DataEditorPr
selected = [selected[0] - rowMarkerOffset, selected[1]];
}

const freezeRegion =
freezeColumns === 0
const freezeLeftRegion =
freezeLeftColumns === 0
? undefined
: {
x: 0,
y: region.y,
width: freezeColumns,
width: freezeLeftColumns,
height: region.height,
};

const freezeRightRegion =
freezeRightColumns === 0
? undefined
: {
x: columns.length - freezeRightColumns,
y: region.y,
width: freezeRightColumns,
height: region.height,
};

const freezeRegions: Rectangle[] = [];
if (freezeRegion !== undefined) freezeRegions.push(freezeRegion);
if (freezeLeftRegion !== undefined) freezeRegions.push(freezeLeftRegion);
if (freezeRightRegion !== undefined) freezeRegions.push(freezeRightRegion);
if (freezeTrailingRows > 0) {
freezeRegions.push({
x: region.x - rowMarkerOffset,
Expand All @@ -2616,11 +2640,20 @@ const DataEditorImpl: React.ForwardRefRenderFunction<DataEditorRef, DataEditorPr
height: freezeTrailingRows,
});

if (freezeColumns > 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,
});
}
Expand All @@ -2635,7 +2668,7 @@ const DataEditorImpl: React.ForwardRefRenderFunction<DataEditorRef, DataEditorPr
ty,
extras: {
selected,
freezeRegion,
freezeRegion: freezeLeftRegion,
freezeRegions,
},
};
Expand All @@ -2649,7 +2682,9 @@ const DataEditorImpl: React.ForwardRefRenderFunction<DataEditorRef, DataEditorPr
rowMarkerOffset,
showTrailingBlankRow,
rows,
freezeColumns,
freezeLeftColumns,
freezeRightColumns,
columns.length,
freezeTrailingRows,
setVisibleRegion,
onVisibleRegionChanged,
Expand Down Expand Up @@ -3991,7 +4026,12 @@ const DataEditorImpl: React.ForwardRefRenderFunction<DataEditorRef, DataEditorPr
);
}, [onGroupHeaderRenamed, renameGroup]);

const mangledFreezeColumns = Math.min(mangledCols.length, freezeColumns + (hasRowMarkers ? 1 : 0));
const mangledFreezeColumns = React.useMemo(() => {
return [
Math.min(mangledCols.length, freezeLeftColumns + (hasRowMarkers ? 1 : 0)),
Math.min(mangledCols.length, freezeRightColumns)
] as const;
}, [freezeLeftColumns, freezeRightColumns, hasRowMarkers, mangledCols.length])

React.useImperativeHandle(
forwardedRef,
Expand Down
16 changes: 12 additions & 4 deletions packages/core/src/docs/examples/freeze-columns.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,14 @@ export default {
],
};

export const FreezeColumns: React.VFC<any> = (p: { freezeColumns: number }) => {
export const FreezeColumns: React.VFC<any> = (p: { freezeLeftColumns: number, freezeRightColumns: number }) => {
const { cols, getCellContent } = useMockDataGenerator(100);

return (
<DataEditor
{...defaultProps}
rowMarkers="both"
freezeColumns={p.freezeColumns}
freezeColumns={[p.freezeLeftColumns, p.freezeRightColumns]}
getCellContent={getCellContent}
columns={cols}
verticalBorder={false}
Expand All @@ -46,7 +46,14 @@ export const FreezeColumns: React.VFC<any> = (p: { freezeColumns: number }) => {
);
};
(FreezeColumns as any).argTypes = {
freezeColumns: {
freezeLeftColumns: {
control: {
type: "range",
min: 0,
max: 10,
},
},
freezeRightColumns: {
control: {
type: "range",
min: 0,
Expand All @@ -55,5 +62,6 @@ export const FreezeColumns: React.VFC<any> = (p: { freezeColumns: number }) => {
},
};
(FreezeColumns as any).args = {
freezeColumns: 1,
freezeLeftColumns: 1,
freezeRightColumns: 1,
};
Loading