Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(WIP) Add subdialog support to Menu and Autocomplete #7561

Draft
wants to merge 7 commits into
base: autocomplete_audit_for_release
Choose a base branch
from

Conversation

LFDanLu
Copy link
Member

@LFDanLu LFDanLu commented Jan 3, 2025

Closes

✅ Pull Request Checklist:

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

📝 Test Instructions:

🧢 Your Project:

@rspbot
Copy link

rspbot commented Jan 3, 2025

…ually focused

typically submenus dont have focus restore turned on since it would move focus manually back to the trigger when keyboard closing the menu. However, we cant move focus to virtually focused triggers so enable focus restore on the submenu in these cases
@rspbot
Copy link

rspbot commented Jan 3, 2025

@rspbot
Copy link

rspbot commented Jan 3, 2025

## API Changes

react-aria-components

/react-aria-components:Autocomplete

-Autocomplete {
-  children: ReactNode
-  defaultFilter?: (string, string) => boolean = contains
-  defaultInputValue?: string
-  inputValue?: string
-  onInputChange?: (string) => void
-  slot?: string | null
-}

/react-aria-components:AutocompleteContext

-AutocompleteContext {
-  UNTYPED
-}

/react-aria-components:AutocompleteStateContext

-AutocompleteStateContext {
-  UNTYPED
-}

/react-aria-components:InternalAutocompleteContext

-InternalAutocompleteContext {
-  UNTYPED
-}

/react-aria-components:Dialog

 Dialog {
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   children?: ReactNode | (DialogRenderProps) => ReactNode
   className?: string
   id?: string
+  onKeyDown?: (KeyboardEvent) => void
+  onKeyUp?: (KeyboardEvent) => void
   role?: 'dialog' | 'alertdialog' = 'dialog'
   slot?: string | null
   style?: CSSProperties
 }

/react-aria-components:DialogProps

 DialogProps {
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   children?: ReactNode | (DialogRenderProps) => ReactNode
   className?: string
   id?: string
+  onKeyDown?: (KeyboardEvent) => void
+  onKeyUp?: (KeyboardEvent) => void
   role?: 'dialog' | 'alertdialog' = 'dialog'
   slot?: string | null
   style?: CSSProperties
 }

/react-aria-components:UNSTABLE_Autocomplete

+UNSTABLE_Autocomplete {
+  children: ReactNode
+  defaultInputValue?: string
+  filter?: (string, string) => boolean
+  inputValue?: string
+  onInputChange?: (string) => void
+  slot?: string | null
+}

/react-aria-components:UNSTABLE_AutocompleteContext

+UNSTABLE_AutocompleteContext {
+  UNTYPED
+}

/react-aria-components:UNSTABLE_AutocompleteStateContext

+UNSTABLE_AutocompleteStateContext {
+  UNTYPED
+}

@react-aria/autocomplete

/@react-aria/autocomplete:useAutocomplete

-useAutocomplete {
-  props: AriaAutocompleteOptions
-  state: AutocompleteState
-  returnVal: undefined
-}

/@react-aria/autocomplete:AriaAutocompleteProps

 AriaAutocompleteProps {
   children: ReactNode
-  defaultFilter?: (string, string) => boolean = contains
   defaultInputValue?: string
+  filter?: (string, string) => boolean
   inputValue?: string
   onInputChange?: (string) => void
 }

/@react-aria/autocomplete:AriaAutocompleteOptions

 AriaAutocompleteOptions {
   collectionRef: RefObject<HTMLElement | null>
-  defaultFilter?: (string, string) => boolean = contains
   defaultInputValue?: string
+  filter?: (string, string) => boolean
   inputRef: RefObject<HTMLInputElement | null>
   inputValue?: string
   onInputChange?: (string) => void
 }

/@react-aria/autocomplete:AutocompleteAria

 AutocompleteAria {
   collectionProps: CollectionOptions
   collectionRef: RefObject<HTMLElement | null>
-  filterFn: (string) => boolean
+  filterFn?: (string) => boolean
   inputProps: InputHTMLAttributes<HTMLInputElement>
 }

/@react-aria/autocomplete:UNSTABLE_useAutocomplete

+UNSTABLE_useAutocomplete {
+  props: AriaAutocompleteOptions
+  state: AutocompleteState
+  returnVal: undefined
+}

@react-aria/dialog

/@react-aria/dialog:AriaDialogProps

 AriaDialogProps {
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   id?: string
+  onKeyDown?: (KeyboardEvent) => void
+  onKeyUp?: (KeyboardEvent) => void
   role?: 'dialog' | 'alertdialog' = 'dialog'
 }

@react-aria/menu

/@react-aria/menu:AriaSubmenuTriggerProps

 AriaSubmenuTriggerProps {
   delay?: number = 200
   isDisabled?: boolean
+  isVirtualFocus?: boolean
   parentMenuRef: RefObject<HTMLElement | null>
   submenuRef: RefObject<HTMLElement | null>
   type?: 'dialog' | 'menu'
 }

@react-aria/utils

/@react-aria/utils:useUpdateLayoutEffect

+useUpdateLayoutEffect {
+  effect: EffectCallback
+  dependencies: Array<any>
+  returnVal: undefined
+}

/@react-aria/utils:isCtrlKeyPressed

+isCtrlKeyPressed {
+  e: Event
+  returnVal: undefined
+}

@react-spectrum/dialog

/@react-spectrum/dialog:Dialog

 Dialog {
   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: ReactNode
   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>
   height?: Responsive<DimensionValue>
   id?: string
   isDismissable?: boolean
   isHidden?: Responsive<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>
   onDismiss?: () => void
+  onKeyDown?: (KeyboardEvent) => void
+  onKeyUp?: (KeyboardEvent) => void
   order?: Responsive<number>
   position?: Responsive<'static' | 'relative' | 'absolute' | 'fixed' | 'sticky'>
   right?: Responsive<DimensionValue>
   role?: 'dialog' | 'alertdialog' = 'dialog'
   start?: Responsive<DimensionValue>
   top?: Responsive<DimensionValue>
   width?: Responsive<DimensionValue>
   zIndex?: Responsive<number>
 }

/@react-spectrum/dialog:SpectrumDialogProps

 SpectrumDialogProps {
   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: ReactNode
   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>
   height?: Responsive<DimensionValue>
   id?: string
   isDismissable?: boolean
   isHidden?: Responsive<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>
   onDismiss?: () => void
+  onKeyDown?: (KeyboardEvent) => void
+  onKeyUp?: (KeyboardEvent) => void
   order?: Responsive<number>
   position?: Responsive<'static' | 'relative' | 'absolute' | 'fixed' | 'sticky'>
   right?: Responsive<DimensionValue>
   role?: 'dialog' | 'alertdialog' = 'dialog'
   start?: Responsive<DimensionValue>
   top?: Responsive<DimensionValue>
   width?: Responsive<DimensionValue>
   zIndex?: Responsive<number>
 }

@react-spectrum/s2

/@react-spectrum/s2:Dialog

 Dialog {
   UNSAFE_className?: string
   UNSAFE_style?: CSSProperties
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   children?: ReactNode | (DialogRenderProps) => ReactNode
   id?: string
   isDismissible?: boolean
   isKeyboardDismissDisabled?: boolean
+  onKeyDown?: (KeyboardEvent) => void
+  onKeyUp?: (KeyboardEvent) => void
   role?: 'dialog' | 'alertdialog' = 'dialog'
   size?: 'S' | 'M' | 'L' = 'M'
   slot?: string | null
   styles?: StylesProp

/@react-spectrum/s2:CustomDialog

 CustomDialog {
   UNSAFE_className?: string
   UNSAFE_style?: CSSProperties
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   children?: ReactNode | (DialogRenderProps) => ReactNode
   id?: string
   isDismissible?: boolean
   isKeyboardDismissDisabled?: boolean
+  onKeyDown?: (KeyboardEvent) => void
+  onKeyUp?: (KeyboardEvent) => void
   padding?: 'default' | 'none' = 'default'
   role?: 'dialog' | 'alertdialog' = 'dialog'
   size?: 'S' | 'M' | 'L' | 'fullscreen' | 'fullscreenTakeover'
   slot?: string | null
 }

/@react-spectrum/s2:FullscreenDialog

 FullscreenDialog {
   UNSAFE_className?: string
   UNSAFE_style?: CSSProperties
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   children?: ReactNode | (DialogRenderProps) => ReactNode
   id?: string
   isKeyboardDismissDisabled?: boolean
+  onKeyDown?: (KeyboardEvent) => void
+  onKeyUp?: (KeyboardEvent) => void
   role?: 'dialog' | 'alertdialog' = 'dialog'
   slot?: string | null
   styles?: StylesProp
   variant?: 'fullscreen' | 'fullscreenTakeover' = "fullscreen"

/@react-spectrum/s2:Popover

 Popover {
   UNSAFE_className?: string
   UNSAFE_style?: CSSProperties
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   children?: ReactNode | (DialogRenderProps) => ReactNode
   containerPadding?: number = 12
   crossOffset?: number = 0
   hideArrow?: boolean = false
   id?: string
   offset?: number = 8
+  onKeyDown?: (KeyboardEvent) => void
+  onKeyUp?: (KeyboardEvent) => void
   placement?: Placement = 'bottom'
   role?: 'dialog' | 'alertdialog' = 'dialog'
   shouldFlip?: boolean = true
   size?: 'S' | 'M' | 'L'
   styles?: StylesProp
 }

/@react-spectrum/s2:DialogProps

 DialogProps {
   UNSAFE_className?: string
   UNSAFE_style?: CSSProperties
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   children?: ReactNode | (DialogRenderProps) => ReactNode
   id?: string
   isDismissible?: boolean
   isKeyboardDismissDisabled?: boolean
+  onKeyDown?: (KeyboardEvent) => void
+  onKeyUp?: (KeyboardEvent) => void
   role?: 'dialog' | 'alertdialog' = 'dialog'
   size?: 'S' | 'M' | 'L' = 'M'
   slot?: string | null
   styles?: StylesProp

/@react-spectrum/s2:CustomDialogProps

 CustomDialogProps {
   UNSAFE_className?: string
   UNSAFE_style?: CSSProperties
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   children?: ReactNode | (DialogRenderProps) => ReactNode
   id?: string
   isDismissible?: boolean
   isKeyboardDismissDisabled?: boolean
+  onKeyDown?: (KeyboardEvent) => void
+  onKeyUp?: (KeyboardEvent) => void
   padding?: 'default' | 'none' = 'default'
   role?: 'dialog' | 'alertdialog' = 'dialog'
   size?: 'S' | 'M' | 'L' | 'fullscreen' | 'fullscreenTakeover'
   slot?: string | null
 }

/@react-spectrum/s2:FullscreenDialogProps

 FullscreenDialogProps {
   UNSAFE_className?: string
   UNSAFE_style?: CSSProperties
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   children?: ReactNode | (DialogRenderProps) => ReactNode
   id?: string
   isKeyboardDismissDisabled?: boolean
+  onKeyDown?: (KeyboardEvent) => void
+  onKeyUp?: (KeyboardEvent) => void
   role?: 'dialog' | 'alertdialog' = 'dialog'
   slot?: string | null
   styles?: StylesProp
   variant?: 'fullscreen' | 'fullscreenTakeover' = "fullscreen"

@react-stately/autocomplete

/@react-stately/autocomplete:useAutocompleteState

-useAutocompleteState {
-  props: AutocompleteStateOptions
-  returnVal: undefined
-}

/@react-stately/autocomplete:UNSTABLE_useAutocompleteState

+UNSTABLE_useAutocompleteState {
+  props: AutocompleteStateOptions
+  returnVal: undefined
+}

@react-stately/selection

/@react-stately/selection:MultipleSelectionManager

 MultipleSelectionManager {
   canSelectItem: (Key) => boolean
   childFocusStrategy: FocusStrategy | null
   clearSelection: () => void
+  collection: Collection<Node<unknown>>
   disabledBehavior: DisabledBehavior
   disabledKeys: Set<Key>
   disallowEmptySelection?: boolean
   extendSelection: (Key) => void
   focusedKey: Key | null
   getItemProps: (Key) => any
   isDisabled: (Key) => boolean
   isEmpty: boolean
   isFocused: boolean
   isLink: (Key) => boolean
   isSelectAll: boolean
   isSelected: (Key) => boolean
   isSelectionEqual: (Set<Key>) => boolean
   lastSelectedKey: Key | null
   replaceSelection: (Key) => void
   select: (Key, PressEvent | LongPressEvent | PointerEvent) => void
   selectAll: () => void
   selectedKeys: Set<Key>
   selectionBehavior: SelectionBehavior
   selectionMode: SelectionMode
   setFocused: (boolean) => void
   setFocusedKey: (Key | null, FocusStrategy) => void
   setSelectedKeys: (Iterable<Key>) => void
   setSelectionBehavior: (SelectionBehavior) => void
   toggleSelectAll: () => void
   toggleSelection: (Key) => void
 }

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

Successfully merging this pull request may close these issues.

2 participants