Disabling TableView/ListView keyboard navigation and press interactions if collection is empty#3422
Conversation
| 'aria-multiselectable': state.selectionManager.selectionMode === 'multiple' ? 'true' : undefined | ||
| }, | ||
| state.isKeyboardNavigationDisabled ? {} : collectionProps, | ||
| state.collection.size === 0 || state.isKeyboardNavigationDisabled ? {} : collectionProps, |
There was a problem hiding this comment.
Opted for putting this logic here so this behavior would apply to all grid components, not just TableView.
Note that this would prevent the user from tabbing to the column headers to resize them if the TableView is empty, but I figured this was a non issue since the Table doesn't have row data to display. Perhaps we should remove the resize menu entirely for this case?
EDIT: the above isn't 100% true (the column resize buttons still have tabindex = 0) but it would be a simple enough task to hide that as well
this makes resizing, clicking on column header and other interactions all inert if the table is empty. Partially makes these changes TableView specific instead of grid wide, to confirm with team.
also fixes some small things the tests revealed like how the hidden inputs in the resizer werent disabled
|
Build successful! 🎉 |
test is skipped for now due to outstanding bug that is fixed in separate PR
|
Known issues:
|
|
Build successful! 🎉 |
| // TODO: this will affect all grids, maybe make it specific to Table via useTableState? | ||
| // something like isInteractionDisabled: state.collection.size === 0 | ||
| isDisabled: state.collection.size === 0 |
There was a problem hiding this comment.
Open to opinions here. I could make the changes in this PR to be grid wide instead of only for TableView which would make the above change valid. If we wanna only affect TableView, I can add something specific to the table state instead OR add isDisabled to useGridCell/useGridRow's props (feels iffy). A third option is that we are ok with the above change even though we are scoping this behavior to TableView, especially since TableView is the only grid that has grid cells and rows when the collection is empty
| expect(tree.queryByRole('slider')).toBeFalsy(); | ||
| }); | ||
|
|
||
| it.skip('should re-enable functionality when the table recieves items', function () { |
There was a problem hiding this comment.
As mentioned, skipped for now until #3414 goes in
|
Build successful! 🎉 |
|
Build successful! 🎉 |
reidbarber
left a comment
There was a problem hiding this comment.
Just one comment, everything else looks good
|
|
||
| return ( | ||
| <Flex direction="column"> | ||
| <ActionButton width="100px" onPress={() => setShow(show => !show)}>Toggle items</ActionButton> |
There was a problem hiding this comment.
If you toggle items in, select all, then toggle items out, the select all checkbox is shown as checked and is keyboard navigable even though it is disabled. Not sure if this is just how the story is written and a non-issue, or a separate issue.
There was a problem hiding this comment.
It remains keyboard navigable even if you undo the select all again. As soon as it has gotten focus, that's what TableView will send focus to. This happens with the Column headers as well. Toggle the items, navigate to col 3, then toggle the items off. Tab will continue to send you to Column Header 3
There was a problem hiding this comment.
Thanks for catching this, I'll probably go the route of clearing the tab index if the grid is empty. One possible issue may be if the collection doesn't properly get an updated tab index but I imagine I could override that as well if need be
|
Build successful! 🎉 |
| onAction: onCellAction ? () => onCellAction(node.key) : onAction, | ||
| // TODO: this will affect all grids, maybe make it specific to Table via useTableState? | ||
| // something like isInteractionDisabled: state.collection.size === 0 | ||
| isDisabled: state.collection.size === 0 |
There was a problem hiding this comment.
is it both non-interactive and non-navigable? or should we name the prop something else? or is that what you're suggesting in the comment?
Lets only do this for Table for now? I assume we'll be asked to do this for ListView eventually, but sounds like not yet?
There was a problem hiding this comment.
The PR also covers ListView.
Also, if the collection is empty, wouldn't there be no grid cells? I guess only in the case of tableview because of the headers?
There was a problem hiding this comment.
Yep, only TableView has grid cells when the collection is empty, CardView and TagGroup don't. Was just a bit worried that there might people using these grid hooks directly to build a table (or some other grid pattern) that wouldn't want to disable interactions when their table is empty
| }, | ||
| onChange | ||
| onChange, | ||
| disabled: isDisabled |
There was a problem hiding this comment.
rather than disabling this, shouldn't we just not render the menu button and not display the resizer on hover?
There was a problem hiding this comment.
That should work, I just felt like it would be appropriate to give the user the ability to disable a column resizer but we can add that when a use case arises.
There was a problem hiding this comment.
Nevermind, since I ran into issues here I'm going to go back to the isDisabled approach
| } | ||
|
|
||
| let _TableColumnHeaderButton = (props, ref: FocusableRef<HTMLDivElement>) => { | ||
| let {isEmpty} = useTableContext(); |
There was a problem hiding this comment.
if the table is empty, maybe we just render a TableColumnHeader instead of one with a menu?
There was a problem hiding this comment.
Seems like we need to render the TableResizeColumnHeader if it is resizable, otherwise flipping from empty to non-empty doesn't replace the in view columns with resizable columns (caching probably?)
|
One small issue I noticed: the column header cells still have |
|
@devongovett Hm, I'm not seeing that right click behavior actually. I'll still go ahead and remove the tab index when the table column headers so the previously focused key doesn't interfere with the empty TableView behavior but just wanna double check: what story/browser combo were your using? |
|
renderEmptyState in Safari. |
|
@devongovett kk good to know, maybe a old Safari bug, not seeing that on my end with the old storybook. TODO: seeing some weirdness when toggling items back and forth, resizers appear for some columns and not for others, need to investigate further. The spacing between the checkbox and the column header is too close locally too |
the columns in view arent updated from TableColumnHeaders to ResizableTableColumnHeader when going from no items to items in the empty TableView story, so I went back to disabling the resize column button instead
|
One last issue I'm digging into: useVirtualizer overrides the provided grid tabindex, means that the table itself isn't tabbable if the user had focused any of the column headers before the table became empty |
|
Build successful! 🎉 |
virtualizer will provide tabindex=-1 if the focused item is still in view which means a empty table will have tabindex -1 if the column header was focused last. We want the table to be tabbable when empty so we override this
| // Override virtualizer provided tabindex if TableView is empty so it is tabbable. | ||
| {...mergeProps(otherProps, virtualizerProps, collection.size === 0 && {tabIndex: 0})} |
There was a problem hiding this comment.
useVirtualizer will return tabindex=-1 if the focused view is still in the TableView, which is problematic for the empty case because the last focused view could be a column header prior to the TableView becoming empty and thus would still be in view. Debated putting this logic into useVirtualizer but I figured that would be too wide of a change. Specifically, in case someone was using Virtualizer to create a component that would keep some focusable views even when the collection was empty but they wanted those view to still be tabbable/didn't want the virtualizer to be tabbable
|
Build successful! 🎉 |
devongovett
left a comment
There was a problem hiding this comment.
Looks good.
One question: is there any value to focusing the collection itself when it is empty and contains a focusable element? Seems like it's kind of an extra tab stop in that case. Maybe we should do something like we do with tabs where if there are no focusable children, then the collection is focusable, otherwise not? Maybe a future improvement.
|
I mainly did it for consistency with ListView's existing behavior (aka how focus moves to the ListView itself when the collection becomes temporarily empty) and to have the announcement of the link happen separately from entering the tableview. Neither of the two are very strong reasons, and making it behave like tabs sounds like a good idea for consistency. Happy to handle that in a follow up |
|
Build successful! 🎉 |
|
Build successful! 🎉 |
|
Build successful! 🎉 |
Closes #3399
Known issues:
✅ Pull Request Checklist:
📝 Test Instructions:
Test the "renderEmptyState" TableView story and the "empty" ListView stories. You should be able to tab to the link in the illustrated message and the select all checkbox should be disabled for the TableView. Empty CardViews shouldn't work at the moment
🧢 Your Project:
RSP