Skip to content

Commit

Permalink
enable column fit layout
Browse files Browse the repository at this point in the history
  • Loading branch information
heswell committed Jun 19, 2024
1 parent 239f5f8 commit d21c587
Show file tree
Hide file tree
Showing 11 changed files with 456 additions and 369 deletions.
9 changes: 8 additions & 1 deletion vuu-ui/packages/vuu-table-types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,14 @@ export interface TableCellRendererProps
onCommit?: DataItemCommitHandler;
}

export type ColumnLayout = "Static" | "Fit";
/**
* static layout simply respects widths specified on column descriptors.
* fit layout attempts to fit columns to available space, either stretching
* or squeezing. manual indicates that user has resized one or more columns,
* on what was originally a fit layout. Once this happens, no further auto
* fitting will take place. Fit layout always respects max and min widths,
*/
export type ColumnLayout = "static" | "fit" | "manual";

export interface TableAttributes {
columnDefaultWidth?: number;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ describe("Table scrolling and keyboard navigation", () => {
it("THEN only those columns within the viewport are rendered", () => {
// this width allows for exactly 6 visible columns, we allow a buffer of 200px
// so 2 out-of-viewport colums are rendered
cy.mount(<TwoHundredColumns width={915} />);
cy.mount(<TwoHundredColumns width={914} />);
assertRenderedColumns({
rendered: { from: 1, to: 8 },
visible: { from: 1, to: 6 },
Expand All @@ -202,7 +202,7 @@ describe("Table scrolling and keyboard navigation", () => {

describe("WHEN table is scrolled horizontally no more than 100px", () => {
it("THEN rendering is unchanged", () => {
cy.mount(<TwoHundredColumns width={915} />);
cy.mount(<TwoHundredColumns width={914} />);
cy.get(".vuuTable-scrollbarContainer").scrollTo(100, 0);
assertRenderedColumns({
rendered: { from: 1, to: 8 },
Expand Down
35 changes: 28 additions & 7 deletions vuu-ui/packages/vuu-table/src/table-config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { ColumnDescriptor, TableConfig } from "@finos/vuu-table-types";
import {
ColumnDescriptor,
RuntimeColumnDescriptor,
TableConfig,
} from "@finos/vuu-table-types";
import { getRuntimeColumnWidth } from "@finos/vuu-utils";

export type MoveColumnTableConfigAction = {
type: "col-move";
Expand All @@ -10,6 +15,7 @@ export type MoveColumnTableConfigAction = {
export type ResizeColumnTableConfigAction = {
type: "col-size";
column: ColumnDescriptor;
columns: RuntimeColumnDescriptor[];
width: number;
};

Expand All @@ -36,15 +42,30 @@ export const updateTableConfig = (
action: TableConfigAction
): TableConfig => {
switch (action.type) {
case "col-size":
case "col-size": {
const { columns: runtimeColumns, width } = action;
const isFit = config.columnLayout === "fit";
return {
...config,
columns: config.columns.map((col) =>
col.name === action.column.name
? { ...col, width: action.width }
: col
),
columnLayout: isFit ? "manual" : config.columnLayout,
columns: config.columns.map((col) => {
if (isFit) {
// When user resizes a column and 'fit' column layout is in effect,
// column layout becomes 'manual' and all columns are set to
// their current widths (unless subsequently resized by user).
return col.name === action.column.name
? { ...col, width }
: col.width
? col
: { ...col, width: getRuntimeColumnWidth(col, runtimeColumns) };
} else {
return col.name === action.column.name
? { ...col, width: action.width }
: col;
}
}),
};
}
case "column-prop":
return {
...config,
Expand Down
1 change: 1 addition & 0 deletions vuu-ui/packages/vuu-table/src/useTable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,7 @@ export const useTable = ({
updateTableConfig(tableConfig, {
type: "col-size",
column,
columns,
width,
})
)
Expand Down
41 changes: 36 additions & 5 deletions vuu-ui/packages/vuu-table/src/useTableModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
import {
applyFilterToColumns,
applyGroupByToColumns,
applyRuntimeColumnWidthsToConfig,
applySortToColumns,
applyWidthToColumns,
existingSort,
Expand Down Expand Up @@ -211,8 +212,26 @@ export type ColumnActionDispatch = (action: GridModelAction) => void;
const columnReducer: GridModelReducer = (state, action) => {
info?.(`TableModelReducer ${action.type}`);
switch (action.type) {
case "init":
return init(action);
case "init": {
if (
state.tableConfig.columnLayout === "manual" &&
action.tableConfig.columnLayout === "fit"
) {
//TODO we're jumping through hoops here when we should just make config a controlled prop

// Manual columnLayout has been assigned because user has resized one or more columns.
// It happened during current session so tableConfig still reflects original value.
return init({
...action,
tableConfig: applyRuntimeColumnWidthsToConfig(
action.tableConfig,
state.columns
),
});
} else {
return init(action);
}
}
case "moveColumn":
return moveColumn(state, action);
case "resizeColumn":
Expand Down Expand Up @@ -293,7 +312,7 @@ function init({
.filter(subscribedOnly(dataSourceConfig?.columns))
.map(toRuntimeColumnDescriptor);

const { columnLayout = "Fit" } = tableConfig;
const { columnLayout = "static" } = tableConfig;
const runtimeColumnsWithLayout = applyWidthToColumns(runtimeColumns, {
availableWidth,
columnLayout,
Expand Down Expand Up @@ -445,8 +464,20 @@ function resizeColumn(
switch (phase) {
case "begin":
return updateColumnProp(state, { type, column, resizing });
case "end":
return updateColumnProp(state, { type, column, resizing, width });
case "end": {
const { tableConfig } = state;
const isFit = tableConfig.columnLayout === "fit";
const newState: InternalTableModel = isFit
? {
...state,
tableConfig: applyRuntimeColumnWidthsToConfig(
tableConfig,
state.columns
),
}
: state;
return updateColumnProp(newState, { type, column, resizing, width });
}
case "resize":
return updateColumnProp(state, { type, column, width });
default:
Expand Down
83 changes: 61 additions & 22 deletions vuu-ui/packages/vuu-utils/src/column-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import type {
DateTimeColumnDescriptor,
TableCellRendererProps,
ColumnLayout,
TableConfig,
} from "@finos/vuu-table-types";
import type { CSSProperties } from "react";
import { moveItem } from "./array-utils";
Expand Down Expand Up @@ -90,6 +91,35 @@ export const getDefaultAlignment = (
? "right"
: "left";

export const getRuntimeColumnWidth = (
col: ColumnDescriptor,
runtimeColumns: RuntimeColumnDescriptor[]
) => {
const runtimeColumn = runtimeColumns.find(({ name }) => name === col.name);
if (runtimeColumn) {
return runtimeColumn.width;
} else {
return DEFAULT_COL_WIDTH;
}
};

// Save the current runtime column widths into the table column config. We do this
// when user has manually resized a column under a fit layout. From this point,
// layout becomes manual - there will be no further automatic column sizing.
export const applyRuntimeColumnWidthsToConfig = (
tableConfig: TableConfig,
columns: RuntimeColumnDescriptor[]
): TableConfig => {
return {
...tableConfig,
columns: columns.map((column) => ({
...column,
width: column.width ?? getRuntimeColumnWidth(column, columns),
})),
columnLayout: "manual",
};
};

export const isValidColumnAlignment = (v: string): v is ColumnAlignment =>
v === "left" || v === "right";

Expand Down Expand Up @@ -996,11 +1026,11 @@ type ColumnLayoutOptions = Pick<
>;

interface StaticColumnLayoutOptions extends ColumnLayoutOptions {
columnLayout: "Static";
columnLayout: "manual" | "static";
}
interface FitColumnLayoutOptions extends ColumnLayoutOptions {
availableWidth?: number;
columnLayout: "Fit";
columnLayout: "fit";
}

type ColumnStats = {
Expand All @@ -1013,25 +1043,15 @@ type ColumnStats = {
const measureColumns = (
columns: RuntimeColumnDescriptor[],
defaultMaxWidth: number,
defaultMinWidth: number,
defaultWidth: number
defaultMinWidth: number
) =>
columns.reduce<ColumnStats>(
(aggregated, column) => {
const { totalMinWidth, totalMaxWidth, totalWidth, flexCount } =
aggregated;
const {
minWidth = defaultMinWidth,
maxWidth = defaultMaxWidth,
width = defaultWidth,
flex = 0,
} = column;
return {
totalMinWidth: totalMinWidth + minWidth,
totalMaxWidth: totalMaxWidth + maxWidth,
totalWidth: totalWidth + width,
flexCount: flexCount + flex,
};
aggregated.totalMinWidth += column.minWidth ?? defaultMinWidth;
aggregated.totalMaxWidth += column.maxWidth ?? defaultMaxWidth;
aggregated.totalWidth += column.width;
aggregated.flexCount += column.flex ?? 0;
return aggregated;
},
{ totalMinWidth: 0, totalMaxWidth: 0, totalWidth: 0, flexCount: 0 }
);
Expand All @@ -1045,18 +1065,20 @@ export function applyWidthToColumns(
columns: RuntimeColumnDescriptor[],
{
availableWidth = 0,
columnLayout = "Static",
columnLayout = "static",
defaultWidth = DEFAULT_COL_WIDTH,
defaultMinWidth = DEFAULT_MIN_WIDTH,
defaultMaxWidth = DEFAULT_MAX_WIDTH,
}: // defaultFlex = DEFAULT_FLEX,
columnOptions
): RuntimeColumnDescriptor[] {
if (columnLayout === "Fit") {
if (columnLayout === "fit") {
const { totalMinWidth, totalMaxWidth, totalWidth, flexCount } =
measureColumns(columns, defaultMaxWidth, defaultMinWidth, defaultWidth);
measureColumns(columns, defaultMaxWidth, defaultMinWidth);

if (totalMinWidth > availableWidth || totalMaxWidth < availableWidth) {
if (totalMaxWidth < availableWidth) {
return assignMaxWidthToAll(columns, defaultMaxWidth);
} else if (totalMinWidth > availableWidth) {
return columns;
} else if (totalWidth > availableWidth) {
return shrinkColumnsToFitAvailableSpace(
Expand All @@ -1081,6 +1103,23 @@ export function applyWidthToColumns(
return columns;
}

const assignMaxWidthToAll = (
columns: RuntimeColumnDescriptor[],
defaultMaxWidth: number
) => {
return columns.map((column) => {
const { maxWidth = defaultMaxWidth } = column;
if (column.width === maxWidth) {
return column;
} else {
return {
...column,
width: maxWidth,
};
}
});
};

const shrinkColumnsToFitAvailableSpace = (
columns: RuntimeColumnDescriptor[],
availableWidth: number,
Expand Down
Loading

0 comments on commit d21c587

Please sign in to comment.