-
Notifications
You must be signed in to change notification settings - Fork 1.3k
feat: Automatically detect if wrapped Autocomplete collection supports virtual focus #8862
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
Conversation
…rable collection and if said collection supports virtual focus
// moving focus back to the subtriggers | ||
let isMobileScreenReader = getInteractionModality() === 'virtual' && (isIOS() || isAndroid()); | ||
let shouldUseVirtualFocus = !isMobileScreenReader && !disableVirtualFocus; | ||
let [shouldUseVirtualFocus, setShouldUseVirtualFocus] = useState(!isMobileScreenReader && !disableVirtualFocus); |
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.
We still want to keep disableVirtualFocus
as a prop so a user can opt out of the virtual focus behavior based on their use case
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.
For context, the Popover used to render a Dialog for the user, but this meant Dialog specific behavior like auto focusing the Dialog would make it impossible for a user to use the S2 Popover for a custom Combobox. We've opted to instead rely on RAC Popover applying a "dialog" role instead since users don't have access to PopoverBase
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 think this makes sense, I had it on my list of things to explore since the change in RAC came after we wrote a good chunk of S2 popover usage
let contextProps; | ||
[contextProps] = useContextProps({}, null, SelectableCollectionContext); | ||
let {filter, ...collectionProps} = contextProps; | ||
[props, ref] = useContextProps(props, ref, SelectableCollectionContext); |
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.
we now use the ref from useAutocomplete so the Autocomplete input knows it has been connected to a collection element it can filter
Build successful! 🎉 |
…tocomplete_updates
Build successful! 🎉 |
}); | ||
|
||
let turnOffVirtualFocus = useCallback(() => { | ||
setShouldUseVirtualFocus(false); |
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 probably needs to stopPropagation so that nested collections don't accidentally tell one farther up as well
Build successful! 🎉 |
Build successful! 🎉 |
Build successful! 🎉 |
|
||
let { | ||
children, | ||
disableInnerDiv, |
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.
super gross, maybe we just export PopoverBase as something like CustomPopover to mirror CustomDialog
b667d5b
to
6ad1bbc
Compare
Build successful! 🎉 |
Build successful! 🎉 |
// Subtract by 2 since these widths are set on the inner div rather than on the outermost div element that has | ||
// a border | ||
width: menuWidth ? `calc(${menuWidth}px - 2 * var(--s2-container-border-width))` : undefined, | ||
'--trigger-width': `calc(${triggerWidth} - 2 * var(--s2-container-border-width))` |
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.
unfortunate consequence of switching to Popover from PopoverBase
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.
not sure i understand this comment
wish we didn't need to use UNSAFE style to declare variables that are arbitrary and the result of calculations
wonder if we should consider a new style prop just for variables, that should be safe
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.
its because previously these width values would go to the outer div that had the border applied to it, but now that we use Popover these values go to the inner div since we need to modify that div's padding/overflow. I'd really like if there was just a single container div per say, but applying overflow: auto
to the outer div will hide the popover arrow...
// TODO: not sure how best to type styles so it also can accept arbitrary css vars | ||
// @ts-ignore | ||
styles={style({ | ||
marginStart: { | ||
isQuiet: -12 | ||
'--cross-offset': { | ||
type: 'width', | ||
value: -12 |
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.
Open to any suggestions on how best to type styles in Popover to accept arbitrary values for setting css variables, might be forgetting where we do this. From what I can tell, this pattern usually happens when setting className
which accepts a style string
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.
is this?
// TODO: not sure how best to type styles so it also can accept arbitrary css vars
you'd need to set it in UNSAFE_style, otherwise you need to specify every possible value and take it as an argument
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, but setting it in UNSAFE_style
doesn't allow for the above syntax and thus we'd need to manually transform that value to rems (or for cases like borderOffsetWidth
, the value is actually in px) essentially duplicating a bunch of the work done in the theme
type popoverOverrides = [ | ||
'overflow', | ||
'padding', | ||
'paddingStart', | ||
'paddingEnd', | ||
'paddingTop', | ||
'paddingBottom', | ||
'paddingX', | ||
'paddingY', | ||
'display', | ||
'flexDirection', | ||
'gap' | ||
]; |
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.
new overrides that users can set on the Popover. These are set on the inner div
…tocomplete_updates
Build successful! 🎉 |
// Subtract by 2 since these widths are set on the inner div rather than on the outermost div element that has | ||
// a border | ||
width: menuWidth ? `calc(${menuWidth}px - 2 * var(--s2-container-border-width))` : undefined, | ||
'--trigger-width': `calc(${triggerWidth} - 2 * var(--s2-container-border-width))` |
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.
not sure i understand this comment
wish we didn't need to use UNSAFE style to declare variables that are arbitrary and the result of calculations
wonder if we should consider a new style prop just for variables, that should be safe
// TODO: not sure how best to type styles so it also can accept arbitrary css vars | ||
// @ts-ignore | ||
styles={style({ | ||
marginStart: { | ||
isQuiet: -12 | ||
'--cross-offset': { | ||
type: 'width', | ||
value: -12 |
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.
is this?
// TODO: not sure how best to type styles so it also can accept arbitrary css vars
you'd need to set it in UNSAFE_style, otherwise you need to specify every possible value and take it as an argument
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 think this makes sense, I had it on my list of things to explore since the change in RAC came after we wrote a good chunk of S2 popover usage
Build successful! 🎉 |
collection should only have a tab index if it isnt using virtual focus
turns out the border of the popover shouldnt be included in the total width calculation, it is considered outside of the popover so no need to adjust. The border will be removed in favor of a box shadow to conform with update designs later
Build successful! 🎉 |
Build successful! 🎉 |
as per discussion with team, if someone wants to modify their popover internals, they are expected to add a inner wrapping div themselves and turn off padding like custom dialog.
Build successful! 🎉 |
Build successful! 🎉 |
# Conflicts: # packages/@react-spectrum/s2/src/Popover.tsx
Build successful! 🎉 |
## API Changes
react-aria-components/react-aria-components:SelectableCollectionContext+SelectableCollectionContext {
+ UNTYPED
+} /react-aria-components:FieldInputContext+FieldInputContext {
+ UNTYPED
+} /react-aria-components:SelectableCollectionContextValue+SelectableCollectionContextValue <T> {
+ aria-describedby?: string
+ aria-details?: string
+ aria-label?: string
+ aria-labelledby?: string
+ disallowTypeAhead?: boolean
+ filter?: (string, Node<T>) => boolean
+ id?: string
+ shouldUseVirtualFocus?: boolean
+} @react-spectrum/s2/@react-spectrum/s2:Popover Popover {
UNSAFE_className?: UnsafeClassName
UNSAFE_style?: CSSProperties
aria-describedby?: string
aria-details?: string
aria-label?: string
aria-labelledby?: string
- children?: ReactNode | (DialogRenderProps) => ReactNode
+ children?: ChildrenOrFunction<PopoverRenderProps>
containerPadding?: number = 12
crossOffset?: number = 0
hideArrow?: boolean = false
id?: string
isOpen?: boolean
offset?: number = 8
onOpenChange?: (boolean) => void
+ padding?: 'default' | 'none' = 'default'
placement?: Placement = 'bottom'
role?: 'dialog' | 'alertdialog' = 'dialog'
shouldFlip?: boolean = true
size?: 'S' | 'M' | 'L'
slot?: string | null
- styles?: StylesProp
+ styles?: PopoverStylesProp
triggerRef?: RefObject<Element | null>
} /@react-spectrum/s2:ColorSchemeContext+ColorSchemeContext {
+ UNTYPED
+} |
Also attempts to replace Dialog in S2 Popover so users trying to recreate a Combobox/Autocomplete like experience can simply use Popover
✅ Pull Request Checklist:
📝 Test Instructions:
Autocomplete behavior should largely be the same as it already is on main, simply sanity check that
aria-activedescendant
only appears when the wrapped collection support virtual focus. For S2 components that used to use Popover (ComboBox, ContextualHelp, DatePicker, Menu, Picker, TabsPicker) check that their styling and behavior haven't changed with the refactor.🧢 Your Project:
RSP