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

[v4] [table] fix layout measuring headers on initial mount #5145

Merged
merged 5 commits into from
Feb 25, 2022
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@

import * as React from "react";

import { Classes } from "@blueprintjs/core";
import { Example, IExampleProps } from "@blueprintjs/docs-theme";
import { Cell, Column, Table2 } from "@blueprintjs/table";
import { Cell, Column, ColumnHeaderCell, Table2 } from "@blueprintjs/table";

// this will obviously get outdated, it's valid only as of August 2021
const USD_TO_EURO_CONVERSION = 0.85;
Expand All @@ -31,10 +32,26 @@ export class TableDollarExample extends React.PureComponent<IExampleProps> {
return (
<Example options={false} showOptionsBelowExample={true} {...this.props}>
<Table2 numRows={20} enableGhostCells={true}>
<Column name="Dollars" cellRenderer={dollarCellRenderer} />
<Column name="Euros" cellRenderer={euroCellRenderer} />
<Column cellRenderer={dollarCellRenderer} columnHeaderCellRenderer={renderColumnHeader} />
<Column cellRenderer={euroCellRenderer} columnHeaderCellRenderer={renderColumnHeader} />
</Table2>
</Example>
);
}
}

function renderColumnHeader(index: number) {
const name = ["Dollars", "Euros"][index]!;
return <ColumnHeaderCell name={name} index={index} nameRenderer={renderName} />;
}

function renderName(name: string) {
return (
<div style={{ lineHeight: "24px" }}>
<div className={Classes.TEXT_LARGE}>
<strong>{name}</strong>
</div>
<div className={Classes.MONOSPACE_TEXT}>Number</div>
</div>
);
}
13 changes: 11 additions & 2 deletions packages/table/src/headers/columnHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ export interface IColumnHeaderProps extends IHeaderProps, IColumnWidths, ColumnI
* A callback invoked when user is done resizing the column
*/
onColumnWidthChanged: IIndexedResizeCallback;

/**
* Called on component mount.
*/
onMount?: (whichHeader: "column" | "row") => void;
}

export class ColumnHeader extends React.Component<IColumnHeaderProps> {
Expand All @@ -68,6 +73,10 @@ export class ColumnHeader extends React.Component<IColumnHeaderProps> {
loading: false,
};

public componentDidMount() {
this.props.onMount?.("column");
}

public render() {
const {
// from IColumnHeaderProps
Expand Down Expand Up @@ -101,8 +110,8 @@ export class ColumnHeader extends React.Component<IColumnHeaderProps> {
handleResizeDoubleClick={this.handleResizeDoubleClick}
handleResizeEnd={this.handleResizeEnd}
handleSizeChanged={this.handleSizeChanged}
headerCellIsReorderablePropName={"enableColumnReordering"}
headerCellIsSelectedPropName={"isColumnSelected"}
headerCellIsReorderablePropName="enableColumnReordering"
headerCellIsSelectedPropName="isColumnSelected"
headerCellRenderer={renderHeaderCell}
indexEnd={indexEnd}
indexStart={indexStart}
Expand Down
9 changes: 9 additions & 0 deletions packages/table/src/headers/rowHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,22 @@ export interface IRowHeaderProps extends IHeaderProps, IRowHeights, RowIndices {
* Renders the cell for each row header
*/
rowHeaderCellRenderer?: RowHeaderRenderer;

/**
* Called on component mount.
*/
onMount?: (whichHeader: "column" | "row") => void;
}

export class RowHeader extends React.Component<IRowHeaderProps> {
public static defaultProps = {
rowHeaderCellRenderer: renderDefaultRowHeader,
};

public componentDidMount() {
this.props.onMount?.("row");
}

public render() {
const {
// from IRowHeaderProps
Expand Down
77 changes: 32 additions & 45 deletions packages/table/src/quadrants/tableQuadrantStack.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ export interface ITableQuadrantStackProps extends Props {
* A callback that renders either all of or just the frozen section of the column header.
* May return undefined if the table is not attached to the DOM yet.
*/
columnHeaderCellRenderer?: (
columnHeaderRenderer?: (
refHandler: IRef<HTMLDivElement>,
resizeHandler: (verticalGuides: number[] | null) => void,
reorderingHandler: (oldIndex: number, newIndex: number, length: number) => void,
Expand All @@ -183,7 +183,7 @@ export interface ITableQuadrantStackProps extends Props {
* A callback that renders either all of or just the frozen section of the row header.
* May return undefined if the table is not attached to the DOM yet.
*/
rowHeaderCellRenderer?: (
rowHeaderRenderer?: (
refHandler: IRef<HTMLDivElement>,
resizeHandler: (verticalGuides: number[] | null) => void,
reorderingHandler: (oldIndex: number, newIndex: number, length: number) => void,
Expand Down Expand Up @@ -234,14 +234,21 @@ export interface ITableQuadrantStackProps extends Props {
* @default undefined
*/
enableColumnInteractionBar?: boolean;

/**
* Flag indicating that both the column headers (if present)
* and row headers (if present) have been rendered and mounted, including any
* custom renderers which may affect quadrant layout measurements.
*/
didHeadersMount: boolean;
}

// when there are no column headers, the header and menu element will
// confusingly collapse to zero height unless we establish this default.
const DEFAULT_COLUMN_HEADER_HEIGHT = 30;
// Used on first render of the top-left and top quadrants to avoid collapsing
// their heights to 0. originally defined in headers/_common.scss
const MIN_COL_HEADER_HEIGHT = 30;

// HACKHACK: used on first render of the top left quadrant to avoid collapsing
// its width to 0. originally defined in headers/_common.scss
// Used on first render of the top-left and left quadrants to avoid collapsing
// their widths to 0. originally defined in headers/_common.scss
const MIN_ROW_HEADER_WIDTH = 30;

// the debounce delay for updating the view on scroll. elements will be resized
Expand All @@ -263,6 +270,7 @@ const SYNC_TRIGGER_PROP_KEYS: Array<keyof ITableQuadrantStackProps> = [
"numColumns",
"numRows",
"enableColumnInteractionBar",
"didHeadersMount",
];

export class TableQuadrantStack extends AbstractComponent2<ITableQuadrantStackProps> {
Expand Down Expand Up @@ -497,12 +505,7 @@ export class TableQuadrantStack extends AbstractComponent2<ITableQuadrantStackPr
return undefined;
}

return this.props.columnHeaderCellRenderer?.(
refHandler,
resizeHandler,
reorderingHandler,
showFrozenColumnsOnly,
);
return this.props.columnHeaderRenderer?.(refHandler, resizeHandler, reorderingHandler, showFrozenColumnsOnly);
};

private renderTopQuadrantColumnHeader = (showFrozenColumnsOnly: boolean) => {
Expand All @@ -514,12 +517,7 @@ export class TableQuadrantStack extends AbstractComponent2<ITableQuadrantStackPr
return undefined;
}

return this.props.columnHeaderCellRenderer?.(
refHandler,
resizeHandler,
reorderingHandler,
showFrozenColumnsOnly,
);
return this.props.columnHeaderRenderer?.(refHandler, resizeHandler, reorderingHandler, showFrozenColumnsOnly);
};

private renderLeftQuadrantColumnHeader = (showFrozenColumnsOnly: boolean) => {
Expand All @@ -531,12 +529,7 @@ export class TableQuadrantStack extends AbstractComponent2<ITableQuadrantStackPr
return undefined;
}

return this.props.columnHeaderCellRenderer?.(
refHandler,
resizeHandler,
reorderingHandler,
showFrozenColumnsOnly,
);
return this.props.columnHeaderRenderer?.(refHandler, resizeHandler, reorderingHandler, showFrozenColumnsOnly);
};

private renderTopLeftQuadrantColumnHeader = (showFrozenColumnsOnly: boolean) => {
Expand All @@ -548,12 +541,7 @@ export class TableQuadrantStack extends AbstractComponent2<ITableQuadrantStackPr
return undefined;
}

return this.props.columnHeaderCellRenderer?.(
refHandler,
resizeHandler,
reorderingHandler,
showFrozenColumnsOnly,
);
return this.props.columnHeaderRenderer?.(refHandler, resizeHandler, reorderingHandler, showFrozenColumnsOnly);
};

// Row header
Expand All @@ -564,7 +552,7 @@ export class TableQuadrantStack extends AbstractComponent2<ITableQuadrantStackPr
return undefined;
}

return this.props.rowHeaderCellRenderer?.(
return this.props.rowHeaderRenderer?.(
refHandler,
this.handleRowResizeGuideMain,
this.handleRowsReordering,
Expand All @@ -578,7 +566,7 @@ export class TableQuadrantStack extends AbstractComponent2<ITableQuadrantStackPr
return undefined;
}

return this.props.rowHeaderCellRenderer?.(
return this.props.rowHeaderRenderer?.(
refHandler,
this.handleRowResizeGuideTop,
this.handleRowsReordering,
Expand All @@ -592,7 +580,7 @@ export class TableQuadrantStack extends AbstractComponent2<ITableQuadrantStackPr
return undefined;
}

return this.props.rowHeaderCellRenderer?.(
return this.props.rowHeaderRenderer?.(
refHandler,
this.handleRowResizeGuideLeft,
this.handleRowsReordering,
Expand All @@ -606,7 +594,7 @@ export class TableQuadrantStack extends AbstractComponent2<ITableQuadrantStackPr
return undefined;
}

return this.props.rowHeaderCellRenderer?.(
return this.props.rowHeaderRenderer?.(
refHandler,
this.handleRowResizeGuideTopLeft,
this.handleRowsReordering,
Expand Down Expand Up @@ -842,8 +830,8 @@ export class TableQuadrantStack extends AbstractComponent2<ITableQuadrantStackPr
const bottomScrollBarHeight = ScrollUtils.measureScrollBarThickness(mainScrollContainer!, "horizontal");

// ensure neither of these measurements confusingly clamps to zero height.
const adjustedColumnHeaderHeight = this.maybeIncreaseToDefaultColumnHeaderHeight(columnHeaderHeight);
const adjustedTopQuadrantHeight = this.maybeIncreaseToDefaultColumnHeaderHeight(topQuadrantHeight);
const adjustedColumnHeaderHeight = this.maybeIncreaseToMinColHeaderHeight(columnHeaderHeight);
const adjustedTopQuadrantHeight = this.maybeIncreaseToMinColHeaderHeight(topQuadrantHeight);

// Update cache: let's read now whatever values we might need later.
// prevents unnecessary reflows in the future.
Expand Down Expand Up @@ -877,10 +865,10 @@ export class TableQuadrantStack extends AbstractComponent2<ITableQuadrantStackPr
};

private maybeSetQuadrantSizes = (width: number, height: number) => {
this.maybesSetQuadrantSize(QuadrantType.LEFT, "width", width);
this.maybesSetQuadrantSize(QuadrantType.TOP, "height", height);
this.maybesSetQuadrantSize(QuadrantType.LEFT, "width", Utils.clamp(width, MIN_ROW_HEADER_WIDTH));
this.maybesSetQuadrantSize(QuadrantType.TOP, "height", Utils.clamp(height, MIN_COL_HEADER_HEIGHT));
this.maybesSetQuadrantSize(QuadrantType.TOP_LEFT, "width", Utils.clamp(width, MIN_ROW_HEADER_WIDTH));
this.maybesSetQuadrantSize(QuadrantType.TOP_LEFT, "height", height);
this.maybesSetQuadrantSize(QuadrantType.TOP_LEFT, "height", Utils.clamp(height, MIN_COL_HEADER_HEIGHT));
};

private maybesSetQuadrantSize = (quadrantType: QuadrantType, dimension: "width" | "height", value: number) => {
Expand Down Expand Up @@ -960,8 +948,8 @@ export class TableQuadrantStack extends AbstractComponent2<ITableQuadrantStackPr
}
}

private maybeIncreaseToDefaultColumnHeaderHeight(height: number) {
return height <= QUADRANT_MIN_SIZE ? DEFAULT_COLUMN_HEADER_HEIGHT : height;
private maybeIncreaseToMinColHeaderHeight(height: number) {
return height <= QUADRANT_MIN_SIZE ? MIN_COL_HEADER_HEIGHT : height;
}

// Helpers
Expand Down Expand Up @@ -1015,8 +1003,7 @@ export class TableQuadrantStack extends AbstractComponent2<ITableQuadrantStackPr
// (alas, we must force a reflow to measure the row header's "desired" width)
mainRowHeader.style.width = "auto";

const desiredRowHeaderWidth = mainRowHeader.clientWidth;
return desiredRowHeaderWidth;
return Utils.clamp(mainRowHeader.clientWidth, MIN_ROW_HEADER_WIDTH);
}
}

Expand All @@ -1029,7 +1016,7 @@ export class TableQuadrantStack extends AbstractComponent2<ITableQuadrantStackPr
// layout and are not actually bound by any fixed `height` that we set,
// so they'll grow freely to their necessary size. makes measuring easy!
const mainColumnHeader = this.quadrantRefs[QuadrantType.MAIN].columnHeader;
return mainColumnHeader == null ? 0 : mainColumnHeader.clientHeight;
return mainColumnHeader == null ? 0 : Utils.clamp(mainColumnHeader.clientHeight, MIN_COL_HEADER_HEIGHT);
}

private shouldRenderLeftQuadrants(props: ITableQuadrantStackProps = this.props) {
Expand Down
5 changes: 3 additions & 2 deletions packages/table/src/table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ export class Table extends AbstractComponent2<TableProps, TableState, TableSnaps
childrenArray,
columnIdToIndex,
columnWidths: newColumnWidths,
didHeadersMount: false,
focusedCell,
horizontalGuides: [],
isLayoutLocked: false,
Expand Down Expand Up @@ -449,7 +450,7 @@ export class Table extends AbstractComponent2<TableProps, TableState, TableSnaps
<TableQuadrantStack
bodyRef={this.refHandlers.cellContainer}
bodyRenderer={this.renderBody}
columnHeaderCellRenderer={this.renderColumnHeader}
columnHeaderRenderer={this.renderColumnHeader}
columnHeaderRef={this.refHandlers.columnHeader}
enableColumnInteractionBar={enableColumnInteractionBar}
enableRowHeader={enableRowHeader}
Expand All @@ -468,7 +469,7 @@ export class Table extends AbstractComponent2<TableProps, TableState, TableSnaps
onScroll={this.handleBodyScroll}
ref={this.refHandlers.quadrantStack}
menuRenderer={this.renderMenu}
rowHeaderCellRenderer={this.renderRowHeader}
rowHeaderRenderer={this.renderRowHeader}
rowHeaderRef={this.refHandlers.rowHeader}
scrollContainerRef={this.refHandlers.scrollContainer}
/>
Expand Down
Loading