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
406 changes: 147 additions & 259 deletions package-lock.json

Large diffs are not rendered by default.

Binary file not shown.
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,4 @@
"tsc-esm-fix": "^3.1.2",
"typedoc": "^0.28.5"
}
}
}
33 changes: 23 additions & 10 deletions packages/core/src/data-editor/data-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ import {
import type { DataGridRef } from "../internal/data-grid/data-grid.js";
import { getScrollBarWidth, useEventListener, whenDefined } from "../common/utils.js";
import {
getColumnGroupPath,
getColumnGroupName,
getGroupDepth,
isGroupEqual,
itemsAreEqual,
itemIsInRect,
Expand Down Expand Up @@ -1128,11 +1131,10 @@ const DataEditorImpl: React.ForwardRefRenderFunction<DataEditorRef, DataEditorPr
);
if (rowMarkers !== "none") nonGrowWidth += rowMarkerWidth;

const enableGroups = React.useMemo(() => {
return columns.some(c => c.group !== undefined);
}, [columns]);

const totalHeaderHeight = enableGroups ? headerHeight + groupHeaderHeight : headerHeight;
const groupHeaderDepth = React.useMemo(() => getGroupDepth(columns), [columns]);
const enableGroups = groupHeaderDepth > 0;
const totalGroupHeaderHeight = enableGroups ? groupHeaderHeight * groupHeaderDepth : 0;
const totalHeaderHeight = headerHeight + totalGroupHeaderHeight;

const numSelectedRows = gridSelection.rows.length;
const rowMarkerChecked =
Expand Down Expand Up @@ -1399,8 +1401,8 @@ const DataEditorImpl: React.ForwardRefRenderFunction<DataEditorRef, DataEditorPr
);

const mangledGetGroupDetails = React.useCallback<NonNullable<DataEditorProps["getGroupDetails"]>>(
group => {
let result = getGroupDetails?.(group) ?? { name: group };
(group, context) => {
let result = getGroupDetails?.(group, context) ?? { name: group };
if (onGroupHeaderRenamed !== undefined && group !== "") {
result = {
icon: result.icon,
Expand Down Expand Up @@ -1429,8 +1431,16 @@ const DataEditorImpl: React.ForwardRefRenderFunction<DataEditorRef, DataEditorPr
(val: Omit<NonNullable<typeof overlay>, "theme">) => {
const [col, row] = val.cell;
const column = mangledCols[col];
const leafGroupPath = getColumnGroupPath(column?.group, 0) ?? [];
const leafGroup =
leafGroupPath.length === 0 ? undefined : leafGroupPath[leafGroupPath.length - 1];
const groupTheme =
column?.group !== undefined ? mangledGetGroupDetails(column.group)?.overrideTheme : undefined;
leafGroup !== undefined
? mangledGetGroupDetails(leafGroup, {
path: leafGroupPath,
levelFromBottom: 0,
})?.overrideTheme
: undefined;
const colTheme = column?.themeOverride;
const rowTheme = getRowThemeOverride?.(row);

Expand Down Expand Up @@ -2144,20 +2154,23 @@ const DataEditorImpl: React.ForwardRefRenderFunction<DataEditorRef, DataEditorPr
}
const isMultiKey = browserIsOSX.value ? args.metaKey : args.ctrlKey;
const [col] = args.location;
const groupLevelFromBottom = -2 - args.location[1];
const selectedColumns = gridSelection.columns;

if (col < rowMarkerOffset) return;
if (groupLevelFromBottom < 0 || groupLevelFromBottom >= getGroupDepth(mangledCols)) return;

const needle = mangledCols[col];
const groupName = getColumnGroupName(needle.group, groupLevelFromBottom);
let start = col;
let end = col;
for (let i = col - 1; i >= rowMarkerOffset; i--) {
if (!isGroupEqual(needle.group, mangledCols[i].group)) break;
if (!isGroupEqual(groupName, getColumnGroupName(mangledCols[i].group, groupLevelFromBottom))) break;
start--;
}

for (let i = col + 1; i < mangledCols.length; i++) {
if (!isGroupEqual(needle.group, mangledCols[i].group)) break;
if (!isGroupEqual(groupName, getColumnGroupName(mangledCols[i].group, groupLevelFromBottom))) break;
end++;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
} from "../../data-editor/stories/utils.js";
import type { GridColumn } from "../../internal/data-grid/data-grid-types.js";
import { CompactSelection } from "../../internal/data-grid/data-grid-types.js";
import { getColumnGroupName } from "../../internal/data-grid/render/data-grid-lib.js";
import { SimpleThemeWrapper } from "../../stories/story-utils.js";
import type { GroupHeaderClickedEventArgs } from "../../internal/data-grid/event-args.js";

Expand Down Expand Up @@ -44,12 +45,12 @@ function useCollapsableColumnGroups(cols: readonly GridColumn[]) {
const [collapsed, setCollapsed] = React.useState<readonly string[]>([]);

const onGroupHeaderClicked = React.useCallback(
(colIndex: number, args: GroupHeaderClickedEventArgs) => {
const group = cols[colIndex].group ?? "";
(_colIndex: number, args: GroupHeaderClickedEventArgs) => {
const group = args.group;
setCollapsed(cv => (cv.includes(group) ? cv.filter(g => g !== group) : [...cv, group]));
args.preventDefault();
},
[cols]
[]
);

const [selectedColumns, setSelectedColumns] = React.useState<CompactSelection>(CompactSelection.empty());
Expand All @@ -62,7 +63,7 @@ function useCollapsableColumnGroups(cols: readonly GridColumn[]) {

const columns = React.useMemo(() => {
return cols.map(c => {
if (!collapsed.includes(c.group ?? ""))
if (!collapsed.includes(getColumnGroupName(c.group, 0) ?? ""))
return {
...c,
hasMenu: true,
Expand Down
15 changes: 13 additions & 2 deletions packages/core/src/internal/data-grid/data-grid-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ export enum GridColumnIcon {
HeaderSplitString = "headerSplitString",
HeaderGeoDistance = "headerGeoDistance",
HeaderArray = "headerArray",
ArrowLeft = "arrowLeft",
ArrowRight = "arrowRight",
RowOwnerOverlay = "rowOwnerOverlay",
ProtectedColumnOverlay = "protectedColumnOverlay",
}
Expand All @@ -138,16 +140,25 @@ export type CellArray = readonly (readonly GridCell[])[];
* numbers identify cells.
*
* - `-1`: Header
* - `-2`: Group header
* - `-2 and lower`: Group headers (for nested groups)
* - `0 and higher`: Row index
*
* @category Types
*/
export type Item = readonly [col: number, row: number];

/** @category Columns */
export type GridColumnGroup = string | readonly string[];

export interface BaseGridColumn {
readonly title: string;
readonly group?: string;
/**
* Assigns this column to one or more header groups.
*
* - `string`: single group row
* - `string[]`: nested groups from outermost to innermost
*/
readonly group?: GridColumnGroup;
readonly icon?: GridColumnIcon | string;
readonly overlayIcon?: GridColumnIcon | string;
readonly menuIcon?: GridColumnMenuIcon | string;
Expand Down
78 changes: 59 additions & 19 deletions packages/core/src/internal/data-grid/data-grid.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import * as React from "react";
import type { FullTheme } from "../../common/styles.js";
import {
getColumnGroupPath,
computeBounds,
getColumnGroupName,
getColumnIndexForX,
getEffectiveColumns,
getGroupDepth,
getRowIndexForY,
getStickyWidth,
rectBottomRight,
Expand Down Expand Up @@ -114,6 +117,8 @@ export interface DataGridProps {
readonly getCellContent: (cell: Item, forceStrict?: boolean) => InnerGridCell;
/**
* Provides additional details about groups to extend group functionality.
* Callback receives the group name and optional context containing
* the nested path and the level from the bottom.
* @group Data
*/
readonly getGroupDetails: GroupDetailsCallback | undefined;
Expand Down Expand Up @@ -431,8 +436,6 @@ const DataGrid: React.ForwardRefRenderFunction<DataGridRef, DataGridProps> = (p,
}),
[headerIcons]
);
const totalHeaderHeight = enableGroups ? groupHeaderHeight + headerHeight : headerHeight;

const scrollingStopRef = React.useRef(-1);
const enableFirefoxRescaling = (experimental?.enableFirefoxRescaling ?? false) && browserIsFirefox.value;
const enableSafariRescaling = (experimental?.enableSafariRescaling ?? false) && browserIsSafari.value;
Expand All @@ -450,12 +453,18 @@ const DataGrid: React.ForwardRefRenderFunction<DataGridRef, DataGridProps> = (p,
}, [cellYOffset, cellXOffset, translateX, translateY, enableFirefoxRescaling, enableSafariRescaling]);

const mappedColumns = useMappedColumns(columns, freezeColumns);
const groupHeaderDepth = React.useMemo(
() => (enableGroups ? Math.max(1, getGroupDepth(columns)) : 0),
[columns, enableGroups]
);
const totalGroupHeaderHeight = enableGroups ? groupHeaderHeight * groupHeaderDepth : 0;
const totalHeaderHeight = headerHeight + totalGroupHeaderHeight;
const stickyX = React.useMemo(
() => (fixedShadowX ? getStickyWidth(mappedColumns, dragAndDropState) : 0),
[mappedColumns, dragAndDropState, fixedShadowX]
);

// row: -1 === columnHeader, -2 === groupHeader
// row: -1 === columnHeader, -2 and lower === nested group headers
const getBoundsForItem = React.useCallback(
(canvas: HTMLCanvasElement, col: number, row: number): Rectangle | undefined => {
const rect = canvas.getBoundingClientRect();
Expand All @@ -472,6 +481,7 @@ const DataGrid: React.ForwardRefRenderFunction<DataGridRef, DataGridProps> = (p,
width,
height,
groupHeaderHeight,
groupHeaderDepth,
totalHeaderHeight,
cellXOffset,
cellYOffset,
Expand Down Expand Up @@ -499,11 +509,12 @@ const DataGrid: React.ForwardRefRenderFunction<DataGridRef, DataGridProps> = (p,
[
width,
height,
groupHeaderHeight,
totalHeaderHeight,
cellXOffset,
cellYOffset,
translateX,
groupHeaderHeight,
groupHeaderDepth,
totalHeaderHeight,
cellXOffset,
cellYOffset,
translateX,
translateY,
rows,
freezeColumns,
Expand Down Expand Up @@ -555,6 +566,7 @@ const DataGrid: React.ForwardRefRenderFunction<DataGridRef, DataGridProps> = (p,
enableGroups,
headerHeight,
groupHeaderHeight,
groupHeaderDepth,
rows,
rowHeight,
cellYOffset,
Expand Down Expand Up @@ -614,22 +626,25 @@ const DataGrid: React.ForwardRefRenderFunction<DataGridRef, DataGridProps> = (p,
let bounds = getBoundsForItem(canvas, col, row);
assert(bounds !== undefined);
let isEdge = bounds !== undefined && bounds.x + bounds.width - posX <= edgeDetectionBuffer;
const groupLevel = row <= -2 ? -2 - row : 0;

const previousCol = col - 1;
if (posX - bounds.x <= edgeDetectionBuffer && previousCol >= 0) {
isEdge = true;
bounds = getBoundsForItem(canvas, previousCol, row);
assert(bounds !== undefined);
result = {
kind: enableGroups && row === -2 ? groupHeaderKind : headerKind,
kind: enableGroups && row <= -2 ? groupHeaderKind : headerKind,
location: [previousCol, row] as any,
bounds: bounds,
group: mappedColumns[previousCol].group ?? "",
group: getColumnGroupName(mappedColumns[previousCol].group, groupLevel) ?? "",
isEdge,
shiftKey,
ctrlKey,
metaKey,
isTouch,
mouseX: posX,
mouseY: posY,
localEventX: posX - bounds.x,
localEventY: posY - bounds.y,
button,
Expand All @@ -638,15 +653,17 @@ const DataGrid: React.ForwardRefRenderFunction<DataGridRef, DataGridProps> = (p,
};
} else {
result = {
kind: enableGroups && row === -2 ? groupHeaderKind : headerKind,
group: mappedColumns[col].group ?? "",
kind: enableGroups && row <= -2 ? groupHeaderKind : headerKind,
group: getColumnGroupName(mappedColumns[col].group, groupLevel) ?? "",
location: [col, row] as any,
bounds: bounds,
isEdge,
shiftKey,
ctrlKey,
metaKey,
isTouch,
mouseX: posX,
mouseY: posY,
localEventX: posX - bounds.x,
localEventY: posY - bounds.y,
button,
Expand Down Expand Up @@ -696,6 +713,8 @@ const DataGrid: React.ForwardRefRenderFunction<DataGridRef, DataGridProps> = (p,
isFillHandle,
metaKey,
isTouch,
mouseX: posX,
mouseY: posY,
localEventX: posX - bounds.x,
localEventY: posY - bounds.y,
button,
Expand All @@ -714,6 +733,7 @@ const DataGrid: React.ForwardRefRenderFunction<DataGridRef, DataGridProps> = (p,
enableGroups,
headerHeight,
groupHeaderHeight,
groupHeaderDepth,
rows,
rowHeight,
cellYOffset,
Expand Down Expand Up @@ -811,6 +831,7 @@ const DataGrid: React.ForwardRefRenderFunction<DataGridRef, DataGridProps> = (p,
theme,
headerHeight,
groupHeaderHeight,
groupHeaderDepth,
disabledRows: disabledRows ?? CompactSelection.empty(),
rowHeight,
verticalBorder,
Expand Down Expand Up @@ -883,6 +904,7 @@ const DataGrid: React.ForwardRefRenderFunction<DataGridRef, DataGridProps> = (p,
theme,
headerHeight,
groupHeaderHeight,
groupHeaderDepth,
disabledRows,
rowHeight,
verticalBorder,
Expand Down Expand Up @@ -958,7 +980,7 @@ const DataGrid: React.ForwardRefRenderFunction<DataGridRef, DataGridProps> = (p,
hCol >= 0 &&
hCol < mappedColumns.length &&
mappedColumns[hCol].headerRowMarkerDisabled !== true;
const groupHeaderHovered = hCol !== undefined && hRow === -2;
const groupHeaderHovered = hCol !== undefined && hRow !== undefined && hRow <= -2;
let clickableInnerCellHovered = false;
let editableBoolHovered = false;
let cursorOverride: React.CSSProperties["cursor"] | undefined = drawCursorOverride;
Expand Down Expand Up @@ -1002,20 +1024,26 @@ const DataGrid: React.ForwardRefRenderFunction<DataGridRef, DataGridProps> = (p,
}

const groupHeaderActionForEvent = React.useCallback(
(group: string, bounds: Rectangle, localEventX: number, localEventY: number) => {
(group: string, location: readonly [number, number], bounds: Rectangle, localEventX: number, localEventY: number) => {
if (getGroupDetails === undefined) return undefined;
const groupDesc = getGroupDetails(group);
const [col, row] = location;
const levelFromBottom = row <= -2 ? -2 - row : 0;
const groupPath = getColumnGroupPath(mappedColumns[col]?.group, levelFromBottom) ?? [];
const groupDesc = getGroupDetails(group, {
path: groupPath,
levelFromBottom,
});
if (groupDesc.actions !== undefined) {
const boxes = getActionBoundsForGroup(bounds, groupDesc.actions);
for (const [i, box] of boxes.entries()) {
if (pointInRect(box, localEventX + bounds.x, localEventY + box.y)) {
if (pointInRect(box, localEventX + bounds.x, localEventY + bounds.y)) {
return groupDesc.actions[i];
}
}
}
return undefined;
},
[getGroupDetails]
[getGroupDetails, mappedColumns]
);

const isOverHeaderElement = React.useCallback(
Expand Down Expand Up @@ -1104,7 +1132,13 @@ const DataGrid: React.ForwardRefRenderFunction<DataGridRef, DataGridProps> = (p,
) {
return;
} else if (args.kind === groupHeaderKind) {
const action = groupHeaderActionForEvent(args.group, args.bounds, args.localEventX, args.localEventY);
const action = groupHeaderActionForEvent(
args.group,
args.location,
args.bounds,
args.localEventX,
args.localEventY
);
if (action !== undefined) {
return;
}
Expand Down Expand Up @@ -1182,7 +1216,13 @@ const DataGrid: React.ForwardRefRenderFunction<DataGridRef, DataGridProps> = (p,
}
return;
} else if (args.kind === groupHeaderKind) {
const action = groupHeaderActionForEvent(args.group, args.bounds, args.localEventX, args.localEventY);
const action = groupHeaderActionForEvent(
args.group,
args.location,
args.bounds,
args.localEventX,
args.localEventY
);
if (action !== undefined) {
if (args.button === 0) {
action.onClick(args);
Expand Down
Loading