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

feat: Add colspan to Table #7638

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open

feat: Add colspan to Table #7638

wants to merge 10 commits into from

Conversation

oyvinmar
Copy link

@oyvinmar oyvinmar commented Jan 20, 2025

Closes #5334, closes #5862

This change adds support for colSpan to the table. The changes can be reviewed commit by commit. The first commit adds colSpan support; the second adds aria-col-index, which makes keyboard navigation slightly simpler but adds some complexity to updating the cells with the correct column index.

Some implementation details/concerns/questions:

  • I'm unsure if this is the best way to update the grid nodes with the attributes. If there's a better way, please let me know.
  • aria-col-index is added to every cell, even in rows where colspan isn't used. I could probably look this up before adding it, but I'm worried that could be an expensive operation.
  • I've not added any tests yet (or updated documentation). I would like to add some for the keyboard delegate but want to make sure I'm on the right path first.

✅ Pull Request Checklist:

  • Included link to corresponding React Spectrum GitHub Issue.
  • Added/updated unit tests and storybook for this change (for new code or code which already has tests).
  • Filled out test instructions.
  • Updated documentation (if it already exists for this component).
  • Looked at the Accessibility Practices for this feature - Aria Practices

📝 Test Instructions:

I've provided Storybook examples, which I have used to test the changes. They should be findable by searching for «colspan».

🧢 Your Project:

We are evaluating react-aria-components for a new implementation of a table provided by our internal design system.

Copy link
Member

@snowystinger snowystinger left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR! Had a quick look, it looks like the right direction to go, I'll bring it up with the team later this week.

Some notes for the team:

Looks like VO support isn't great for colspans, announcement is only "x of n" where "n" is less than overall number of columns. It doesn't mention anything about how many columns it covers or what the title/owning column is. I tested it against the MDN docs as well, appears to be the same, so this may be expected. It behaves the same with the virtualized RSP table as well.

We'll need to do more testing with the other screen readers.

packages/@react-aria/grid/src/useGridCell.ts Outdated Show resolved Hide resolved
@nwidynski
Copy link
Contributor

nwidynski commented Jan 20, 2025

@oyvinmar I've recently worked a lot on the grid edit mode, so I'm hoping to be able to be of help with some of your questions.

I'm unsure if this is the best way to update the grid nodes with the attributes.

Usually @react-aria/collections access pattern for node props is via the collection. You can view this pattern being applied in the SelectionManager, but it would look something like state.collection.getItem(node.key)?.props.colSpan.

The value of this gets automatically populated on the first render, when the collection is built. That is also why you don't see most of the cell props inside of the GridCellProps or TableCellProps interface(s).

aria-col-index is added to every cell, even in rows where colspan isn't used. I could probably look this up before adding it, but I'm worried that could be an expensive operation.

Via the before-mentioned pattern you can trivially check for the existence of a specific prop, since only then it will exist in the prop object of the node item. This is also not a DOM operation so no worries on that front.

I would like to add some for the keyboard delegate but want to make sure I'm on the right path first.

Afaik, the colSpan attribute is only supported on td and th elements per W3C spec. From a quick check through the codebase, i can only find one reference to the column node type inside grid hooks, so it's likely useTableCell & TableKeyboardDelegate are better suited to accomodate this behavior.

EDIT: Saw that @snowystinger also provided feedback on VO support for this already, so I'm sure the core maintainers will get back to you shortly with more specific guidance. Thanks again for the PR 👍

@oyvinmar
Copy link
Author

Thanks for the fast feedback! I've changed to setting the grid node attributes in the collections and only include aria-col-index when the row has a colSpan cell (and when virtualized).

Copy link
Member

@snowystinger snowystinger left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We briefly discussed this today. Again, thank you so much for contributing this feature. It looks like this is the correct direction. Adding tests is a good next step while we review the code.

One thing we'd like to add in the logic is some validation:
num cols === sum of col spans
and throw if this isn't true

@danielbrodin
Copy link

Will this also add support for colspan on Column for cases where you want a subheading in the table with scope="colgroup"? Don't think the Column component has support for colgroup yet, but if it gets it colspan would be needed.

@snowystinger
Copy link
Member

Will this also add support for colspan on Column for cases where you want a subheading in the table with scope="colgroup"? Don't think the Column component has support for colgroup yet, but if it gets it colspan would be needed.

column group support will have to come later, we don't have an API for it yet and I'd prefer not to add more to this pr

colspan is only added on Cell in this PR, so it's not on Column

Copy link
Member

@snowystinger snowystinger left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general this looks awesome, I only have a few comments. Thanks for adding tests and doing the React Spectrum implementation.

Question to team: Should we track column keys for repetitive arrow key up/down so that you stay in the same column until you intentionally change columns?
Go to /?path=/story/tableview--table-cell-col-span-with-various-spans-example, press Tab, ArrowRight, then ArrowDown x3, you'll have focus on the cell in Col1 instead of Col2.

@@ -251,6 +253,8 @@ export function useGridCell<T, C extends GridCollection<T>>(props: GridCellProps
let gridCellProps: DOMAttributes = mergeProps(itemProps, {
role: 'gridcell',
onKeyDownCapture,
'aria-colspan': node.colspan,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can colspan match the capitalisation of the GridCellProps' colSpan?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be a fairly straightforward to change, but I think it would introduce a breaking change in table header (useTableColumnHeader): https://react-spectrum.adobe.com/react-aria/useTable.html#table-header

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, then probably we should go the other direction to match.

packages/react-aria-components/test/Table.test.js Outdated Show resolved Hide resolved
@@ -1213,6 +1257,7 @@ export const Cell = /*#__PURE__*/ createLeafComponent('cell', (props: CellProps,
<TD
{...mergeProps(filterDOMProps(props as any), gridCellProps, focusProps, hoverProps)}
{...renderProps}
colSpan={cell.colspan}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it looks like in our table hooks we check for isVirtualized as a proxy for the elementType at the moment. let's move this down into the useGridCell hook

we may expand the support to actually checking elementType later and i'd prefer that all the logic is together

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean something like adding something like this: colSpan: isVirtualized? undefined: node.colspan,? One potential issue I see with that is that it might be semantically wrong if you add virtualization to a RAC implementation. But that might be an edge case, and you would still have the aria-colspan attribute.

if (child.type === 'cell' && rowHasCellWithColSpan) {
child.colspan = child.props?.colSpan;
child.colIndex = !last ? child.index : (last.colIndex ?? last.index) + (last.colspan ?? 1);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have access to column count here, we should do the same check as we're doing in RAC, otherwise this is the message in React Spectrum

Uncaught TypeError: Cannot read properties of undefined (reading 'props')

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I tried adding it there but got a lot of failing tests before I figured out I needed to check that node is of item type 😅 .

I've added it now.

@@ -90,10 +90,6 @@ Row.getCollectionNode = function* getCollectionNode<T>(props: RowProps<T>, conte
}
});

if (cells.length !== context.columns.length) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that or we can check here again

@henkkasoft
Copy link

It's really great that this has been implemented, thank you very much! We are looking forward to start using this as soon as possible.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
5 participants