Conversation
# Conflicts: # packages/@react-aria/table/package.json # yarn.lock
| } | ||
|
|
||
| const editableCell = style<CellRenderProps & S2TableProps & {isDivider: boolean, selectionMode?: 'none' | 'single' | 'multiple', isSaving?: boolean}>({ | ||
| const expandButton = style<ExpandableRowChevronProps>({ |
There was a problem hiding this comment.
Copied these styles from TreeView. There is no design for nested table rows in S2 yet.
|
Build successful! 🎉 |
| parentKey: parentNode ? parentNode.key : null, | ||
| value: partialNode.value ?? null, | ||
| level: parentNode ? parentNode.level + 1 : 0, | ||
| level: (parentNode?.level ?? 0) + (parentNode?.type === 'item' ? 1 : 0), |
There was a problem hiding this comment.
Mirrors logic in new collections. We should only increase the level if the parent node is an item. Will this break anything?
There was a problem hiding this comment.
I don't think it will, this should only affect nodes like items in sections or rows in tables, the latter of which you've handled and the former which doesn't translate the level prop into anything aria related I think
| getDropOperation(e) { | ||
| let {target, isInternal, draggingKeys} = e; | ||
|
|
||
| // Prevent dropping items onto themselves or their descendants |
There was a problem hiding this comment.
Move from TreeView to here so it can be shared with TableView. Is there any case where we wouldn't want this?
There was a problem hiding this comment.
nah, I don't think we'd ever want to support dropping something into itself or into its own descendant
| // Clone row node and its children so modifications to the node for treegrid specific values aren't applied on the nodes provided | ||
| // to TableCollection. Index, level, and parent keys are all changed to reflect a flattened row structure rather than the treegrid structure | ||
| // values automatically calculated via CollectionBuilder | ||
| let visitNode = (node: GridNode<T>) => { |
There was a problem hiding this comment.
We don't need update the nodes when we flatten them, just need to collect them into an array. We rely on the nodes having their original properties, not their flattened ones. This matches how new collections works.
snowystinger
left a comment
There was a problem hiding this comment.
In https://reactspectrum.blob.core.windows.net/reactspectrum/df4fce641a37f6508e9edc5cccbff956ed1270a0/storybook-s2/index.html?path=/story/tableview--table-with-nested-rows
If I ArrowRight open both rows, the entire table scrolls to the right when i open the second one.
This expand button doesn't do anything? https://reactspectrum.blob.core.windows.net/reactspectrum/df4fce641a37f6508e9edc5cccbff956ed1270a0/storybook/index.html?path=/story/react-aria-components-table--table-nested-rows&providerSwitcher-express=false
Need a story with disabled keys that are the nested rows so i can check disabledBehaviour 'all' | 'selection'
Will continue reviewing soon
| } | ||
|
|
||
| if (idScope != null) { | ||
| if (idScope != null && rendered.props.id == null) { |
There was a problem hiding this comment.
when does this happen? was it a bug we had not keeping the id as a key?
There was a problem hiding this comment.
Nested rows without an explicit id would have an idScope prepended for every level. Since the collection is flattened anyways I suppose it makes sense to skip that.
There was a problem hiding this comment.
yeah this was meant to scope the ids of the cells within a row so you can loop over the columns multiple times and not get duplicates. We don't want that for rows. If an explicit id is given, we should respect that. That was the change here. I was also considering moving this scoping into Cell itself so it would only apply there. wdyt?
There was a problem hiding this comment.
I think moving this into Cell only makes sense, I can't really think of any other cases where we'd also want to do this scoping
There was a problem hiding this comment.
will have to think about how to do this. createLeafComponent doesn't really provide a way to add that logic right now. maybe we could do it by creating a wrapper component or something.
| }), [keyboardDelegate, state.collection, state.disabledKeys, disabledBehavior, ref, direction, collator, layoutDelegate, layout]); | ||
| let id = useId(props.id); | ||
| gridIds.set(state, id); | ||
| gridIds.set(state as TableState<T>, id); |
There was a problem hiding this comment.
safe cast? what's the issue here?
There was a problem hiding this comment.
has to do with backward compatibility with the unstable version from v3. TreeGridState supports expandedKeys="all" but we don't have that in TableState.
| }; | ||
|
|
||
| if (isVirtualized && !(tableNestedRows() && 'expandedKeys' in state)) { | ||
| if (isVirtualized && state.treeColumn == null) { |
There was a problem hiding this comment.
bit of a strange check, could we put it behind some other state property? like state.isATreeRoot() or something that makes more sense
There was a problem hiding this comment.
hmm why is it strange? if there is a tree column it is a tree grid.
| delete rowProps['aria-rowindex']; | ||
| } | ||
|
|
||
| let isExpanded = state.treeColumn != null && (state.expandedKeys === 'all' || state.expandedKeys.has(node.key)); |
There was a problem hiding this comment.
no expandedKeys === 'all' i thought? that's what we decided for tree, is this just backwards compat?
There was a problem hiding this comment.
yes it was there before. I left it so as not to break v3.
| let treeNode = state.keyMap.get(node.key); | ||
| let expandButtonProps: AriaButtonProps = {}; | ||
| if (state.treeColumn != null) { | ||
| let treeNode = state.collection.getItem(node.key); |
There was a problem hiding this comment.
i assume this works with old collections given that tests pass?
There was a problem hiding this comment.
what specifically are you concerned about? getItem existed before
| <Cell>Games</Cell> | ||
| <Cell>Folder</Cell> | ||
| <Cell>6/7/2023</Cell> | ||
| <Row id="mario"> |
There was a problem hiding this comment.
I assume order matters, and if someone did this instead, that would break?
<Row id="games">
<Cell>Games</Cell>
<Cell>Folder</Cell>
<Row id="mario">
</Row>
<Cell>6/7/2023</Cell>
There was a problem hiding this comment.
yes order is important.
|
|
||
| export const TableWithNestedRows: StoryObj<typeof TableView> = { | ||
| render: (args) => ( | ||
| <TableView aria-label="Files" treeColumn="name" {...args} styles={style({width: 700, height: 320})}> |
There was a problem hiding this comment.
what happens if a cell that is in the treeColumn has a colSpan of more than 1? Or if the cell in a row is included but not the first cell of a colSpan?
There was a problem hiding this comment.
there are ways to mess it up for yourself, yes. you could also not render the tree column. All this does is two things:
- Make it behave as a tree grid
- Provide the
isTreeColumnrender prop to cells if that cell's column key matches thetreeColumn. If you don't render such a cell then this won't happen.
yarn.lock
Outdated
| linkType: soft | ||
|
|
||
| "@react-aria/button@npm:^3.14.5, @react-aria/button@workspace:packages/@react-aria/button": | ||
| "@react-aria/button@npm:^3.14.4, @react-aria/button@npm:^3.14.5, @react-aria/button@workspace:packages/@react-aria/button": |
There was a problem hiding this comment.
| "@react-aria/button@npm:^3.14.4, @react-aria/button@npm:^3.14.5, @react-aria/button@workspace:packages/@react-aria/button": | |
| "@react-aria/button@npm:^3.14.5, @react-aria/button@workspace:packages/@react-aria/button": |
I think the version in the package.json just needs to be updated after the release
| export function useTableState<T extends object>(props: TableStateProps<T>): TableState<T> { | ||
| let [isKeyboardNavigationDisabled, setKeyboardNavigationDisabled] = useState(false); | ||
| let {selectionMode = 'none', showSelectionCheckboxes, showDragButtons} = props; | ||
| let {selectionMode = 'none', showSelectionCheckboxes, showDragButtons, treeColumn = null} = props; |
There was a problem hiding this comment.
I'm wondering whether we could traverse the collection instead to look for an isTreeColumn prop on a column node. A bit of a bummer to have isRowHeader match the compositional API but then carry treeColumn as a prop on the parent.
There was a problem hiding this comment.
the reason for this is that there can only be one tree column, but there can be many row headers. It would be would be harder to enforce that there is only one with isTreeColumn.
| } as const; | ||
|
|
||
| const cell = style<CellRenderProps & S2TableProps & {isDivider: boolean}>({ | ||
| const treeColumnStyles = { |
There was a problem hiding this comment.
Should/can we add a height transition like we have in Disclosure?
There was a problem hiding this comment.
it's hard because the rows are flattened, and at least in non-virtualized tables, you can't add a wrapper div (that's not valid in an HTML table). not sure if there's another way to achieve that...
| let {dragAndDropHooks, dragState, dropState} = useContext(DragAndDropContext); | ||
| let {isVirtualized, CollectionBranch} = useContext(CollectionRendererContext); | ||
| let {rowProps, ...states} = useTableRow( | ||
| let {rowProps, expandButtonProps, ...states} = useTableRow( |
There was a problem hiding this comment.
If a row has no selection and no provided actions, I would kind of expect clicking anywhere on the row to expand it by default.
There was a problem hiding this comment.
yeah we can discuss that. The APG example doesn't do that, but we do it in TreeView.
| [ButtonContext, { | ||
| slots: { | ||
| [DEFAULT_SLOT]: {}, | ||
| chevron: expandButtonProps, |
There was a problem hiding this comment.
Super-nit, but if you expand an item and one of it's children was previously expanded, I think the child's chevron should not animate.
child-expansion.mov
There was a problem hiding this comment.
huh, must be due to virtualizer re-using the element from a different row or something
| on external state (e.g. `columns` in this example).</Content> | ||
| </InlineAlert> | ||
|
|
||
| ### Nested rows |
There was a problem hiding this comment.
I like the name "Expandable rows" better. I feel like nested rows doesn't imply that there is an expansion feature. You could theoretically have/want nested rows that don't expand/collapse.
LFDanLu
left a comment
There was a problem hiding this comment.
initial review, still need to do some testing/look at the rest but figured I'd do a dive into some of the open questions
| } | ||
|
|
||
| if (idScope != null) { | ||
| if (idScope != null && rendered.props.id == null) { |
There was a problem hiding this comment.
I think moving this into Cell only makes sense, I can't really think of any other cases where we'd also want to do this scoping
| ((getLastItem(state.collection.body.childNodes) as GridNode<T>)?.indexOfType ?? 0) + 1 | ||
| 'aria-level': treeNode.level + 1, | ||
| 'aria-posinset': treeNode.index - (isParentBody ? 0 : state.collection.columnCount) + 1, | ||
| 'aria-setsize': lastSibling.index - (isParentBody ? 0 : state.collection.columnCount) + 1 |
There was a problem hiding this comment.
the aria-setsize seems to be too large by 1 in S2. https://reactspectrum.blob.core.windows.net/reactspectrum/df4fce641a37f6508e9edc5cccbff956ed1270a0/storybook-s2/index.html?path=/story/tableview--table-with-nested-rows shows a aria-setsize=5 on each top level row when there are only 4 rows in the table body
There was a problem hiding this comment.
did some more digging here, this seems to be due to the loader element that we always include in the S2 table implementation. A bit unfortunate, but I guess we could either make sure to never count the loader row in the aria-rowcount/aria-setsize calculations? Not a blocker for this PR since it was actually already like this apparently.
There was a problem hiding this comment.
good catch. I think I fixed it by traversing backwards to find the last row. This assumes the loader is at the end though. If we want to support other positions it'll be more complicated and performance intensive.
| parentKey: parentNode ? parentNode.key : null, | ||
| value: partialNode.value ?? null, | ||
| level: parentNode ? parentNode.level + 1 : 0, | ||
| level: (parentNode?.level ?? 0) + (parentNode?.type === 'item' ? 1 : 0), |
There was a problem hiding this comment.
I don't think it will, this should only affect nodes like items in sections or rows in tables, the latter of which you've handled and the former which doesn't translate the level prop into anything aria related I think
| getDropOperation(e) { | ||
| let {target, isInternal, draggingKeys} = e; | ||
|
|
||
| // Prevent dropping items onto themselves or their descendants |
There was a problem hiding this comment.
nah, I don't think we'd ever want to support dropping something into itself or into its own descendant
LFDanLu
left a comment
There was a problem hiding this comment.
refactored logic looks good to me, approving for testing
|
|
||
| export const TableNestedRows: TableStory = (args) => { | ||
| return ( | ||
| <Table aria-label="Files" selectionMode="multiple" {...args}> |
There was a problem hiding this comment.
nit: missing the treeColumn prop here, story doesn't actually expand/collapse
| ((getLastItem(state.collection.body.childNodes) as GridNode<T>)?.indexOfType ?? 0) + 1 | ||
| 'aria-level': treeNode.level + 1, | ||
| 'aria-posinset': treeNode.index - (isParentBody ? 0 : state.collection.columnCount) + 1, | ||
| 'aria-setsize': lastSibling.index - (isParentBody ? 0 : state.collection.columnCount) + 1 |
There was a problem hiding this comment.
did some more digging here, this seems to be due to the loader element that we always include in the S2 table implementation. A bit unfortunate, but I guess we could either make sure to never count the loader row in the aria-rowcount/aria-setsize calculations? Not a blocker for this PR since it was actually already like this apparently.
reidbarber
left a comment
There was a problem hiding this comment.
I'm good to get this in for testing.
|
Build successful! 🎉 |
## API Changes
react-aria-components/react-aria-components:Table Table {
aria-describedby?: string
aria-details?: string
aria-label?: string
aria-labelledby?: string
children?: ReactNode
className?: ClassNameOrFunction<TableRenderProps> = 'react-aria-Table'
+ defaultExpandedKeys?: Iterable<Key>
defaultSelectedKeys?: 'all' | Iterable<Key>
disabledBehavior?: DisabledBehavior = "all"
disabledKeys?: Iterable<Key>
disallowEmptySelection?: boolean
dragAndDropHooks?: DragAndDropHooks
escapeKeyBehavior?: 'clearSelection' | 'none' = 'clearSelection'
+ expandedKeys?: Iterable<Key>
+ onExpandedChange?: (Set<Key>) => any
onRowAction?: (Key) => void
onSelectionChange?: (Selection) => void
onSortChange?: (SortDescriptor) => any
render?: DOMRenderFunction<keyof React.JSX.IntrinsicElements, TableRenderProps>
selectedKeys?: 'all' | Iterable<Key>
selectionBehavior?: SelectionBehavior = "toggle"
selectionMode?: SelectionMode
shouldSelectOnPressUp?: boolean
slot?: string | null
sortDescriptor?: SortDescriptor
style?: StyleOrFunction<TableRenderProps>
+ treeColumn?: Key
}/react-aria-components:Row Row <T extends {}> {
children?: ReactNode | ({}) => ReactElement
className?: ClassNameOrFunction<RowRenderProps> = 'react-aria-Row'
columns?: Iterable<{}>
dependencies?: ReadonlyArray<any>
download?: boolean | string
+ hasChildItems?: boolean
href?: Href
hrefLang?: string
id?: Key
isDisabled?: boolean
onClick?: (MouseEvent<FocusableElement>) => void
onHoverChange?: (boolean) => void
onHoverEnd?: (HoverEvent) => void
onHoverStart?: (HoverEvent) => void
onPress?: (PressEvent) => void
onPressChange?: (boolean) => void
onPressEnd?: (PressEvent) => void
onPressStart?: (PressEvent) => void
onPressUp?: (PressEvent) => void
ping?: string
referrerPolicy?: HTMLAttributeReferrerPolicy
rel?: string
render?: DOMRenderFunction<keyof React.JSX.IntrinsicElements, RowRenderProps>
routerOptions?: RouterOptions
style?: StyleOrFunction<RowRenderProps>
target?: HTMLAttributeAnchorTarget
textValue?: string
value?: {}
}/react-aria-components:TableProps TableProps {
aria-describedby?: string
aria-details?: string
aria-label?: string
aria-labelledby?: string
children?: ReactNode
className?: ClassNameOrFunction<TableRenderProps> = 'react-aria-Table'
+ defaultExpandedKeys?: Iterable<Key>
defaultSelectedKeys?: 'all' | Iterable<Key>
disabledBehavior?: DisabledBehavior = "all"
disabledKeys?: Iterable<Key>
disallowEmptySelection?: boolean
dragAndDropHooks?: DragAndDropHooks
escapeKeyBehavior?: 'clearSelection' | 'none' = 'clearSelection'
+ expandedKeys?: Iterable<Key>
+ onExpandedChange?: (Set<Key>) => any
onRowAction?: (Key) => void
onSelectionChange?: (Selection) => void
onSortChange?: (SortDescriptor) => any
render?: DOMRenderFunction<keyof React.JSX.IntrinsicElements, TableRenderProps>
selectedKeys?: 'all' | Iterable<Key>
selectionBehavior?: SelectionBehavior = "toggle"
selectionMode?: SelectionMode
shouldSelectOnPressUp?: boolean
slot?: string | null
sortDescriptor?: SortDescriptor
style?: StyleOrFunction<TableRenderProps>
+ treeColumn?: Key
}/react-aria-components:RowProps RowProps <T> {
children?: ReactNode | (T) => ReactElement
className?: ClassNameOrFunction<RowRenderProps> = 'react-aria-Row'
columns?: Iterable<T>
dependencies?: ReadonlyArray<any>
download?: boolean | string
+ hasChildItems?: boolean
href?: Href
hrefLang?: string
id?: Key
isDisabled?: boolean
onClick?: (MouseEvent<FocusableElement>) => void
onHoverChange?: (boolean) => void
onHoverEnd?: (HoverEvent) => void
onHoverStart?: (HoverEvent) => void
onPress?: (PressEvent) => void
onPressChange?: (boolean) => void
onPressEnd?: (PressEvent) => void
onPressStart?: (PressEvent) => void
onPressUp?: (PressEvent) => void
ping?: string
referrerPolicy?: HTMLAttributeReferrerPolicy
rel?: string
render?: DOMRenderFunction<keyof React.JSX.IntrinsicElements, RowRenderProps>
routerOptions?: RouterOptions
style?: StyleOrFunction<RowRenderProps>
target?: HTMLAttributeAnchorTarget
textValue?: string
value?: T
}/react-aria-components:RowRenderProps RowRenderProps {
allowsDragging?: boolean
+ hasChildItems: boolean
id?: Key
isDisabled: boolean
isDragging?: boolean
isDropTarget?: boolean
+ isExpanded: boolean
isFocusVisible: boolean
isFocusVisibleWithin: boolean
isFocused: boolean
isHovered: boolean
isPressed: boolean
isSelected: boolean
+ level: number
selectionBehavior: SelectionBehavior
selectionMode: SelectionMode
}/react-aria-components:CellRenderProps CellRenderProps {
columnIndex?: number | null
+ hasChildItems: boolean
id?: Key
+ isDisabled: boolean
+ isExpanded: boolean
isFocusVisible: boolean
isFocused: boolean
isHovered: boolean
isPressed: boolean
isSelected: boolean
+ isTreeColumn: boolean
+ level: number
}/react-aria-components:TableState TableState <T> {
collection: TableCollection<T>
disabledKeys: Set<Key>
+ expandedKeys: Set<Key>
isKeyboardNavigationDisabled: boolean
selectionManager: SelectionManager
setKeyboardNavigationDisabled: (boolean) => void
showSelectionCheckboxes: boolean
sort: (Key, 'ascending' | 'descending') => void
sortDescriptor: SortDescriptor | null
+ toggleKey: (Key) => void
+ treeColumn: Key | null
}@react-aria/test-utils/@react-aria/test-utils:TableTester TableTester {
cells: ({
element?: HTMLElement
}) => Array<HTMLElement>
columns: Array<HTMLElement>
constructor: (TableTesterOpts) => void
findCell: ({
text: string
}) => HTMLElement
findRow: ({
rowIndexOrText: number | string
}) => HTMLElement
rowGroups: Array<HTMLElement>
rowHeaders: Array<HTMLElement>
rows: Array<HTMLElement>
selectedRows: Array<HTMLElement>
setInteractionType: (UserOpts['interactionType']) => void
table: HTMLElement
+ toggleRowExpansion: (TableToggleExpansionOpts) => Promise<void>
toggleRowSelection: (TableToggleRowOpts) => Promise<void>
toggleSelectAll: ({
interactionType?: UserOpts['interactionType']
}) => Promise<void>
triggerColumnHeaderAction: (TableColumnHeaderActionOpts) => Promise<void>
triggerRowAction: (TableRowActionOpts) => Promise<void>
}@react-spectrum/s2/@react-spectrum/s2:TableView TableView {
UNSAFE_className?: UnsafeClassName
UNSAFE_style?: CSSProperties
aria-describedby?: string
aria-details?: string
aria-label?: string
aria-labelledby?: string
children?: ReactNode
+ defaultExpandedKeys?: Iterable<Key>
defaultSelectedKeys?: 'all' | Iterable<Key>
density?: 'compact' | 'spacious' | 'regular' = 'regular'
disabledBehavior?: DisabledBehavior = "all"
disabledKeys?: Iterable<Key>
disallowEmptySelection?: boolean
escapeKeyBehavior?: 'clearSelection' | 'none' = 'clearSelection'
+ expandedKeys?: Iterable<Key>
id?: string
isQuiet?: boolean
loadingState?: LoadingState
onAction?: (Key) => void
+ onExpandedChange?: (Set<Key>) => any
onLoadMore?: () => any
onResize?: (Map<Key, ColumnSize>) => void
onResizeEnd?: (Map<Key, ColumnSize>) => void
onResizeStart?: (Map<Key, ColumnSize>) => void
onSelectionChange?: (Selection) => void
onSortChange?: (SortDescriptor) => any
overflowMode?: 'wrap' | 'truncate' = 'truncate'
renderActionBar?: ('all' | Set<Key>) => ReactElement
selectedKeys?: 'all' | Iterable<Key>
selectionMode?: SelectionMode
shouldSelectOnPressUp?: boolean
slot?: string | null
sortDescriptor?: SortDescriptor
styles?: StylesPropWithHeight
+ treeColumn?: Key
}/@react-spectrum/s2:TableViewProps TableViewProps {
UNSAFE_className?: UnsafeClassName
UNSAFE_style?: CSSProperties
aria-describedby?: string
aria-details?: string
aria-label?: string
aria-labelledby?: string
children?: ReactNode
+ defaultExpandedKeys?: Iterable<Key>
defaultSelectedKeys?: 'all' | Iterable<Key>
density?: 'compact' | 'spacious' | 'regular' = 'regular'
disabledBehavior?: DisabledBehavior = "all"
disabledKeys?: Iterable<Key>
disallowEmptySelection?: boolean
escapeKeyBehavior?: 'clearSelection' | 'none' = 'clearSelection'
+ expandedKeys?: Iterable<Key>
id?: string
isQuiet?: boolean
loadingState?: LoadingState
onAction?: (Key) => void
+ onExpandedChange?: (Set<Key>) => any
onLoadMore?: () => any
onResize?: (Map<Key, ColumnSize>) => void
onResizeEnd?: (Map<Key, ColumnSize>) => void
onResizeStart?: (Map<Key, ColumnSize>) => void
onSelectionChange?: (Selection) => void
onSortChange?: (SortDescriptor) => any
overflowMode?: 'wrap' | 'truncate' = 'truncate'
renderActionBar?: ('all' | Set<Key>) => ReactElement
selectedKeys?: 'all' | Iterable<Key>
selectionMode?: SelectionMode
shouldSelectOnPressUp?: boolean
slot?: string | null
sortDescriptor?: SortDescriptor
styles?: StylesPropWithHeight
+ treeColumn?: Key
}@react-spectrum/table/@react-spectrum/table:TableView TableView <T extends {}> {
UNSAFE_className?: string
UNSAFE_style?: CSSProperties
alignSelf?: Responsive<'auto' | 'normal' | 'start' | 'end' | 'center' | 'flex-start' | 'flex-end' | 'self-start' | 'self-end' | 'stretch'>
aria-describedby?: string
aria-details?: string
aria-label?: string
aria-labelledby?: string
bottom?: Responsive<DimensionValue>
- children: [ReactElement<TableHeaderProps<{}>>, ReactElement<TableBodyProps<{}>>]
+ children: [ReactElement<TableHeaderProps<T>>, ReactElement<TableBodyProps<T>>]
defaultSelectedKeys?: 'all' | Iterable<Key>
density?: 'compact' | 'regular' | 'spacious' = 'regular'
disabledBehavior?: DisabledBehavior = "selection"
disabledKeys?: Iterable<Key>
dragAndDropHooks?: DragAndDropHooks<NoInfer<{}>>['dragAndDropHooks']
end?: Responsive<DimensionValue>
escapeKeyBehavior?: 'clearSelection' | 'none' = 'clearSelection'
flex?: Responsive<string | number | boolean>
flexBasis?: Responsive<number | string>
flexGrow?: Responsive<number>
flexShrink?: Responsive<number>
gridArea?: Responsive<string>
gridColumn?: Responsive<string>
gridColumnEnd?: Responsive<string>
gridColumnStart?: Responsive<string>
gridRow?: Responsive<string>
gridRowEnd?: Responsive<string>
gridRowStart?: Responsive<string>
height?: Responsive<DimensionValue>
id?: string
isHidden?: Responsive<boolean>
isQuiet?: boolean
justifySelf?: Responsive<'auto' | 'normal' | 'start' | 'end' | 'flex-start' | 'flex-end' | 'self-start' | 'self-end' | 'center' | 'left' | 'right' | 'stretch'>
left?: Responsive<DimensionValue>
margin?: Responsive<DimensionValue>
marginBottom?: Responsive<DimensionValue>
marginEnd?: Responsive<DimensionValue>
marginStart?: Responsive<DimensionValue>
marginTop?: Responsive<DimensionValue>
marginX?: Responsive<DimensionValue>
marginY?: Responsive<DimensionValue>
maxHeight?: Responsive<DimensionValue>
maxWidth?: Responsive<DimensionValue>
minHeight?: Responsive<DimensionValue>
minWidth?: Responsive<DimensionValue>
onAction?: (Key) => void
onResize?: (Map<Key, ColumnSize>) => void
onResizeEnd?: (Map<Key, ColumnSize>) => void
onResizeStart?: (Map<Key, ColumnSize>) => void
onSelectionChange?: (Selection) => void
onSortChange?: (SortDescriptor) => any
order?: Responsive<number>
overflowMode?: 'wrap' | 'truncate' = 'truncate'
position?: Responsive<'static' | 'relative' | 'absolute' | 'fixed' | 'sticky'>
renderEmptyState?: () => JSX.Element
right?: Responsive<DimensionValue>
selectedKeys?: 'all' | Iterable<Key>
selectionMode?: SelectionMode
selectionStyle?: 'checkbox' | 'highlight'
shouldSelectOnPressUp?: boolean
sortDescriptor?: SortDescriptor
start?: Responsive<DimensionValue>
top?: Responsive<DimensionValue>
width?: Responsive<DimensionValue>
zIndex?: Responsive<number>
}@react-spectrum/test-utils/@react-spectrum/test-utils:TableTester TableTester {
cells: ({
element?: HTMLElement
}) => Array<HTMLElement>
columns: Array<HTMLElement>
constructor: (TableTesterOpts) => void
findCell: ({
text: string
}) => HTMLElement
findRow: ({
rowIndexOrText: number | string
}) => HTMLElement
rowGroups: Array<HTMLElement>
rowHeaders: Array<HTMLElement>
rows: Array<HTMLElement>
selectedRows: Array<HTMLElement>
setInteractionType: (UserOpts['interactionType']) => void
table: HTMLElement
+ toggleRowExpansion: (TableToggleExpansionOpts) => Promise<void>
toggleRowSelection: (TableToggleRowOpts) => Promise<void>
toggleSelectAll: ({
interactionType?: UserOpts['interactionType']
}) => Promise<void>
triggerColumnHeaderAction: (TableColumnHeaderActionOpts) => Promise<void>
triggerRowAction: (TableRowActionOpts) => Promise<void>
}@react-stately/table/@react-stately/table:TableState TableState <T> {
collection: TableCollection<T>
disabledKeys: Set<Key>
+ expandedKeys: Set<Key>
isKeyboardNavigationDisabled: boolean
selectionManager: SelectionManager
setKeyboardNavigationDisabled: (boolean) => void
showSelectionCheckboxes: boolean
sort: (Key, 'ascending' | 'descending') => void
sortDescriptor: SortDescriptor | null
+ toggleKey: (Key) => void
+ treeColumn: Key | null
}/@react-stately/table:TableStateProps TableStateProps <T> {
allowDuplicateSelectionEvents?: boolean
children?: [ReactElement<TableHeaderProps<T>>, ReactElement<TableBodyProps<T>>]
collection?: TableCollection<T>
+ defaultExpandedKeys?: Iterable<Key>
defaultSelectedKeys?: 'all' | Iterable<Key>
disabledBehavior?: DisabledBehavior
disabledKeys?: Iterable<Key>
disallowEmptySelection?: boolean
+ expandedKeys?: Iterable<Key>
+ onExpandedChange?: (Set<Key>) => any
onSelectionChange?: (Selection) => void
onSortChange?: (SortDescriptor) => any
selectedKeys?: 'all' | Iterable<Key>
selectionBehavior?: SelectionBehavior
selectionMode?: SelectionMode
showSelectionCheckboxes?: boolean
sortDescriptor?: SortDescriptor
+ treeColumn?: Key
}/@react-stately/table:TreeGridState TreeGridState <T> {
collection: TableCollection<T>
disabledKeys: Set<Key>
expandedKeys: 'all' | Set<Key>
isKeyboardNavigationDisabled: boolean
keyMap: Map<Key, GridNode<T>>
selectionManager: SelectionManager
setKeyboardNavigationDisabled: (boolean) => void
showSelectionCheckboxes: boolean
sort: (Key, 'ascending' | 'descending') => void
sortDescriptor: SortDescriptor | null
toggleKey: (Key) => void
+ treeColumn: Key | null
userColumnCount: number
}/@react-stately/table:TreeGridStateProps TreeGridStateProps <T> {
UNSTABLE_defaultExpandedKeys?: 'all' | Iterable<Key>
UNSTABLE_expandedKeys?: 'all' | Iterable<Key>
UNSTABLE_onExpandedChange?: (Set<Key>) => any
allowDuplicateSelectionEvents?: boolean
children?: [ReactElement<TableHeaderProps<T>>, ReactElement<TableBodyProps<T>>]
+ defaultExpandedKeys?: Iterable<Key>
defaultSelectedKeys?: 'all' | Iterable<Key>
disabledBehavior?: DisabledBehavior
disabledKeys?: Iterable<Key>
disallowEmptySelection?: boolean
+ expandedKeys?: Iterable<Key>
+ onExpandedChange?: (Set<Key>) => any
onSelectionChange?: (Selection) => void
onSortChange?: (SortDescriptor) => any
selectedKeys?: 'all' | Iterable<Key>
selectionBehavior?: SelectionBehavior
selectionMode?: SelectionMode
showSelectionCheckboxes?: boolean
sortDescriptor?: SortDescriptor
+ treeColumn?: Key
} |
Adds support for nested table rows to React Aria Components and S2. The new
treeColumnprop designates a column key as the one with hierarchical data. Render a<Button slot="chevron">within the cells in this column to allow expanding and collapsing. Several new render props are added to rows and cells to provide the level, whether the row has child rows, whether the cell is in the tree column, and whether the row is expanded.It works by updating TableCollection to match how TreeCollection works, i.e. flattening based on the expanded keys. Updated the hooks according to this structure (
useTableStatenow always supportsexpandedKeys, the separateuseTreeGridStateis not needed), and also updated the legacy TreeGridCollection from v3 to match this for backward compatibility. Also updated Table in RAC to support drag and drop with expandable rows using the TreeDropTargetDelegate.📝 Test Instructions:
Test added stories for RAC Table and S2 TableView. Also test docs examples for both. Test docs and storybook for v3 TableView expandable rows.
Questions
treeColumnin S2, or only the first one?getChildrenon a row (and not nested rows since they are flattened)?idScopeto only apply to cells?