Skip to content

Commit

Permalink
Improve accuracy and add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
jassmith committed Feb 7, 2024
1 parent 39dceb3 commit ffa364a
Show file tree
Hide file tree
Showing 5 changed files with 453 additions and 73 deletions.
5 changes: 2 additions & 3 deletions packages/core/src/data-editor/data-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -745,7 +745,7 @@ const DataEditorImpl: React.ForwardRefRenderFunction<DataEditorRef, DataEditorPr
height,
columns: columnsIn,
rows: rowsIn,
getCellContent: getCellContentIn,
getCellContent,
onCellClicked,
onCellActivated,
onFillPattern,
Expand Down Expand Up @@ -866,11 +866,10 @@ const DataEditorImpl: React.ForwardRefRenderFunction<DataEditorRef, DataEditorPr

const {
rows,
getCellContent,
rowNumberMapper,
rowHeight: rowHeightPostGrouping,
getRowThemeOverride,
} = useRowGroupingInner(rowGrouping, rowsIn, getCellContentIn, rowHeightIn, getRowThemeOverrideIn);
} = useRowGroupingInner(rowGrouping, rowsIn, rowHeightIn, getRowThemeOverrideIn);

const remSize = React.useMemo(() => Number.parseFloat(docStyle.fontSize), [docStyle]);
const { rowHeight, headerHeight, groupHeaderHeight, theme, overscrollX, overscrollY } = useRemAdjuster({
Expand Down
23 changes: 17 additions & 6 deletions packages/core/src/data-editor/row-grouping-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@ import React from "react";
import type { Item } from "../internal/data-grid/data-grid-types.js";
import { flattenRowGroups, mapRowIndexToPath, type RowGroup, type RowGroupingOptions } from "./row-grouping.js";

type RowGroupingMapper = (itemOrRow: Item | number) => {
type RowGroupingMapperResult<T> = {
path: readonly number[];
originalIndex: number;
originalIndex: T;
isGroupHeader: boolean;
groupRows: number;
};

type RowGroupingMapper = {
(itemOrRow: number): RowGroupingMapperResult<number>;
(itemOrRow: Item): RowGroupingMapperResult<Item>;
};

interface UseRowGroupingResult {
readonly mapper: RowGroupingMapper;
readonly updateRowGroupingByPath: (
Expand All @@ -28,10 +33,16 @@ export function useRowGrouping(options: RowGroupingOptions | undefined, rows: nu
return {
getRowGroupingForPath,
updateRowGroupingByPath,
mapper: React.useCallback(
(itemOrRow: Item | number) => {
itemOrRow = typeof itemOrRow === "number" ? itemOrRow : itemOrRow[1];
return mapRowIndexToPath(itemOrRow, flattenedRowGroups);
mapper: React.useCallback<UseRowGroupingResult["mapper"]>(
(itemOrRow: number | Item) => {
if (typeof itemOrRow === "number") {
return mapRowIndexToPath(itemOrRow, flattenedRowGroups);
}
const r = mapRowIndexToPath(itemOrRow[1], flattenedRowGroups);
return {
...r,
originalIndex: [itemOrRow[0], r.originalIndex],
} as any; // FIXME
},
[flattenedRowGroups]
),
Expand Down
103 changes: 41 additions & 62 deletions packages/core/src/data-editor/row-grouping.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React from "react";
import type { Theme } from "../common/styles.js";
import type { GridCell, Item } from "../internal/data-grid/data-grid-types.js";
import type { DataEditorProps } from "./data-editor.js";
import type { DataGridProps } from "../internal/data-grid/data-grid.js";
import { whenDefined } from "../common/utils.js";
Expand Down Expand Up @@ -67,7 +66,7 @@ export type ExpandedRowGroup = {
subGroups?: readonly ExpandedRowGroup[];
};

function expandRowGroups(groups: readonly RowGroup[]): ExpandedRowGroup[] {
export function expandRowGroups(groups: readonly RowGroup[]): ExpandedRowGroup[] {
function processGroup(group: RowGroup, depth: number, path: readonly number[]): ExpandedRowGroup {
if (typeof group === "number") {
return {
Expand Down Expand Up @@ -100,38 +99,49 @@ function expandRowGroups(groups: readonly RowGroup[]): ExpandedRowGroup[] {
return expanded.sort((a, b) => a.headerIndex - b.headerIndex);
}

export type FlattenedRowGroup = {
export interface FlattenedRowGroup {
readonly headerIndex: number;
readonly contentIndex: number; // the content index of the first row in the group
readonly isCollapsed: boolean;
readonly depth: number;
readonly rows: number;
readonly path: readonly number[];
};
}

export function flattenRowGroups(rowGrouping: RowGroupingOptions, rows: number): FlattenedRowGroup[] {
if (rowGrouping === undefined) return [];
interface SkippableFlattenedRowGroup extends FlattenedRowGroup {
readonly skip: boolean;
}

const flattened: FlattenedRowGroup[] = [];
export function flattenRowGroups(rowGrouping: RowGroupingOptions, rows: number): FlattenedRowGroup[] {
const flattened: SkippableFlattenedRowGroup[] = [];

function processGroup(group: ExpandedRowGroup, nextHeaderIndex: number | null): void {
function processGroup(
group: ExpandedRowGroup,
nextHeaderIndex: number | null,
skipChildren: boolean = false
): void {
let rowsInGroup = nextHeaderIndex !== null ? nextHeaderIndex - group.headerIndex : rows - group.headerIndex;
if (!group.isCollapsed && group.subGroups !== undefined) {
if (group.subGroups !== undefined) {
rowsInGroup = group.subGroups[0].headerIndex - group.headerIndex;
}

rowsInGroup--; // the header isn't in the group

flattened.push({
headerIndex: group.headerIndex,
contentIndex: -1, // we will fill this in later
skip: skipChildren,
isCollapsed: group.isCollapsed,
depth: group.depth,
path: group.path,
rows: rowsInGroup,
});

if (!group.isCollapsed && group.subGroups) {
if (group.subGroups) {
for (let i = 0; i < group.subGroups.length; i++) {
const nextSubHeaderIndex =
i < group.subGroups.length - 1 ? group.subGroups[i + 1].headerIndex : nextHeaderIndex;
processGroup(group.subGroups[i], nextSubHeaderIndex);
processGroup(group.subGroups[i], nextSubHeaderIndex, skipChildren || group.isCollapsed);
}
}
}
Expand All @@ -143,7 +153,18 @@ export function flattenRowGroups(rowGrouping: RowGroupingOptions, rows: number):
processGroup(expandedGroups[i], nextHeaderIndex);
}

return flattened;
let contentIndex = 0;
for (const g of flattened) {
(g as any).contentIndex = contentIndex;
contentIndex += g.rows;
}

return flattened
.filter(x => x.skip === false)
.map(x => {
const { skip, ...rest } = x;
return rest;
});
}

interface MapResult {
Expand All @@ -168,34 +189,29 @@ export function mapRowIndexToPath(row: number, flattenedRowGroups?: readonly Fla
};

let toGo = row;
let originalIndex = 0;
let contentIndex = 0;
for (const group of flattenedRowGroups) {
if (toGo === 0)
return {
path: [...group.path, -1],
originalIndex,
originalIndex: group.headerIndex,
isGroupHeader: true,
groupIndex: -1,
contentIndex: -1,
groupRows: group.rows,
};
toGo--;
originalIndex++;
if (!group.isCollapsed) {
if (toGo < group.rows)
return {
path: [...group.path, toGo],
originalIndex: originalIndex + toGo,
originalIndex: group.headerIndex + toGo,
isGroupHeader: false,
groupIndex: toGo,
contentIndex: contentIndex + toGo,
contentIndex: group.contentIndex + toGo,
groupRows: group.rows,
};
toGo -= group.rows;
contentIndex += group.rows;
}
originalIndex += group.rows;
}
// this shouldn't happen
// this is a fucking awful code smell. Probably means the algorithm above is trash and can be done better.
Expand All @@ -212,7 +228,6 @@ export function mapRowIndexToPath(row: number, flattenedRowGroups?: readonly Fla

export interface UseRowGroupingInnerResult {
readonly rows: number;
readonly getCellContent: (cell: Item) => GridCell;
readonly rowNumberMapper: (row: number) => number | undefined;
readonly rowHeight: NonNullable<DataEditorProps["rowHeight"]>;
readonly getRowThemeOverride: DataGridProps["getRowThemeOverride"];
Expand All @@ -221,7 +236,6 @@ export interface UseRowGroupingInnerResult {
export function useRowGroupingInner(
options: RowGroupingOptions | undefined,
rows: number,
getCellContentIn: DataEditorProps["getCellContent"],
rowHeightIn: NonNullable<DataEditorProps["rowHeight"]>,
getRowThemeOverrideIn: DataEditorProps["getRowThemeOverride"]
): UseRowGroupingInnerResult {
Expand All @@ -245,67 +259,34 @@ export function useRowGroupingInner(
};
}, [flattenedRowGroups, options, rowHeightIn]);

const rowNumberMapper = React.useCallback(
(row: number): number => {
if (flattenedRowGroups === undefined) return row;
let resultRow = 0;
let toGo = row;

for (const group of flattenedRowGroups) {
if (toGo === 0) resultRow;
toGo--;
resultRow++;
if (!group.isCollapsed) {
if (toGo < group.rows) return resultRow + toGo;
toGo -= group.rows;
resultRow += group.rows;
}
}

return row;
},
[flattenedRowGroups]
);

const rowNumberMapperOut = React.useCallback(
(row: number): number | undefined => {
if (flattenedRowGroups === undefined) return row;
let resultRow = 0;
let toGo = row;

for (const group of flattenedRowGroups) {
if (toGo === 0) return undefined;
toGo--;
if (!group.isCollapsed) {
if (toGo < group.rows) return resultRow + toGo;
if (toGo < group.rows) return group.contentIndex + toGo;
toGo -= group.rows;
}
resultRow += group.rows;
}

return row;
},
[flattenedRowGroups]
);

const getCellContentOut = React.useCallback(
(cell: Item) => {
if (options === undefined) return getCellContentIn(cell);
const mapped = rowNumberMapper(cell[1]);
return getCellContentIn([cell[0], mapped]);
},
[getCellContentIn, options, rowNumberMapper]
);

const getRowThemeOverride = whenDefined(
getRowThemeOverrideIn,
getRowThemeOverrideIn ?? options?.themeOverride,
React.useCallback(
(row: number): Partial<Theme> | undefined => {
if (getRowThemeOverrideIn === undefined) return undefined;
if (options === undefined) return getRowThemeOverrideIn(row, row, row);
if (options === undefined) return getRowThemeOverrideIn?.(row, row, row);
if (getRowThemeOverrideIn === undefined && options?.themeOverride === undefined) return undefined;
const { isGroupHeader, contentIndex, groupIndex } = mapRowIndexToPath(row, flattenedRowGroups);
if (isGroupHeader) return options.themeOverride;
return getRowThemeOverrideIn(row, groupIndex, contentIndex);
return getRowThemeOverrideIn?.(row, groupIndex, contentIndex);
},
[flattenedRowGroups, getRowThemeOverrideIn, options]
)
Expand All @@ -314,15 +295,13 @@ export function useRowGroupingInner(
if (options === undefined)
return {
rowHeight,
getCellContent: getCellContentIn,
rows: rows,
rowNumberMapper: rowNumberMapperOut,
getRowThemeOverride,
};

return {
rowHeight,
getCellContent: getCellContentOut,
rows: effectiveRows,
rowNumberMapper: rowNumberMapperOut,
getRowThemeOverride,
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/docs/examples/row-grouping.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export const RowGrouping: React.VFC<any> = (p: { freezeColumns: number }) => {

const getCellContentMangled = React.useCallback<DataEditorAllProps["getCellContent"]>(
item => {
const { path, originalIndex, isGroupHeader } = mapper(item);
const { path, isGroupHeader, originalIndex } = mapper(item);
if (item[0] === 0) {
return {
kind: GridCellKind.Text,
Expand All @@ -111,7 +111,7 @@ export const RowGrouping: React.VFC<any> = (p: { freezeColumns: number }) => {
};
}

return getCellContent([item[0], originalIndex]);
return getCellContent(originalIndex);
},
[getCellContent, mapper]
);
Expand Down
Loading

0 comments on commit ffa364a

Please sign in to comment.