feat: support Textfields and other interactive components in Table#10159
feat: support Textfields and other interactive components in Table#10159LFDanLu wants to merge 27 commits into
Conversation
…ection when in input field
the failing tests didnt focus the element when triggering a keypress
|
Build successful! 🎉 |
|
Build successful! 🎉 |
|
Build successful! 🎉 |
snowystinger
left a comment
There was a problem hiding this comment.
CardView story. Tab into Card with textfield, open menu, press Esc, focus returns to Card not menu trigger.
I think focus return was something we knew we'd struggle with. We have some open issues about this when Shift+Tab'ing back to a collection.
Looks like you noted this as well https://github.com/adobe/react-spectrum/pull/10159/changes#diff-b741e806d7e03bb7f42427dbe388c23128044ae2c96c612a9bceb9852ec408abR1021
Should hitting Esc in a TextField of a selected card de-select it? I thought we were ignoring events that came from inside a cell? basically, if the event's target was anything other than a data-collection* element, then it should be ignored.
In a similar vein, fn+arrow causes grid level navigation even when inside the textfield, which it shouldn't because those keys are shortcuts for moving the cursor inside the textfield already.
|
|
||
| ## Keyboard navigation | ||
|
|
||
| By default, GridList uses arrow key navigation to move focus into rows. Set `keyboardNavigationBehavior="tab"` to have <Keyboard>Tab</Keyboard> move focus in and out of a row. |
There was a problem hiding this comment.
We should include a blurb about when you'd be likely to want to change the keyboard navigation behavior, ie you have something like a textfield or something else that would conflict with arrow key navigation
|
|
||
| // TODO: a change in behavior since taggroup is a gridlist with "tab" keyboard navigation behavior | ||
| // previously you could go to the next tab via arrow keys when you were focused on the close button | ||
| await user.keyboard('{Shift>}{Tab}{/Shift}'); |
There was a problem hiding this comment.
?
| await user.keyboard('{Shift>}{Tab}{/Shift}'); | |
| await user.tab({shift: true}); |
Do you think Tags should be special and differ on this? Not like they can have anything other than the button inside them? or do you think editable tags could be a thing?
There was a problem hiding this comment.
I feel like it might be odd to make an exception since it muddies the waters w/ regards to expected behavior. I do feel like editable tabs could be a thing, but I'm not sure if the experience would be more like our editable table or with an inline textfield
There was a problem hiding this comment.
some other exceptions/special cases that we will want to discuss:
- selection checkboxes in Table, feels a bit awkward that you have to tab into them now
- drag handles, same case as the checkboxes
- table column menus
- do we want to change the editable table row experience to use inline textfields now that is supported?
| // TODO: guarding against selection when firing space/enter/click on a element in a row is technically not only limited to textfields so I | ||
| // am not making it specific to keyboardNavigationBehavior = tab, but maybe we should still? | ||
| // we need to guard against space/enter triggering selection/row link via usePress (from itemProps) so check if propagation | ||
| // is stopped. this also fixes space not working in a textfield in a tree parent row |
There was a problem hiding this comment.
this also fixes space not working in a textfield in a tree parent row
was this not fixed in my other PRs for event leaks? I'm not following the issue here
There was a problem hiding this comment.
I didn't check against your PR since I wanted to approach this in a more isolated approach to see what was the minimal set of changes I needed.
The issue was that useSelectableItem returns itemProps that include onKeyDown that handles updating row selection that then gets merged with our onKeyDown via mergeProps below. That meant hitting Enter in a textfield/area in a row also toggled selection
| role: 'row', | ||
| onKeyDownCapture, | ||
| onKeyDown, | ||
| onKeyDownCapture: keyboardNavigationBehavior === 'arrow' ? onKeyDownCapture : undefined, |
There was a problem hiding this comment.
I want to move this one away from capturing, maybe that will simplify, I was going to look at this in keyboard-shortcut-handler...new-event-leaks#diff-3989d5920106dcc67dc6bdd9497aac4530373a27f5cc521fbee771d773650b92R171
|
@snowystinger with regards to
I agree, I made a comment here wondering if we should automatically stop propagation on ALL events if they bubbled up from the cell. I can update the code to do so, but still a bit wary about if its too much of a blanket approach. EDIT: |
|
Build successful! 🎉 |
… into textfield_gridlists
…en when in tab navigation and on a child of a cell/row
|
Build successful! 🎉 |
| </GridList> | ||
| ``` | ||
|
|
||
| You can further control if a row automatically focuses itself or its children on keyboard focus via `focusMode`. Futhermore, `allowsArrowNavigation` can be used to allow arrow key navigation from the row's children to adjacent rows. |
There was a problem hiding this comment.
feed back on the example here welcome, didn't want to make something too complicated haha. Also wasn't quite sure what a realistic example for Tree would be, so I skipped adding docs for that
|
Build successful! 🎉 |
| return; | ||
| } | ||
|
|
||
| let treeWalker = getFocusableTreeWalker(ref.current); |
There was a problem hiding this comment.
Should only walk tabbable?
| // If the cell itself is focused, wait a frame so that focus finishes propagating | ||
| // up to the tree, and move focus to a focusable child if possible. | ||
| requestAnimationFrame(() => { | ||
| if (focusMode === 'child' && getActiveElement() === ref.current) { |
There was a problem hiding this comment.
should probably be passed the owner document?
| target && | ||
| target !== ref.current && | ||
| isTabbable(target) && | ||
| !target.hasAttribute('data-key') |
There was a problem hiding this comment.
I think this should also check for data-collection at the very least, otherwise we risk preventing selection on nested selectable collections.
There was a problem hiding this comment.
Just a thought because I haven't dug deep, but why does this need to propagate events to the row? Couldn't selection be propagated in state?
There was a problem hiding this comment.
not sure I follow, it isn't so much propagating the events but rather stopping the bubbling press event in the case that the user was clicking on a child element + not calling the press action/selection/etc of the row
There was a problem hiding this comment.
in that case, why doesn't the child already stop propagation? is this just convenience for non react-aria children?
There was a problem hiding this comment.
yep thats right, we figured that we can't guarantee that users wouldn't provide a native input/3rd party elements to the collection component
| let allowsSorting = node.props.allowsSorting; | ||
| // if there are no focusable children, the column header will focus the cell | ||
| let {gridCellProps} = useGridCell({...props, focusMode: 'child'}, state, ref); | ||
| // TODO: I removed the default here so that in tab navigation will default to 'cell' rather than 'child' |
There was a problem hiding this comment.
Maybe this should be modality specific? I understand this being disruptive for keyboard, but on the other hand a click into an interactive cell should imo place focus on the interactive child.
I remember Rob asking for an example of this, so imagine a typical bootstrap-looking table cell with a textfield + save button. Pressing into the cell imo should place focus on the input?
There was a problem hiding this comment.
would a user actually expect that clicking on the cell and not onto the actual interactive content (e.g. the textfield) in the cell to focus the textfield? I personally would expect the cell to get focused, especially if the cell itself had an action tied to it like column resizing/cell action
There was a problem hiding this comment.
Hm, interactivity on the cell itself is a good question - in that case I would expect it to land on the cell, haha. But for a cell that's just an uninteractive wrapper I would want to have it land on the interactive child for convenience. I figure 90% of cell presses in those cases are going to be miss-clicks intended for an interactive child. I understand if you guys streamline to one way though, I'm fine with either since users can still implement something like that on their own.
| let treeWalker = getFocusableTreeWalker(ref.current); | ||
| let focusable = treeWalker.firstChild() as FocusableElement; | ||
| if (focusable) { | ||
| focusSafely(focusable); |
There was a problem hiding this comment.
Do we care about overflowing cells/items? Might have to scroll the child into view?
There was a problem hiding this comment.
good point, I suppose we could call scrollIntoViewport like it is done for the arrow key navigation
There was a problem hiding this comment.
Yeah, I think scrollIntoViewport is fine for now. I think most of the time, what we really want instead of scrollIntoViewport is a scrollIntoNearestVisibleIfNeeded or something like that - that's kind of what I'm working on.
|
|
||
| if (focusMode === 'child') { | ||
| // If focus is already on a focusable child within the row, early return so we don't shift focus | ||
| if (isFocusWithin(ref.current) && ref.current !== getActiveElement()) { |
There was a problem hiding this comment.
Same note about owner document.
| }; | ||
|
|
||
| let onKeyDown = (e: ReactKeyboardEvent) => { | ||
| let activeElement = getActiveElement(); |
|
Build successful! 🎉 |
## API Changes
react-aria-components/react-aria-components:GridListItem GridListItem <T> {
+ allowsArrowNavigation?: boolean
children?: ChildrenOrFunction<GridListItemRenderProps>
className?: ClassNameOrFunction<GridListItemRenderProps> = 'react-aria-GridListItem'
download?: boolean | string
+ focusMode?: 'child' | 'row'
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, GridListItemRenderProps>
routerOptions?: RouterOptions
style?: StyleOrFunction<GridListItemRenderProps>
target?: HTMLAttributeAnchorTarget
textValue?: string
value?: T
}/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>
+ keyboardNavigationBehavior?: 'arrow' | 'tab' = 'arrow'
onExpandedChange?: (Set<Key>) => any
onRowAction?: (Key) => void
onSelectionChange?: (Selection) => void
onSortChange?: (SortDescriptor) => any
selectedKeys?: 'all' | Iterable<Key>
selectionBehavior?: SelectionBehavior = 'toggle'
selectionMode?: SelectionMode
shouldSelectOnPressUp?: boolean
slot?: string | null
sortDescriptor?: SortDescriptor
style?: StyleOrFunction<TableRenderProps>
treeColumn?: Key
}/react-aria-components:Cell Cell {
+ allowsArrowNavigation?: boolean
children?: ChildrenOrFunction<CellRenderProps>
className?: ClassNameOrFunction<CellRenderProps> = 'react-aria-Cell'
colSpan?: number
+ focusMode?: 'child' | 'cell'
id?: Key
render?: DOMRenderFunction<keyof React.JSX.IntrinsicElements, CellRenderProps>
style?: StyleOrFunction<CellRenderProps>
textValue?: string/react-aria-components:Column Column {
+ allowsArrowNavigation?: boolean
allowsSorting?: boolean
children?: ChildrenOrFunction<ColumnRenderProps>
className?: ClassNameOrFunction<ColumnRenderProps> = 'react-aria-Column'
defaultWidth?: ColumnSize | null
+ focusMode?: 'child' | 'cell'
id?: Key
isRowHeader?: boolean
maxWidth?: ColumnStaticSize | null
minWidth?: ColumnStaticSize | null
style?: StyleOrFunction<ColumnRenderProps>
textValue?: string
width?: ColumnSize | null
}/react-aria-components:TreeItem TreeItem <T extends any> {
+ allowsArrowNavigation?: boolean
aria-label?: string
children: ReactNode
className?: ClassNameOrFunction<TreeItemRenderProps> = 'react-aria-TreeItem'
download?: boolean | string
+ focusMode?: 'child' | 'row' = 'row'
hasChildItems?: boolean
href?: Href
hrefLang?: string
id?: Key
onAction?: () => void
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, TreeItemRenderProps>
routerOptions?: RouterOptions
style?: StyleOrFunction<TreeItemRenderProps>
target?: HTMLAttributeAnchorTarget
textValue: string
value?: any
}/react-aria-components:GridListItemProps GridListItemProps <T = {}> {
+ allowsArrowNavigation?: boolean
children?: ChildrenOrFunction<GridListItemRenderProps>
className?: ClassNameOrFunction<GridListItemRenderProps> = 'react-aria-GridListItem'
download?: boolean | string
+ focusMode?: 'child' | 'row'
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, GridListItemRenderProps>
routerOptions?: RouterOptions
style?: StyleOrFunction<GridListItemRenderProps>
target?: HTMLAttributeAnchorTarget
textValue?: string
value?: T
}/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>
+ keyboardNavigationBehavior?: 'arrow' | 'tab' = 'arrow'
onExpandedChange?: (Set<Key>) => any
onRowAction?: (Key) => void
onSelectionChange?: (Selection) => void
onSortChange?: (SortDescriptor) => any
selectedKeys?: 'all' | Iterable<Key>
selectionBehavior?: SelectionBehavior = 'toggle'
selectionMode?: SelectionMode
shouldSelectOnPressUp?: boolean
slot?: string | null
sortDescriptor?: SortDescriptor
style?: StyleOrFunction<TableRenderProps>
treeColumn?: Key
}/react-aria-components:ColumnProps ColumnProps {
+ allowsArrowNavigation?: boolean
allowsSorting?: boolean
children?: ChildrenOrFunction<ColumnRenderProps>
className?: ClassNameOrFunction<ColumnRenderProps> = 'react-aria-Column'
defaultWidth?: ColumnSize | null
+ focusMode?: 'child' | 'cell'
id?: Key
isRowHeader?: boolean
maxWidth?: ColumnStaticSize | null
minWidth?: ColumnStaticSize | null
style?: StyleOrFunction<ColumnRenderProps>
textValue?: string
width?: ColumnSize | null
}/react-aria-components:CellProps CellProps {
+ allowsArrowNavigation?: boolean
children?: ChildrenOrFunction<CellRenderProps>
className?: ClassNameOrFunction<CellRenderProps> = 'react-aria-Cell'
colSpan?: number
+ focusMode?: 'child' | 'cell'
id?: Key
render?: DOMRenderFunction<keyof React.JSX.IntrinsicElements, CellRenderProps>
style?: StyleOrFunction<CellRenderProps>
textValue?: string/react-aria-components:TreeItemProps TreeItemProps <T = {}> {
+ allowsArrowNavigation?: boolean
aria-label?: string
children: ReactNode
className?: ClassNameOrFunction<TreeItemRenderProps> = 'react-aria-TreeItem'
download?: boolean | string
+ focusMode?: 'child' | 'row' = 'row'
hasChildItems?: boolean
href?: Href
hrefLang?: string
id?: Key
onAction?: () => void
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, TreeItemRenderProps>
routerOptions?: RouterOptions
style?: StyleOrFunction<TreeItemRenderProps>
target?: HTMLAttributeAnchorTarget
textValue: string
value?: T
}@react-aria/grid/@react-aria/grid:GridProps GridProps {
aria-describedby?: string
aria-details?: string
aria-label?: string
aria-labelledby?: string
disallowTypeAhead?: boolean = false
escapeKeyBehavior?: 'clearSelection' | 'none' = 'clearSelection'
focusMode?: 'row' | 'cell' = 'row'
getRowText?: (Key) => string = (key) => state.collection.getItem(key)?.textValue
id?: string
isVirtualized?: boolean
keyboardDelegate?: KeyboardDelegate
+ keyboardNavigationBehavior?: 'arrow' | 'tab' = 'arrow'
onCellAction?: (Key) => void
onRowAction?: (Key) => void
scrollRef?: RefObject<HTMLElement | null>
shouldSelectOnPressUp?: boolean/@react-aria/grid:GridCellProps GridCellProps {
+ allowsArrowNavigation?: boolean
colSpan?: number
focusMode?: 'child' | 'cell'
isVirtualized?: boolean
node: GridNode<unknown>
}@react-aria/gridlist/@react-aria/gridlist:AriaGridListItemOptions AriaGridListItemOptions {
+ allowsArrowNavigation?: boolean
+ focusMode?: 'child' | 'row' = 'row'
hasChildItems?: boolean
isVirtualized?: boolean
node: Node<unknown>
shouldSelectOnPressUp?: boolean@react-aria/table/@react-aria/table:AriaTableProps AriaTableProps {
aria-describedby?: string
aria-details?: string
aria-label?: string
aria-labelledby?: string
disallowTypeAhead?: boolean = false
escapeKeyBehavior?: 'clearSelection' | 'none' = 'clearSelection'
focusMode?: 'row' | 'cell' = 'row'
getRowText?: (Key) => string = (key) => state.collection.getItem(key)?.textValue
id?: string
isVirtualized?: boolean
keyboardDelegate?: KeyboardDelegate
+ keyboardNavigationBehavior?: 'arrow' | 'tab' = 'arrow'
layoutDelegate?: LayoutDelegate
onCellAction?: (Key) => void
onRowAction?: (Key) => void
scrollRef?: RefObject<HTMLElement | null>
}/@react-aria/table:AriaTableColumnHeaderProps AriaTableColumnHeaderProps <T> {
+ allowsArrowNavigation?: boolean
+ focusMode?: 'child' | 'cell'
isVirtualized?: boolean
node: GridNode<T>
}/@react-aria/table:AriaTableCellProps AriaTableCellProps {
+ allowsArrowNavigation?: boolean
+ focusMode?: 'child' | 'cell'
isVirtualized?: boolean
node: GridNode<unknown>
shouldSelectOnPressUp?: boolean
}@react-aria/tree/@react-aria/tree:AriaTreeItemOptions AriaTreeItemOptions {
+ allowsArrowNavigation?: boolean
+ focusMode?: 'child' | 'row' = 'row'
hasChildItems?: boolean
node: Node<unknown>
shouldSelectOnPressUp?: boolean
}@react-spectrum/ai/@react-spectrum/ai:Attachment Attachment {
+ allowsArrowNavigation?: boolean
aria-describedby?: string
aria-details?: string
aria-label?: string
aria-labelledby?: string
children: ReactNode | (AttachmentRenderProps) => ReactNode
density?: 'compact' | 'regular' | 'spacious' = 'regular'
download?: boolean | string
+ focusMode?: 'child' | 'row'
href?: Href
hrefLang?: string
id?: Key
isDisabled?: boolean
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, TagRenderProps>
routerOptions?: RouterOptions
size?: 'XS' | 'S' | 'M' | 'L' | 'XL' = 'M'
styles?: StyleString
target?: HTMLAttributeAnchorTarget
textValue?: string
uploadProgress?: number
value?: T
variant?: 'primary' | 'secondary' | 'tertiary' | 'quiet' = 'primary'
}/@react-spectrum/ai:ThreadItem ThreadItem {
+ allowsArrowNavigation?: boolean
children?: ChildrenOrFunction<GridListItemRenderProps>
+ focusMode?: 'child' | 'row'
isStreaming?: boolean
shouldAnnounceOnMount?: boolean
styles?: StyleString
textValue?: string/@react-spectrum/ai:AttachmentProps AttachmentProps {
+ allowsArrowNavigation?: boolean
aria-describedby?: string
aria-details?: string
aria-label?: string
aria-labelledby?: string
children: ReactNode | (AttachmentRenderProps) => ReactNode
density?: 'compact' | 'regular' | 'spacious' = 'regular'
download?: boolean | string
+ focusMode?: 'child' | 'row'
href?: Href
hrefLang?: string
id?: Key
isDisabled?: boolean
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, TagRenderProps>
routerOptions?: RouterOptions
size?: 'XS' | 'S' | 'M' | 'L' | 'XL' = 'M'
styles?: StyleString
target?: HTMLAttributeAnchorTarget
textValue?: string
uploadProgress?: number
value?: T
variant?: 'primary' | 'secondary' | 'tertiary' | 'quiet' = 'primary'
}/@react-spectrum/ai:ThreadItemProps ThreadItemProps {
+ allowsArrowNavigation?: boolean
children?: ChildrenOrFunction<GridListItemRenderProps>
+ focusMode?: 'child' | 'row'
isStreaming?: boolean
shouldAnnounceOnMount?: boolean
styles?: StyleString
textValue?: string@react-spectrum/s2/@react-spectrum/s2:Card Card {
UNSAFE_className?: UnsafeClassName
UNSAFE_style?: CSSProperties
+ allowsArrowNavigation?: boolean
children: ReactNode | (CardRenderProps) => ReactNode
density?: 'compact' | 'regular' | 'spacious' = 'regular'
download?: boolean | string
+ focusMode?: 'child' | 'row'
href?: Href
hrefLang?: string
id?: Key
isDisabled?: boolean
onPress?: (PressEvent) => void
onPressChange?: (boolean) => void
onPressEnd?: (PressEvent) => void
onPressStart?: (PressEvent) => void
onPressUp?: (PressEvent) => void
ping?: string
referrerPolicy?: HTMLAttributeReferrerPolicy
rel?: string
routerOptions?: RouterOptions
size?: 'XS' | 'S' | 'M' | 'L' | 'XL' = 'M'
styles?: StylesProp
target?: HTMLAttributeAnchorTarget
textValue?: string
value?: T
variant?: 'primary' | 'secondary' | 'tertiary' | 'quiet' = 'primary'
}/@react-spectrum/s2:AssetCard AssetCard {
UNSAFE_className?: UnsafeClassName
UNSAFE_style?: CSSProperties
+ allowsArrowNavigation?: boolean
children: ReactNode | (CardRenderProps) => ReactNode
download?: boolean | string
+ focusMode?: 'child' | 'row'
href?: Href
hrefLang?: string
id?: Key
isDisabled?: boolean
onPress?: (PressEvent) => void
onPressChange?: (boolean) => void
onPressEnd?: (PressEvent) => void
onPressStart?: (PressEvent) => void
onPressUp?: (PressEvent) => void
ping?: string
referrerPolicy?: HTMLAttributeReferrerPolicy
rel?: string
routerOptions?: RouterOptions
size?: 'XS' | 'S' | 'M' | 'L' | 'XL' = 'M'
styles?: StylesProp
target?: HTMLAttributeAnchorTarget
textValue?: string
value?: T
variant?: 'primary' | 'secondary' | 'tertiary' | 'quiet' = 'primary'
}/@react-spectrum/s2:UserCard UserCard {
UNSAFE_className?: UnsafeClassName
UNSAFE_style?: CSSProperties
+ allowsArrowNavigation?: boolean
children: ReactNode | (CardRenderProps) => ReactNode
download?: boolean | string
+ focusMode?: 'child' | 'row'
href?: Href
hrefLang?: string
id?: Key
isDisabled?: boolean
onPress?: (PressEvent) => void
onPressChange?: (boolean) => void
onPressEnd?: (PressEvent) => void
onPressStart?: (PressEvent) => void
onPressUp?: (PressEvent) => void
ping?: string
referrerPolicy?: HTMLAttributeReferrerPolicy
rel?: string
routerOptions?: RouterOptions
size?: 'XS' | 'S' | 'M' | 'L' | 'XL' = 'M'
styles?: StylesProp
target?: HTMLAttributeAnchorTarget
textValue?: string
value?: T
variant?: 'primary' | 'secondary' | 'tertiary'
}/@react-spectrum/s2:ProductCard ProductCard {
UNSAFE_className?: UnsafeClassName
UNSAFE_style?: CSSProperties
+ allowsArrowNavigation?: boolean
children: ReactNode | (CardRenderProps) => ReactNode
download?: boolean | string
+ focusMode?: 'child' | 'row'
href?: Href
hrefLang?: string
id?: Key
isDisabled?: boolean
onPress?: (PressEvent) => void
onPressChange?: (boolean) => void
onPressEnd?: (PressEvent) => void
onPressStart?: (PressEvent) => void
onPressUp?: (PressEvent) => void
ping?: string
referrerPolicy?: HTMLAttributeReferrerPolicy
rel?: string
routerOptions?: RouterOptions
size?: 'XS' | 'S' | 'M' | 'L' | 'XL' = 'M'
styles?: StylesProp
target?: HTMLAttributeAnchorTarget
textValue?: string
value?: T
variant?: 'primary' | 'secondary' | 'tertiary'
}/@react-spectrum/s2:ListViewItem ListViewItem {
+ allowsArrowNavigation?: boolean
children: ReactNode
download?: boolean | string
+ focusMode?: 'child' | 'row'
hasChildItems?: boolean
href?: Href
hrefLang?: string
id?: Key
onAction?: () => 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
routerOptions?: RouterOptions
target?: HTMLAttributeAnchorTarget
textValue?: string
value?: T
}/@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
dragAndDropHooks?: DragAndDropHooks
escapeKeyBehavior?: 'clearSelection' | 'none' = 'clearSelection'
expandedKeys?: Iterable<Key>
id?: string
isQuiet?: boolean
+ keyboardNavigationBehavior?: 'arrow' | 'tab' = 'arrow'
loadingState?: LoadingState
onAction?: (Key) => void
onExpandedChange?: (Set<Key>) => any
onLoadMore?: () => any
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
selectionStyle?: 'checkbox' | 'highlight' = 'checkbox'
shouldSelectOnPressUp?: boolean
slot?: string | null
sortDescriptor?: SortDescriptor
styles?: StylesPropWithHeight
treeColumn?: Key
}/@react-spectrum/s2:Cell Cell {
align?: 'start' | 'center' | 'end' = 'start'
+ allowsArrowNavigation?: boolean
children: ReactNode
colSpan?: number
+ focusMode?: 'child' | 'cell'
id?: Key
showDivider?: boolean
textValue?: string
}/@react-spectrum/s2:Column Column {
align?: 'start' | 'center' | 'end' = 'start'
+ allowsArrowNavigation?: boolean
allowsResizing?: boolean
allowsSorting?: boolean
children: ReactNode
defaultWidth?: ColumnSize | null
+ focusMode?: 'child' | 'cell'
id?: Key
isRowHeader?: boolean
maxWidth?: ColumnStaticSize | null
menuItems?: ReactNode
showDivider?: boolean
textValue?: string
width?: ColumnSize | null
}/@react-spectrum/s2:EditableCell EditableCell {
action?: string | FormHTMLAttributes<HTMLFormElement>['action']
align?: 'start' | 'center' | 'end' = 'start'
+ allowsArrowNavigation?: boolean
children: ReactNode
colSpan?: number
+ focusMode?: 'child' | 'cell'
id?: Key
isSaving?: boolean
onCancel?: () => void
onSubmit?: (FormEvent<HTMLFormElement>) => void
showDivider?: boolean
textValue?: string
}/@react-spectrum/s2:TreeViewItem TreeViewItem {
+ allowsArrowNavigation?: boolean
aria-label?: string
children: ReactNode
download?: boolean | string
+ focusMode?: 'child' | 'row' = 'row'
hasChildItems?: boolean
href?: Href
hrefLang?: string
id?: Key
onAction?: () => 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
routerOptions?: RouterOptions
target?: HTMLAttributeAnchorTarget
textValue: string
value?: T
}/@react-spectrum/s2:CardProps CardProps {
UNSAFE_className?: UnsafeClassName
UNSAFE_style?: CSSProperties
+ allowsArrowNavigation?: boolean
children: ReactNode | (CardRenderProps) => ReactNode
density?: 'compact' | 'regular' | 'spacious' = 'regular'
download?: boolean | string
+ focusMode?: 'child' | 'row'
href?: Href
hrefLang?: string
id?: Key
isDisabled?: boolean
onPress?: (PressEvent) => void
onPressChange?: (boolean) => void
onPressEnd?: (PressEvent) => void
onPressStart?: (PressEvent) => void
onPressUp?: (PressEvent) => void
ping?: string
referrerPolicy?: HTMLAttributeReferrerPolicy
rel?: string
routerOptions?: RouterOptions
size?: 'XS' | 'S' | 'M' | 'L' | 'XL' = 'M'
styles?: StylesProp
target?: HTMLAttributeAnchorTarget
textValue?: string
value?: T
variant?: 'primary' | 'secondary' | 'tertiary' | 'quiet' = 'primary'
}/@react-spectrum/s2:AssetCardProps AssetCardProps {
UNSAFE_className?: UnsafeClassName
UNSAFE_style?: CSSProperties
+ allowsArrowNavigation?: boolean
children: ReactNode | (CardRenderProps) => ReactNode
download?: boolean | string
+ focusMode?: 'child' | 'row'
href?: Href
hrefLang?: string
id?: Key
isDisabled?: boolean
onPress?: (PressEvent) => void
onPressChange?: (boolean) => void
onPressEnd?: (PressEvent) => void
onPressStart?: (PressEvent) => void
onPressUp?: (PressEvent) => void
ping?: string
referrerPolicy?: HTMLAttributeReferrerPolicy
rel?: string
routerOptions?: RouterOptions
size?: 'XS' | 'S' | 'M' | 'L' | 'XL' = 'M'
styles?: StylesProp
target?: HTMLAttributeAnchorTarget
textValue?: string
value?: T
variant?: 'primary' | 'secondary' | 'tertiary' | 'quiet' = 'primary'
}/@react-spectrum/s2:ProductCardProps ProductCardProps {
UNSAFE_className?: UnsafeClassName
UNSAFE_style?: CSSProperties
+ allowsArrowNavigation?: boolean
children: ReactNode | (CardRenderProps) => ReactNode
download?: boolean | string
+ focusMode?: 'child' | 'row'
href?: Href
hrefLang?: string
id?: Key
isDisabled?: boolean
onPress?: (PressEvent) => void
onPressChange?: (boolean) => void
onPressEnd?: (PressEvent) => void
onPressStart?: (PressEvent) => void
onPressUp?: (PressEvent) => void
ping?: string
referrerPolicy?: HTMLAttributeReferrerPolicy
rel?: string
routerOptions?: RouterOptions
size?: 'XS' | 'S' | 'M' | 'L' | 'XL' = 'M'
styles?: StylesProp
target?: HTMLAttributeAnchorTarget
textValue?: string
value?: T
variant?: 'primary' | 'secondary' | 'tertiary'
}/@react-spectrum/s2:UserCardProps UserCardProps {
UNSAFE_className?: UnsafeClassName
UNSAFE_style?: CSSProperties
+ allowsArrowNavigation?: boolean
children: ReactNode | (CardRenderProps) => ReactNode
download?: boolean | string
+ focusMode?: 'child' | 'row'
href?: Href
hrefLang?: string
id?: Key
isDisabled?: boolean
onPress?: (PressEvent) => void
onPressChange?: (boolean) => void
onPressEnd?: (PressEvent) => void
onPressStart?: (PressEvent) => void
onPressUp?: (PressEvent) => void
ping?: string
referrerPolicy?: HTMLAttributeReferrerPolicy
rel?: string
routerOptions?: RouterOptions
size?: 'XS' | 'S' | 'M' | 'L' | 'XL' = 'M'
styles?: StylesProp
target?: HTMLAttributeAnchorTarget
textValue?: string
value?: T
variant?: 'primary' | 'secondary' | 'tertiary'
}/@react-spectrum/s2:ListViewItemProps ListViewItemProps {
+ allowsArrowNavigation?: boolean
children: ReactNode
download?: boolean | string
+ focusMode?: 'child' | 'row'
hasChildItems?: boolean
href?: Href
hrefLang?: string
id?: Key
onAction?: () => 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
routerOptions?: RouterOptions
target?: HTMLAttributeAnchorTarget
textValue?: string
value?: T
}/@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
dragAndDropHooks?: DragAndDropHooks
escapeKeyBehavior?: 'clearSelection' | 'none' = 'clearSelection'
expandedKeys?: Iterable<Key>
id?: string
isQuiet?: boolean
+ keyboardNavigationBehavior?: 'arrow' | 'tab' = 'arrow'
loadingState?: LoadingState
onAction?: (Key) => void
onExpandedChange?: (Set<Key>) => any
onLoadMore?: () => any
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
selectionStyle?: 'checkbox' | 'highlight' = 'checkbox'
shouldSelectOnPressUp?: boolean
slot?: string | null
sortDescriptor?: SortDescriptor
styles?: StylesPropWithHeight
treeColumn?: Key
}/@react-spectrum/s2:CellProps CellProps {
align?: 'start' | 'center' | 'end' = 'start'
+ allowsArrowNavigation?: boolean
children: ReactNode
colSpan?: number
+ focusMode?: 'child' | 'cell'
id?: Key
showDivider?: boolean
textValue?: string
}/@react-spectrum/s2:ColumnProps ColumnProps {
align?: 'start' | 'center' | 'end' = 'start'
+ allowsArrowNavigation?: boolean
allowsResizing?: boolean
allowsSorting?: boolean
children: ReactNode
defaultWidth?: ColumnSize | null
+ focusMode?: 'child' | 'cell'
id?: Key
isRowHeader?: boolean
maxWidth?: ColumnStaticSize | null
menuItems?: ReactNode
showDivider?: boolean
textValue?: string
width?: ColumnSize | null
}/@react-spectrum/s2:TreeViewItemProps TreeViewItemProps {
+ allowsArrowNavigation?: boolean
aria-label?: string
children: ReactNode
download?: boolean | string
+ focusMode?: 'child' | 'row' = 'row'
hasChildItems?: boolean
href?: Href
hrefLang?: string
id?: Key
onAction?: () => 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
routerOptions?: RouterOptions
target?: HTMLAttributeAnchorTarget
textValue: string
value?: T
}@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<T>>, ReactElement<TableBodyProps<T>>]
defaultSelectedKeys?: 'all' | Iterable<Key>
density?: 'compact' | 'regular' | 'spacious' = 'regular'
disabledBehavior?: DisabledBehavior = 'selection'
disabledKeys?: Iterable<Key>
disallowEmptySelection?: boolean
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'>
+ keyboardNavigationBehavior?: 'arrow' | 'tab' = 'arrow'
left?: Responsive<DimensionValue>
margin?: Responsive<DimensionValue>
marginBottom?: Responsive<DimensionValue>
marginEnd?: 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/table:SpectrumTableProps SpectrumTableProps <T> {
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<T>>, ReactElement<TableBodyProps<T>>]
defaultSelectedKeys?: 'all' | Iterable<Key>
density?: 'compact' | 'regular' | 'spacious' = 'regular'
disabledBehavior?: DisabledBehavior = 'selection'
disabledKeys?: Iterable<Key>
disallowEmptySelection?: boolean
dragAndDropHooks?: DragAndDropHooks<NoInfer<T>>['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'>
+ keyboardNavigationBehavior?: 'arrow' | 'tab' = 'arrow'
left?: Responsive<DimensionValue>
margin?: Responsive<DimensionValue>
marginBottom?: Responsive<DimensionValue>
marginEnd?: 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/tree/@react-spectrum/tree:TreeViewItem TreeViewItem {
+ allowsArrowNavigation?: boolean
aria-label?: string
children: ReactNode
download?: boolean | string
+ focusMode?: 'child' | 'row' = 'row'
hasChildItems?: boolean
href?: Href
hrefLang?: string
id?: Key
onAction?: () => void
onPress?: (PressEvent) => void
onPressChange?: (boolean) => void
onPressEnd?: (PressEvent) => void
onPressStart?: (PressEvent) => void
onPressUp?: (PressEvent) => void
ping?: string
referrerPolicy?: HTMLAttributeReferrerPolicy
rel?: string
routerOptions?: RouterOptions
target?: HTMLAttributeAnchorTarget
textValue: string
}/@react-spectrum/tree:SpectrumTreeViewItemProps SpectrumTreeViewItemProps {
+ allowsArrowNavigation?: boolean
aria-label?: string
children: ReactNode
download?: boolean | string
+ focusMode?: 'child' | 'row' = 'row'
hasChildItems?: boolean
href?: Href
hrefLang?: string
id?: Key
onAction?: () => void
onPress?: (PressEvent) => void
onPressChange?: (boolean) => void
onPressEnd?: (PressEvent) => void
onPressStart?: (PressEvent) => void
onPressUp?: (PressEvent) => void
ping?: string
referrerPolicy?: HTMLAttributeReferrerPolicy
rel?: string
routerOptions?: RouterOptions
target?: HTMLAttributeAnchorTarget
textValue: string
}@react-stately/table/@react-stately/table:TableProps TableProps <T> {
children: [ReactElement<TableHeaderProps<T>>, ReactElement<TableBodyProps<T>>]
defaultExpandedKeys?: Iterable<Key>
defaultSelectedKeys?: 'all' | Iterable<Key>
disabledKeys?: Iterable<Key>
disallowEmptySelection?: boolean
escapeKeyBehavior?: 'clearSelection' | 'none' = 'clearSelection'
expandedKeys?: Iterable<Key>
+ keyboardNavigationBehavior?: 'arrow' | 'tab' = 'arrow'
onExpandedChange?: (Set<Key>) => any
onSelectionChange?: (Selection) => void
onSortChange?: (SortDescriptor) => any
selectedKeys?: 'all' | Iterable<Key>
shouldSelectOnPressUp?: boolean
sortDescriptor?: SortDescriptor
treeColumn?: Key
} |
Agent Skills ChangesModified (7)
InstallReact Spectrum S2: React Aria: |
| // not if the target is a child of a different collections aka taggroup in table cell. | ||
| let isChildInteraction = (target: Element) => { | ||
| let el: Element | null = target; | ||
| while (el && el !== ref.current) { |
There was a problem hiding this comment.
I believe this should check isTabbable() on a nested collection item? And just to make sure, we don't propagate selection in submenus, right? Asking because Menu still has the issue with unstable collection ids I referred to a while back.
There was a problem hiding this comment.
hm, I didn't check for isTabbable here since we have instances of roaming tabindex collections like TagGroup where the user might click on a tag that has tabindex=-1 which isn't tabbable but should still be considered a child interaction. As for submenus, just to clarify are you talking about the selection event propagating from the parent menu to the submenu?
There was a problem hiding this comment.
Related to #10159 (comment). In that case the nested Tag would have stopped propagation so we would never be receiving the event up here, no? I was under the impression that events were supposed to bubble from a cell to a row to propagate the select action - apparently that's not the case, thereby I can't quite follow why we need to handle children at all, if not for non react-aria children.
Edit: Since this is just convenience, we ought to draw a line at some point, no? I think a roving tabindex would be a reasonable thing to be implemented through react-aria, so is that something we need to account for? I was worried about nested, yet disabled, interactive children. The more I think about it though, the more I'm also okay with keeping it the way it is - probably better to ignore selection events when there is visual cue than it is to have third party components break behavior.
There was a problem hiding this comment.
so this check is not needed if the nested Tag has selection enabled since it will stop propagation as you mentioned. However, your original comment about nested collection components made me think about cases such as inline text selection/pressing to move focus in nested collection components in which we may not stop propagation since there isn't a event that is being explicitly handled. That made me lean towards the current implementation of not firing the parent collection's row's press handlers if the original target is a nested collection element, but disabled interactive children in a nested collection component is a case I didn't consider. I suppose you would expect the parent row's press action to fire in that case?
I'm a bit torn now as to what would be the best experience here now haha. I'd rather not have accidental row action/selection happen when a user was pressing on a child but that might be overly conservative
There was a problem hiding this comment.
Yeah that's what I lean towards also now. Visually this is probably the easiest to reason about and assisted users are unlikely to land presses on disabled content in general.
snowystinger
left a comment
There was a problem hiding this comment.
happy to get in for more testing and use
|
|
||
| return ( | ||
| <RACColumn | ||
| // will need to make sure that focusMode and allowsArrowNavigation are only both set if said cell |
There was a problem hiding this comment.
do we need to police this? what happens if this is ignored?
There was a problem hiding this comment.
I guess it partially depends on our stance around if we want to expose this in S2 or just in RAC. I'm currently leaning towards exposing these in S2 but not policing it since it falls into the realm of user error rather an accessibility problem IMO
I'm also not 100% tied to exposing these props in S2, I'm not sure what use cases may exist that S2 end users would have so maybe it would best to wait for requests to expose these props.
|
@snowystinger Just noting that this PR will need the work of #10102 and another follow-up to patch scrollIntoView, otherwise cells with inputs will fail to be positioned correctly on devices with an OSK. |
Closes #2328
✅ Pull Request Checklist:
📝 Test Instructions:
Test GridList/Table/Tree with textfield stories and test
keyboardNavigationBehavior=tabbehavior. You should be able to interact/type/etc with the interactive elements without causing focus navigation/row selection even if you use the arrow keys/Enter/Space/etc. It should requireTabfrom the cell/row to the cell/row's content to interact with the children, andShift-Tabback to the cell/row to perform row navigation/selection via arrow keys/Enter/Space as usual.Test the same in S2 TableView/CardView/TagGroup. Be sure to test TableView more throughly since
keyboardNavigationBehavior=tabis net new as wellIn addition,
focusModeandallowsArrowNavigationhas been added to Cell/GridListItem/TreeViewItem/etc. This allows users to have selection cells automatically focus the checkbox and maintain arrow key navigation to the adjacent rows even inkeyboardNavigationBehavior=tab. Docs added for this in RAC Table and Grid, S2 TableView takes advantage of this automatically for its selection checkbox cells, drag cells, and column headers that have menus🧢 Your Project:
RSP