-
Notifications
You must be signed in to change notification settings - Fork 1.3k
feat: TextFields in GridList and Grid #8691
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
base: main
Are you sure you want to change the base?
Conversation
if (keyboardNavigationBehavior === 'tab') { | ||
focusMode = 'cell'; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be a hard override? IIRC, my impl had
focusMode ??= keyboardNavigation === 'tab' ? 'cell' : 'child';
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a good point, hadn't thought this through yet
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trying to think of the mixed use case, I was thinking maybe for the selection column in a table. But it's a little weird to mix the navigation styles, feels a little inconsistent even if i don't like the way the selection column ends up looking in React Spectrum. What was your reason for doing it not as a hard override?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Imagine a cell with a text field and "save" button. When pressing on the cell, wouldn't you want to have the textfield focused for convenience?
if (keyboardNavigationBehavior === 'tab' && e.target !== ref.current) { | ||
return; | ||
} | ||
|
||
let walker = getFocusableTreeWalker(ref.current); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe this should only walk tabbable, right? Same in L78.
We need to add tests for disabled interactive content 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is for cell level navigation, not inside the cell. In keyboardNavigationBehavior = tab, we've already exited if we were inside the cell, skipping this code. I think it is correct as it is, but maybe an example would be helpful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added a test for disabled interactive content
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know this isn't related to edit, IIRC they were bugs I fixed while working on it. L78 is basically the focus strategy of the cell, which should honor whether a child is tabbable. L123 is for keyboardNavigationBehavior=arrow
movement inside the cell, which also does not respect tabbable at the moment.
@@ -90,7 +96,8 @@ export function useGrid<T>(props: GridProps, state: GridState<T, GridCollection< | |||
onRowAction, | |||
onCellAction, | |||
escapeKeyBehavior = 'clearSelection', | |||
shouldSelectOnPressUp | |||
shouldSelectOnPressUp, | |||
keyboardNavigationBehavior = 'arrow' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One of the insights while working on this was that "edit mode", when handling events in the scroll container, was pretty much just keyboardNavigationBehavior="tab"
applied conditionally.
Raising here because I think it could make sense to lift this prop into grid state instead of forwarding via props, which could replace state.isKeyboardNavigationDisabled
of table and grid.
For impl, see useGridEditState & useGridState
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Edit: started playing around with resizable table columns, I'm definitely going to need to think more about this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you clarify in your changes how this decided a cell was editable? I don't think I'm following that right.
I implemented an extended SelectionManager
for the editable grid, called a GridManager
. You can checkout the impl here. TLDR; isEditable
was set as a node prop on either column or cell.
@@ -113,6 +116,10 @@ export function useGridCell<T, C extends GridCollection<T>>(props: GridCellProps | |||
return; | |||
} | |||
|
|||
if (keyboardNavigationBehavior === 'tab' && e.target !== ref.current) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
L115: I think state.isKeyboardNavigationBehaviorDisabled
shouldn't interfere with editing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm sorry, I don't follow your concern here, both are possible conditions for leaving this event handler early?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nevermind, you are correct. I had removed this condition for the W3C focus wrapping feature.
See here for impl.
if (!isFocusVisible()) { | ||
state.selectionManager.setFocusedKey(node.key); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just flagging that this is still missing a fix to maintain a consistent tab sequence. Re-entering the collection from the bottom after exiting may cause focus to land on the item instead of the field. Same goes for the cell.onFocus()
.
For impl, see here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I suspect this is related to #8686 as well
Thanks for the extra info, really appreciated. I'll make it much easier to determine if we tackle it in this PR as well.
Build successful! 🎉 |
Build successful! 🎉 |
Build successful! 🎉 |
Build successful! 🎉 |
Build successful! 🎉 |
@@ -123,8 +130,45 @@ export function useGrid<T>(props: GridProps, state: GridState<T, GridCollection< | |||
escapeKeyBehavior | |||
}); | |||
|
|||
|
|||
let originalOnKeyDown = collectionProps.onKeyDown; | |||
let onKeyDown = useCallback((e: BaseEvent<KeyboardEvent<any>>) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't follow what these event handlers are doing. Why the special cases? Seems quite specific to our implementation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was trying to avoid passing keyboardNavigationBehavior to useSelectableCollection
I couldn't necessarily pass it to the GridKeyboardDelegate either since that can be passed in, but that was another thought I had.
I know this won't be the final logic for the PR, but it serves as a starting point since I'm unsure what we want to do here. So this shows where there are problematic event handlers.
@nwidynski had this suggestion #8691 (comment)
But there are a few ways we could approach this.
The problematic behaviours that this is blocking are:
useSelectableCollection encompasses type select, which is capturing, and moving focus out of the collection on bubble phase tab.
@@ -171,7 +178,7 @@ export function useGridCell<T, C extends GridCollection<T>>(props: GridCellProps | |||
? walker.previousNode() as FocusableElement | |||
: walker.nextNode() as FocusableElement; | |||
|
|||
if (focusMode === 'child' && focusable === ref.current) { | |||
if ((focusMode === 'child' && focusable === ref.current) || (keyboardNavigationBehavior === 'tab')) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will never happen because of the early return above right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It happens if you are focused on the cell, the early return above is only for events inside the cell
@@ -135,6 +135,10 @@ export function useGridListItem<T>(props: AriaGridListItemOptions, state: ListSt | |||
return; | |||
} | |||
|
|||
if (keyboardNavigationBehavior === 'tab' && e.target !== ref.current) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's some logic below that previously ran on arrowup/arrowdown with keyboardNavigationBehavior = tab. Noticed in CardView you can no longer press up/down to move to the next card when focused on an action menu.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I allow those through, it'll conflict with cursor positioning in textfields. I think we'll have to solve event leaks to have that not collide. We'll also need to make it non-capturing.
originalOnKeyDown?.(e); | ||
}; | ||
|
||
// Prevent clicking on a focusable element from selecting the row. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one and the one above seem like they should be handled by the event leaks - i.e. stopping propagation within the component that handles the press.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
agreed, these would go away, but this pr isn't fixing event leaks, see description
Note: this does not address the event leaking brought up in another issue. That work is still to come and may make this unnecessary, however, that touches a much wider set of components and hooks with much more testing needed.
I'm working on event leaks in another local branch at the moment, but it has some interesting issues to solve still.
@@ -144,6 +146,38 @@ export function useGridList<T>(props: AriaGridListOptions<T>, state: ListState<T | |||
escapeKeyBehavior | |||
}); | |||
|
|||
let originalOnKeyDown = listProps.onKeyDown; | |||
let onKeyDown = useCallback((e: BaseEvent<KeyboardEvent<any>>) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same
</TableHeader> | ||
<TableBody items={manyRows}> | ||
{item => ( | ||
<Row id={item.id} columns={manyColumns}> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why so many rows and columns? We should try to make a realistic example
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's funny, I've actually seen a screenshot of one which looked much like this (though only one textfield to a cell, but i wanted more for testing)
But I'm happy to change it to something a little more real life looking.
* via the left/right arrow keys or the tab key. | ||
* @default 'arrow' | ||
*/ | ||
keyboardNavigationBehavior?: 'arrow' | 'tab' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wondering if this API should be at the cell level instead of (or in addition to?) the whole grid. I could imagine a UI where only certain columns have textfields in them, and it would be nice if the other columns still automatically focused the content of the cell. For example, in the Reddit Table story in S2 with keyboardNavigationBehavior="tab"
, now you have to tab to the links instead of just arrowing to them, but there are no inputs in that cell.
Ideally we'd even automatically set a cell to tab behavior if we detected inputs in them. But I guess there will be cases with custom components where we'll need a prop too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Linking related discussion #8691 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, I don't love it either, I have to tab to the column header menu trigger now as well for resizing table, and the focus ring on selection checkbox cells looks funny.
Build successful! 🎉 |
## API Changes
react-aria-components/react-aria-components:Popover Popover {
aria-describedby?: string
aria-details?: string
aria-label?: string
aria-labelledby?: string
arrowBoundaryOffset?: number = 0
- arrowRef?: RefObject<Element | null>
boundaryElement?: Element = document.body
children?: ChildrenOrFunction<PopoverRenderProps>
className?: ClassNameOrFunction<PopoverRenderProps>
containerPadding?: number = 12
defaultOpen?: boolean
isEntering?: boolean
isExiting?: boolean
isKeyboardDismissDisabled?: boolean = false
isNonModal?: boolean
isOpen?: boolean
maxHeight?: number
offset?: number = 8
onOpenChange?: (boolean) => void
placement?: Placement = 'bottom'
scrollRef?: RefObject<Element | null> = overlayRef
shouldCloseOnInteractOutside?: (Element) => boolean
shouldFlip?: boolean = true
shouldUpdatePosition?: boolean = true
slot?: string | null
style?: StyleOrFunction<PopoverRenderProps>
trigger?: string
triggerRef?: RefObject<Element | null>
} /react-aria-components:Table Table {
aria-describedby?: string
aria-details?: string
aria-label?: string
aria-labelledby?: string
children?: ReactNode
className?: ClassNameOrFunction<TableRenderProps>
defaultSelectedKeys?: 'all' | Iterable<Key>
disabledBehavior?: DisabledBehavior = "selection"
disabledKeys?: Iterable<Key>
disallowEmptySelection?: boolean
dragAndDropHooks?: DragAndDropHooks
escapeKeyBehavior?: 'clearSelection' | 'none' = 'clearSelection'
+ keyboardNavigationBehavior?: 'arrow' | 'tab' = 'arrow'
onRowAction?: (Key) => void
onSelectionChange?: (Selection) => void
onSortChange?: (SortDescriptor) => any
selectedKeys?: 'all' | Iterable<Key>
selectionMode?: SelectionMode
shouldSelectOnPressUp?: boolean
slot?: string | null
sortDescriptor?: SortDescriptor
style?: StyleOrFunction<TableRenderProps>
} /react-aria-components:VisuallyHidden-VisuallyHidden {
- children?: ReactNode
- className?: string | undefined
- elementType?: string | JSXElementConstructor<any> = 'div'
- id?: string | undefined
- isFocusable?: boolean
- role?: AriaRole | undefined
- style?: CSSProperties | undefined
- tabIndex?: number | undefined
-} /react-aria-components:PopoverProps PopoverProps {
aria-describedby?: string
aria-details?: string
aria-label?: string
aria-labelledby?: string
arrowBoundaryOffset?: number = 0
- arrowRef?: RefObject<Element | null>
boundaryElement?: Element = document.body
children?: ChildrenOrFunction<PopoverRenderProps>
className?: ClassNameOrFunction<PopoverRenderProps>
containerPadding?: number = 12
defaultOpen?: boolean
isEntering?: boolean
isExiting?: boolean
isKeyboardDismissDisabled?: boolean = false
isNonModal?: boolean
isOpen?: boolean
maxHeight?: number
offset?: number = 8
onOpenChange?: (boolean) => void
placement?: Placement = 'bottom'
scrollRef?: RefObject<Element | null> = overlayRef
shouldCloseOnInteractOutside?: (Element) => boolean
shouldFlip?: boolean = true
shouldUpdatePosition?: boolean = true
slot?: string | null
style?: StyleOrFunction<PopoverRenderProps>
trigger?: string
triggerRef?: RefObject<Element | null>
} /react-aria-components:TableProps TableProps {
aria-describedby?: string
aria-details?: string
aria-label?: string
aria-labelledby?: string
children?: ReactNode
className?: ClassNameOrFunction<TableRenderProps>
defaultSelectedKeys?: 'all' | Iterable<Key>
disabledBehavior?: DisabledBehavior = "selection"
disabledKeys?: Iterable<Key>
disallowEmptySelection?: boolean
dragAndDropHooks?: DragAndDropHooks
escapeKeyBehavior?: 'clearSelection' | 'none' = 'clearSelection'
+ keyboardNavigationBehavior?: 'arrow' | 'tab' = 'arrow'
onRowAction?: (Key) => void
onSelectionChange?: (Selection) => void
onSortChange?: (SortDescriptor) => any
selectedKeys?: 'all' | Iterable<Key>
selectionMode?: SelectionMode
shouldSelectOnPressUp?: boolean
slot?: string | null
sortDescriptor?: SortDescriptor
style?: StyleOrFunction<TableRenderProps>
} /react-aria-components:GridLayoutOptions GridLayoutOptions {
dropIndicatorThickness?: number = 2
maxColumns?: number = Infinity
- maxHorizontalSpace?: number = Infinity
maxItemSize?: Size = Infinity
minItemSize?: Size = 200 x 200
minSpace?: Size = 18 x 18
preserveAspectRatio?: boolean = false @internationalized/date/@internationalized/date:setLocalTimeZone-setLocalTimeZone {
- timeZone: string
- returnVal: undefined
-} /@internationalized/date:resetLocalTimeZone-resetLocalTimeZone {
- returnVal: undefined
-} @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/overlays/@react-aria/overlays:AriaPositionProps AriaPositionProps {
arrowBoundaryOffset?: number = 0
- arrowRef?: RefObject<Element | null>
arrowSize?: number = 0
boundaryElement?: Element = document.body
containerPadding?: number = 12
crossOffset?: number = 0
maxHeight?: number
offset?: number = 0
onClose?: () => void | null
overlayRef: RefObject<Element | null>
placement?: Placement = 'bottom'
scrollRef?: RefObject<Element | null> = overlayRef
shouldFlip?: boolean = true
shouldUpdatePosition?: boolean = true
targetRef: RefObject<Element | null>
} /@react-aria/overlays:PositionAria PositionAria {
arrowProps: DOMAttributes
overlayProps: DOMAttributes
placement: PlacementAxis | null
- triggerOrigin: {
- x: number
- y: number
-} | null
updatePosition: () => void
} /@react-aria/overlays:AriaPopoverProps AriaPopoverProps {
arrowBoundaryOffset?: number = 0
- arrowRef?: RefObject<Element | null>
arrowSize?: number = 0
boundaryElement?: Element = document.body
containerPadding?: number = 12
crossOffset?: number = 0
isKeyboardDismissDisabled?: boolean = false
isNonModal?: boolean
maxHeight?: number
offset?: number = 0
placement?: Placement = 'bottom'
popoverRef: RefObject<Element | null>
scrollRef?: RefObject<Element | null> = overlayRef
shouldCloseOnInteractOutside?: (Element) => boolean
shouldFlip?: boolean = true
shouldUpdatePosition?: boolean = true
triggerRef: RefObject<Element | null>
} /@react-aria/overlays:PopoverAria PopoverAria {
arrowProps: DOMAttributes
placement: PlacementAxis | null
popoverProps: DOMAttributes
- triggerOrigin: {
- x: number
- y: number
-} | null
underlayProps: DOMAttributes
} @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-spectrum/overlays/@react-spectrum/overlays:Popover Popover {
UNSAFE_className?: string
UNSAFE_style?: CSSProperties
alignSelf?: Responsive<'auto' | 'normal' | 'start' | 'end' | 'center' | 'flex-start' | 'flex-end' | 'self-start' | 'self-end' | 'stretch'>
arrowBoundaryOffset?: number = 0
- arrowRef?: RefObject<Element | null>
arrowSize?: number = 0
bottom?: Responsive<DimensionValue>
boundaryElement?: Element = document.body
children: ReactNode
containerPadding?: number = 12
crossOffset?: number = 0
disableFocusManagement?: boolean
enableBothDismissButtons?: boolean
end?: Responsive<DimensionValue>
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>
groupRef?: RefObject<Element | null>
height?: Responsive<DimensionValue>
hideArrow?: boolean
isDisabled?: boolean
isHidden?: Responsive<boolean>
isKeyboardDismissDisabled?: boolean = false
isNonModal?: 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>
offset?: number = 0
onBlurWithin?: (FocusEvent) => void
onDismissButtonPress?: () => void
onEnter?: () => void
onEntered?: () => void
onEntering?: () => void
onExit?: () => void
onExited?: () => void
onExiting?: () => void
onFocusWithin?: (FocusEvent) => void
onFocusWithinChange?: (boolean) => void
order?: Responsive<number>
placement?: Placement = 'bottom'
position?: Responsive<'static' | 'relative' | 'absolute' | 'fixed' | 'sticky'>
right?: Responsive<DimensionValue>
scrollRef?: RefObject<Element | null> = overlayRef
shouldCloseOnInteractOutside?: (Element) => boolean
shouldContainFocus?: boolean
shouldFlip?: boolean = true
shouldUpdatePosition?: boolean = true
start?: Responsive<DimensionValue>
state: OverlayTriggerState
top?: Responsive<DimensionValue>
triggerRef: RefObject<Element | null>
width?: Responsive<DimensionValue>
zIndex?: Responsive<number>
} @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
defaultSelectedKeys?: 'all' | Iterable<Key>
density?: 'compact' | 'spacious' | 'regular' = 'regular'
disabledKeys?: Iterable<Key>
disallowEmptySelection?: boolean
escapeKeyBehavior?: 'clearSelection' | 'none' = 'clearSelection'
isQuiet?: boolean
+ keyboardNavigationBehavior?: 'arrow' | 'tab' = 'arrow'
loadingState?: LoadingState
onAction?: (Key) => void
onLoadMore?: () => any
onResize?: (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
} /@react-spectrum/s2:PopoverProps PopoverProps {
UNSAFE_className?: UnsafeClassName
UNSAFE_style?: CSSProperties
aria-describedby?: string
aria-details?: string
aria-label?: string
aria-labelledby?: string
- arrowRef?: RefObject<Element | null>
boundaryElement?: Element = document.body
children?: ChildrenOrFunction<PopoverRenderProps>
className?: ClassNameOrFunction<PopoverRenderProps>
containerPadding?: number = 12
defaultOpen?: boolean
hideArrow?: boolean = false
isEntering?: boolean
isExiting?: boolean
isOpen?: boolean
maxHeight?: number
offset?: number = 8
onOpenChange?: (boolean) => void
placement?: Placement = 'bottom'
scrollRef?: RefObject<Element | null> = overlayRef
shouldFlip?: boolean = true
size?: 'S' | 'M' | 'L'
slot?: string | null
style?: StyleOrFunction<PopoverRenderProps>
styles?: StyleString
trigger?: string
triggerRef?: RefObject<Element | null>
} /@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
defaultSelectedKeys?: 'all' | Iterable<Key>
density?: 'compact' | 'spacious' | 'regular' = 'regular'
disabledKeys?: Iterable<Key>
disallowEmptySelection?: boolean
escapeKeyBehavior?: 'clearSelection' | 'none' = 'clearSelection'
isQuiet?: boolean
+ keyboardNavigationBehavior?: 'arrow' | 'tab' = 'arrow'
loadingState?: LoadingState
onAction?: (Key) => void
onLoadMore?: () => any
onResize?: (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
} @react-stately/data/@react-stately/data:ListData ListData <T> {
- addKeysToSelection: (Selection) => void
append: (Array<T>) => void
filterText: string
getItem: (Key) => T | undefined
insert: (number, Array<T>) => void
insertAfter: (Key, Array<T>) => void
insertBefore: (Key, Array<T>) => void
items: Array<T>
move: (Key, number) => void
moveAfter: (Key, Iterable<Key>) => void
moveBefore: (Key, Iterable<Key>) => void
prepend: (Array<T>) => void
remove: (Array<Key>) => void
- removeKeysFromSelection: (Selection) => void
removeSelectedItems: () => void
selectedKeys: Selection
setFilterText: (string) => void
setSelectedKeys: (Selection) => void
} /@react-stately/data:AsyncListData AsyncListData <T> {
- addKeysToSelection: (Selection) => void
append: (Array<T>) => void
error?: Error
filterText: string
getItem: (Key) => T | undefined
insert: (number, Array<T>) => void
insertAfter: (Key, Array<T>) => void
insertBefore: (Key, Array<T>) => void
isLoading: boolean
items: Array<T>
loadMore: () => void
loadingState: LoadingState
move: (Key, number) => void
moveAfter: (Key, Iterable<Key>) => void
moveBefore: (Key, Iterable<Key>) => void
prepend: (Array<T>) => void
reload: () => void
remove: (Array<Key>) => void
- removeKeysFromSelection: (Selection) => void
removeSelectedItems: () => void
selectedKeys: Selection
setFilterText: (string) => void
setSelectedKeys: (Selection) => void
sortDescriptor?: SortDescriptor
update: (Key, T) => void
} @react-stately/layout/@react-stately/layout:GridLayoutOptions GridLayoutOptions {
dropIndicatorThickness?: number = 2
maxColumns?: number = Infinity
- maxHorizontalSpace?: number = Infinity
maxItemSize?: Size = Infinity
minItemSize?: Size = 200 x 200
minSpace?: Size = 18 x 18
preserveAspectRatio?: boolean = false |
Closes RSP Component Milestones (view)
This uses the approach of only handling events on grid/row/cells and not on their contents.
Note: this does not address the event leaking brought up in another issue. That work is still to come and may make this unnecessary, however, that touches a much wider set of components and hooks with much more testing needed.
The approach in this PR allows us to more quickly get TextFields working in our grids, and we'll be able to write tests against it which can further inform our other work.
Adding more tests monday
✅ Pull Request Checklist:
📝 Test Instructions:
Added two stories, one for RAC GridList and one for S2 TableView which shows a textfield inside an item. You must use tab keyboard navigation on the grids in order to get to the field.
Notes: If typing in the inputs, selection should not occur, nor should type ahead.
I did not apply any logic in the TableView to remember the textfield contents, so when reused, they may display the contents of a previous cell.
🧢 Your Project: