-
Notifications
You must be signed in to change notification settings - Fork 2.2k
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
[Table] TruncatedFormat popover improvements and performance tuning for dev preview #1624
Conversation
Uses off-DOM canvas text measuring to avoid forced reflows. SlowLayout component allows us to highlight potential perf issues in the dev preview.
|
||
private contentDiv: HTMLDivElement; | ||
private cachedFontString: string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we cache the font string on first use
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this will be out of date if the style changes, but async font loading will not be an issue since the fontstring will still apply once the font is loaded.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm fine moving forward with this and letting someone file a ticket if out-of-date styles become an issue during the life of the page.
); | ||
} else { | ||
return ( | ||
<span className={Classes.TABLE_TRUNCATED_POPOVER_TARGET} onClick={this.handlePopoverOpen}> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this reproduces <Popover>
's internal structure without calling any layout-triggering listeners
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👏, but can we add a comment in Popover—or somewhere—indicating that this assumption-ridden code is here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Woo woo! Left some stuff.
packages/table/preview/index.html
Outdated
@@ -40,6 +63,7 @@ | |||
flex: 1; | |||
order: 2; | |||
z-index: 2; | |||
width: 100%; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reasoning for this rule? Is it because of:
To be a layout boundary, the element must...Not have an implicit or
auto
width value
?
packages/table/preview/index.html
Outdated
@@ -52,6 +76,7 @@ | |||
.sidebar { | |||
background-color: #EBF1F5; | |||
flex-basis: 300px; | |||
flex-shrink: 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And this rule?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i had some trouble getting the preview layout to work with the slowlayoutstack. i'd like to revisit these styles and solidify how we're going to recommend creating layout boundaries.
it might even be worth adding a <LayoutBoundary>
react component that uses style attributes (to avoid any class overrides from breaking them)
@@ -34,6 +34,7 @@ import { RenderMode } from "../src/common/renderMode"; | |||
import { IRegion } from "../src/regions"; | |||
import { DenseGridMutableStore } from "./denseGridMutableStore"; | |||
import { LocalStore } from "./localStore"; | |||
import { SlowLayoutStack } from "./slowLayout"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would prefer to rename to ./slowLayoutStack
to make the export match the filename
} | ||
|
||
// const CELL_FONT_PROPERTIES_STRING = `normal normal normal normal 12px / 20px -apple-system, system-ui, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", Icons16, sans-serif`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
delete commented code
|
||
private contentDiv: HTMLDivElement; | ||
private cachedFontString: string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm fine moving forward with this and letting someone file a ticket if out-of-date styles become an issue during the life of the page.
// <Popover> will always check the content's position on update | ||
// regardless if it is open or not. This negatively affects perf due to | ||
// layout thrashing. So instead we manage the popover state ourselves | ||
// and mimic its popover target |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
eww, but so clever
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
really, this should just be done by popover. the dom measuring should not be done on update, but rather on interaction
); | ||
} else { | ||
return ( | ||
<span className={Classes.TABLE_TRUNCATED_POPOVER_TARGET} onClick={this.handlePopoverOpen}> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👏, but can we add a comment in Popover—or somewhere—indicating that this assumption-ridden code is here?
const containerWidth = parseInt(this.props.parentCellWidth, 10); | ||
const availableWidth = containerWidth - CONTAINER_PADDING; | ||
const isTruncated = contentWidth > availableWidth; | ||
this.setState({ isTruncated } as ITruncatedFormatState); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// summoning @gscshoyru for thorough review of this new code
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// appears in a puff of smoke, surrounded by chanting and candles
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
...um. Where'd height go? Why are we only measuring the width of the text itself? This doesn't actually work for any cases where we've got work wrap turned on. Did you test with that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so, uh, the only thing you can measure with canvas is width because that's all that exists on TextMetrics.
the text measurement assumes no wrapping in the text and is only useful at detecting horizontal overflow.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
...this removes any and all measuring considerations around height. Which means this is horribly broken for any cases where we have text wrapping turned on.
renderRowHeader={this.renderRowHeader} | ||
selectionModes={this.getEnabledSelectionModes()} | ||
styledRegionGroups={this.getStyledRegionGroups()} | ||
<SlowLayoutStack |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
..not that it really matters, and maybe I'm misunderstanding something -- but would it have been better to split the changes to truncated format and the changes to the preview into separate commits?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, and might still need to do that, but i'm using this slowlayoutstack to test my changes to truncatedformat.
const containerWidth = parseInt(this.props.parentCellWidth, 10); | ||
const availableWidth = containerWidth - CONTAINER_PADDING; | ||
const isTruncated = contentWidth > availableWidth; | ||
this.setState({ isTruncated } as ITruncatedFormatState); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// appears in a puff of smoke, surrounded by chanting and candles
const containerWidth = parseInt(this.props.parentCellWidth, 10); | ||
const availableWidth = containerWidth - CONTAINER_PADDING; | ||
const isTruncated = contentWidth > availableWidth; | ||
this.setState({ isTruncated } as ITruncatedFormatState); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
...um. Where'd height go? Why are we only measuring the width of the text itself? This doesn't actually work for any cases where we've got work wrap turned on. Did you test with that?
// Since we measure only the `textContent` of the cell to determine the | ||
// truncation state, we must account for the padding that is applied via CSS to | ||
// the cell. | ||
const CONTAINER_PADDING = 20; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why the change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ping @themadcreator
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Previously we had to consider the current truncation state when doing the calculation for content width because the element containing the text would have a different size if the "..." button was shown.
Now, since we're measuring with canvas, the size of the text content element is irrelevant. Instead we just need to consider the amount of padding between the parent element and the text content element.
fix tests for real this timePreview: documentation | table |
@gscshoyru @themadcreator @llorca RE: not considering parent-cell height anymore: It looks like the only side effect of that omission is that we'll occasionally show the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@themadcreator looks okay overall, but not approving yet just to make sure we have a clear story on https://github.com/palantir/blueprint/pull/1624/files#diff-820e3fef53ee313553edff37ddbf4737R19 first.
enableMultiSelection: true, | ||
enableRowReordering: false, | ||
enableRowResizing: false, | ||
enableRowSelection: true, | ||
enableSlowLayout: false, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Since we measure only the `textContent` of the cell to determine the | ||
// truncation state, we must account for the padding that is applied via CSS to | ||
// the cell. | ||
const CONTAINER_PADDING = 20; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ping @themadcreator
Reverting canvas measuring. Keeping dev preview updatesPreview: documentation | landing | table |
some test in core is failing:
|
coveragePreview: documentation | table |
function getRandomObject(propCount: number, depth = 0): object { | ||
const childPropCount = propCount; | ||
const obj: any = {}; | ||
while (propCount-- >= 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: I prefer to avoid the --
and ++
operators, because they're so sneaky and tend to make lines denser.
packages/table/src/common/utils.ts
Outdated
* | ||
* Returns a `TextMetrics` object. | ||
*/ | ||
measureElementTextContent(element: Element, fontProperties?: string | ICssFontProperties) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we revert all these utility changes?
revert utils and test changes. use for loopPreview: documentation | table |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM! I updated the PR title to help with future bookkeeping.
Reverted the TruncatedFormat measurement changes
Switches<TrunctedFormat>
to measure text with a canvas element, which does not trigger a relayout.Also adding switches to the dev preview to demonstrate how the table performs with: