-
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
Fix: Table not keeping focused cell visible #6882
Fix: Table not keeping focused cell visible #6882
Conversation
Thanks for your interest in palantir/blueprint, @filipbalucha! Before we can accept your pull request, you need to sign our contributor license agreement - just visit https://cla.palantir.com/ and follow the instructions. Once you sign, I'll automatically update this pull request. |
1c5b9fe
to
7b351a6
Compare
// we need to modify the scroll container explicitly for the viewport to shift. in so | ||
// doing, we add the size of the header elements, which are not technically part of the | ||
// "grid" concept (the grid only consists of body cells at present). |
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 is not true. scrollTop
should not be dependent on header size here:
scrolltop.mov
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.
if that's the case then I'd expect the viewportRect to not include space for the headers, re https://github.com/palantir/blueprint/pull/6882/files#diff-c2ca442c1f8367c203de64884a8526876c32365d9dd5f071887fb16183ada632R100
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.
viewportRect
does include space for the headers (it contains headers, frozen cells, and the visible "scrollable cells"). The absolute placement of the scrollable cells within the scrollable element S
is such that S.scrollTop
need not take frozen cell or header height into account. Hopefully the diagram in #6882 (comment) helps explain this a bit better.
nextScrollLeft ?? 0, | ||
nextScrollTop ?? 0, | ||
nextScrollLeft ?? viewportRect.left, | ||
nextScrollTop ?? viewportRect.top, |
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.
Unless both nextScrollLeft
and nextScrollTop
were used, we would set one value to 0 here. However, viewportRect
's top-left point is the value by which the scrollable cells have been scrolled. So, we would be scrolling this back to the first row / column (likely causing batcher to schedule the rendering of cells in that region before being corrected by some other prop 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.
I think this change makes sense. In getSnapshotBeforeUpdate
I'm seeing
// these will only be defined if they differ from viewportRect
return { nextScrollLeft, nextScrollTop };
so using the current viewportRect top/width instead of 0 sounds like a better default
I think we could merge this now if you'd like to split this little bit off
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 is my first time really diving into the table code so doing my best to review here.
Ideally I'd like to see minimal changes, ex if we can do something without introducing a new setState call, that would be preferred.
Adding to my worries is that it looks like some table tests are not running, or may only be running on Table and not Table2: https://github.com/palantir/blueprint/issues?q=is%3Aissue+is%3Aopen+table2 - and that there are no new tests introduced in this PR.
As a next step, I think it may help if you can help clarify my comments with your thoughts, and consider if we can avoid refactoring the header size into state.
nextScrollLeft ?? 0, | ||
nextScrollTop ?? 0, | ||
nextScrollLeft ?? viewportRect.left, | ||
nextScrollTop ?? viewportRect.top, |
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 think this change makes sense. In getSnapshotBeforeUpdate
I'm seeing
// these will only be defined if they differ from viewportRect
return { nextScrollLeft, nextScrollTop };
so using the current viewportRect top/width instead of 0 sounds like a better default
I think we could merge this now if you'd like to split this little bit off
(this.state.focusedCell == null || this.state.focusedCell !== newState.focusedCell) | ||
) { | ||
// Ensure controlled focused cell is scrolled into view | ||
this.scrollBodyToFocusedCell(newState.focusedCell); |
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.
it looks like focusedCell
can be provided in props which may cause this scroll call to be missed
// only set focused cell state if not specified in props
if (this.props.focusedCell == null) {
this.setState({ focusedCell });
}
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.
thoughts on adding this scrollBodyToFocusedCell
here while leaving it in the places it was already? is there no concern with calling this twice?
what are the cases this handled that are currently missed by handleFocusMove
and handleFocusMoveInternal
where this is called at the end?
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.
what are the cases this handled that are currently missed by
handleFocusMove
andhandleFocusMoveInternal
where this is called at the end?
BLUF: It helps when the focusedCell
is controlled and we do not want to rely on native Table2
navigation logic (handleFocusMove
, handleFocusMoveInternal
).
handleFocusMove
and handleFocusMoveInternal
correctly call scrollBodyToFocusedCell
. But they do this even if focusedCell
is controlled:
blueprint/packages/table/src/table2Utils.ts
Lines 78 to 85 in f2e9d6e
if (enableFocusedCell != null) { | |
hotkeys.push( | |
{ | |
combo: "left", | |
group: "Table", | |
label: "Move focus cell left", | |
onKeyDown: hotkeysImpl.handleFocusMoveLeft, | |
}, |
Now, the table should not use its own navigation mechanism when focusedCell
is controlled.
In our case, navigation logic cannot rely on handleFocusMove
or handleFocusMoveInternal
, and so our controlled focusedCell
would not get scrolled into view.
thoughts on adding this scrollBodyToFocusedCell here while leaving it in the places it was already?
Done!
is there no concern with calling this twice?
Assuming ref-consistency of consumers, this should early exit without calling scrollBodyToFocusedCell
, so I'm not too concerned.
// we need to modify the scroll container explicitly for the viewport to shift. in so | ||
// doing, we add the size of the header elements, which are not technically part of the | ||
// "grid" concept (the grid only consists of body cells at present). |
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.
if that's the case then I'd expect the viewportRect to not include space for the headers, re https://github.com/palantir/blueprint/pull/6882/files#diff-c2ca442c1f8367c203de64884a8526876c32365d9dd5f071887fb16183ada632R100
: this.rowHeaderElement.clientWidth; | ||
|
||
this.scrollContainerElement.scrollLeft = nextScrollLeft + leftCorrection; | ||
this.scrollContainerElement.scrollLeft = nextScrollLeft; |
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.
the change here removing the "correction" looks like the scrollContainer and viewportRect will have the same scroll top/left now - which seems to be different than what https://github.com/palantir/blueprint/pull/6882/files#diff-8cd23c90b1959192d311f6112803042130c4c7709017e6cac6fd991ea89221beR313-R318 is saying
7bbc491
to
3f64d03
Compare
3f64d03
to
6e284f1
Compare
@evansjohnson This is now ready for another look. I have:
|
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.
going to give final review on this one to @ggdouglas but leaving some last comments for consideration - sorry for the delayed review on this one
newState.focusedCell != null && | ||
(this.state.focusedCell == null || this.state.focusedCell !== newState.focusedCell) | ||
) { | ||
this.scrollBodyToFocusedCell(newState.focusedCell); |
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.
is it really true that we always want to scroll the table to the focused cell on any state update? it seems like maybe this should be happening as a more targeted change
ideally we can add this call to the specific internal places needed rather than just assuming this is fine to do on any state update
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.
is it really true that we always want to scroll the table to the focused cell on any state update
Totally appreciate the concern. In short, yes.
The focused cell is a UI metaphor for the centre point of user interaction in the table. By that logic, it should always be visible. That's what Excel does, for instance, with the exception of being scrolled away:
excel.mov
For full disclosure, not keeping the focused cell in view is precisely what brought my team to this issue. The lack of alignment in the presence of headers and frozen rows and columns was a side-discovery made while working on this.
}; | ||
}; | ||
|
||
private getColumnHeaderHeight = (): number => { |
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.
my preference would be to merge all no-op refactors like this in a setup PR so we can really be reviewing a minimal diff 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.
I actually tried putting up a PR for noops but they are so minimal they should hardly be a distractor. For instance, getColumnHeaderHeight
here just encapsulates the implementation from elsewhere in the class.
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.
Having tested this locally and reviewed the work/conversations here in this PR, I'm satisfied that this meets the bar. Approving so that we can merge this fix in.
Thanks @evansjohnson and @ggdouglas for reviewing! |
Generate changelog in
|
Invalidated by push of 90587a2
Fixes #1609. Partially addresses #5114.
Checklist
Changes proposed in this pull request:
Reviewers should focus on:
Screenshot
Before:
before.mov
After:
after.mov
Not addressed in this PR
Polishing interactions with frozen cells. The scrollable part of the table should only scroll if the frozen cell would otherwise not be visible. I file a FR after this merges and maybe FLUP.
frozen.cells.mov
getColumnIndicesInRect
operates on viewport rect but does not ignore headers or take frozen cells into account; this might be feeding into Table Whites Out With Mouse Scroll Greater Than 3 Rows #4780 and potentially contaminating the response ofonVisibleCellsChange
. Again, this is a side observation rather than a regression.