Skip to content

fix: Move focus to newest item when using "scroll to bottom" button, transitions, and other follow ups to Chat component#10172

Open
LFDanLu wants to merge 11 commits into
mainfrom
ai_thread_testing_followup
Open

fix: Move focus to newest item when using "scroll to bottom" button, transitions, and other follow ups to Chat component#10172
LFDanLu wants to merge 11 commits into
mainfrom
ai_thread_testing_followup

Conversation

@LFDanLu

@LFDanLu LFDanLu commented Jun 9, 2026

Copy link
Copy Markdown
Member

Follow ups to comments in #10045

Fixes the following:

  • strange user message text wrapping the streaming chat story
  • focuses the newest item when using the scroll to bottom button
  • tabbing now takes you out of the chat properly without hitting every toggle button group
  • rename to Chat and Thread from Thread and ThreadList
  • intl, transitions to scroll to bottom button, adding UNSTABLE to the UNSTABLE_focusonEntry, announcing new items if focus is inside the Chat but out side the Thread

✅ 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:

Test the Streaming Chat story, see the fixes above to see what to check

🧢 Your Project:

RSP

LFDanLu added 2 commits June 8, 2026 16:39
used to hit every toggle button group in the thread before exiting
@github-actions github-actions Bot added the v3 label Jun 9, 2026
@rspbot

rspbot commented Jun 9, 2026

Copy link
Copy Markdown

@LFDanLu LFDanLu changed the title fix: (WIP) Move focus to newest item when using "scroll to bottom" button and fix tabbing out of Thread fix: Move focus to newest item when using "scroll to bottom" button, transitions, and other follow ups to Chat component Jun 11, 2026
@rspbot

rspbot commented Jun 11, 2026

Copy link
Copy Markdown

@LFDanLu LFDanLu marked this pull request as ready for review June 11, 2026 18:36
@rspbot

rspbot commented Jun 11, 2026

Copy link
Copy Markdown

yihuiliao
yihuiliao previously approved these changes Jun 12, 2026
@rspbot

rspbot commented Jun 12, 2026

Copy link
Copy Markdown

yihuiliao
yihuiliao previously approved these changes Jun 12, 2026
let isChatFocusWithinRef = useRef(false);
let hasNewMessagesRef = useRef(false);
let timeout = useRef<ReturnType<typeof setTimeout> | null>(null);
let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-spectrum/s2');

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wrong package name?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ugh copy pasta, thanks for the catch. I'll fix the other ones in the rest of the files

[InternalChatContext, {announceItem, setIsNearBottom, setScrollElement}],
[ThreadScrollButtonContext, {isNearBottom, scrollToBottom}],
[
TextFieldContext,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah we'll need to update this to support TokenField too I guess.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep, I can update once your PR goes in. Maybe some kind of generic context could be used to pass this, ideally we'd just need to see if the user is inside what we consider the "field" regardless of what elements those would be

onScroll={handleScroll}
keyboardNavigationBehavior="tab"
focusOnEntry={focusOnEntry}
UNSTABLE_focusonEntry={UNSTABLE_focusonEntry}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the On be capitalized?

@@ -31,21 +32,43 @@ import {
GridListItemProps,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this file supposed to exist still since it is also in Chat.tsx?

@rspbot

rspbot commented Jun 13, 2026

Copy link
Copy Markdown

@rspbot

rspbot commented Jun 13, 2026

Copy link
Copy Markdown
## API Changes

react-aria-components

/react-aria-components:Menu

 Menu <T> {
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   autoFocus?: boolean | FocusStrategy
   children?: ReactNode | (T) => ReactNode
   className?: ClassNameOrFunction<MenuRenderProps> = 'react-aria-Menu'
   defaultSelectedKeys?: 'all' | Iterable<Key>
   dependencies?: ReadonlyArray<any>
   disabledKeys?: Iterable<Key>
   disallowEmptySelection?: boolean
   escapeKeyBehavior?: 'clearSelection' | 'none' = 'clearSelection'
   id?: string
   items?: Iterable<T>
-  onAction?: (Key, T) => void
+  onAction?: (Key) => void
   onClose?: () => void
   onSelectionChange?: (Selection) => void
   render?: DOMRenderFunction<keyof React.JSX.IntrinsicElements, MenuRenderProps>
   renderEmptyState?: () => ReactNode
   selectionMode?: SelectionMode
   shouldCloseOnSelect?: boolean
   shouldFocusWrap?: boolean
   slot?: string | null
   style?: StyleOrFunction<MenuRenderProps>
 }

/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> = 'react-aria-Popover'
   containerPadding?: number = 12
   crossOffset?: number = 0
   defaultOpen?: boolean
-  getTargetRect?: (Element) => DOMRect | null | undefined = target.getBoundingClientRect()
   isEntering?: boolean
   isExiting?: boolean
   isKeyboardDismissDisabled?: boolean = false
   isNonModal?: boolean
   maxHeight?: number
   offset?: number = 8
   onOpenChange?: (boolean) => void
   placement?: Placement = 'bottom'
   render?: DOMRenderFunction<keyof React.JSX.IntrinsicElements, PopoverRenderProps>
   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:MenuProps

 MenuProps <T> {
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   autoFocus?: boolean | FocusStrategy
   children?: ReactNode | (T) => ReactNode
   className?: ClassNameOrFunction<MenuRenderProps> = 'react-aria-Menu'
   defaultSelectedKeys?: 'all' | Iterable<Key>
   dependencies?: ReadonlyArray<any>
   disabledKeys?: Iterable<Key>
   disallowEmptySelection?: boolean
   escapeKeyBehavior?: 'clearSelection' | 'none' = 'clearSelection'
   id?: string
   items?: Iterable<T>
-  onAction?: (Key, T) => void
+  onAction?: (Key) => void
   onClose?: () => void
   onSelectionChange?: (Selection) => void
   render?: DOMRenderFunction<keyof React.JSX.IntrinsicElements, MenuRenderProps>
   renderEmptyState?: () => ReactNode
   selectionMode?: SelectionMode
   shouldCloseOnSelect?: boolean
   shouldFocusWrap?: boolean
   slot?: string | null
   style?: StyleOrFunction<MenuRenderProps>
 }

/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> = 'react-aria-Popover'
   containerPadding?: number = 12
   crossOffset?: number = 0
   defaultOpen?: boolean
-  getTargetRect?: (Element) => DOMRect | null | undefined = target.getBoundingClientRect()
   isEntering?: boolean
   isExiting?: boolean
   isKeyboardDismissDisabled?: boolean = false
   isNonModal?: boolean
   maxHeight?: number
   offset?: number = 8
   onOpenChange?: (boolean) => void
   placement?: Placement = 'bottom'
   render?: DOMRenderFunction<keyof React.JSX.IntrinsicElements, PopoverRenderProps>
   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:DragTypes

 DragTypes {
-  has: (DragType | Array<DragType>) => boolean
+  has: (string | symbol) => boolean
 }

@react-aria/dnd

/@react-aria/dnd:DragTypes

 DragTypes {
-  has: (DragType | Array<DragType>) => boolean
+  has: (string | symbol) => boolean
 }

@react-aria/menu

/@react-aria/menu:MenuProps

 MenuProps <T> {
   autoFocus?: boolean | FocusStrategy
   children: CollectionChildren<T>
   defaultSelectedKeys?: 'all' | Iterable<Key>
   disabledKeys?: Iterable<Key>
   disallowEmptySelection?: boolean
   items?: Iterable<T>
-  onAction?: (Key, T) => void
+  onAction?: (Key) => void
   onClose?: () => void
   onSelectionChange?: (Selection) => void
   selectedKeys?: 'all' | Iterable<Key>
   selectionMode?: SelectionMode
 }

/@react-aria/menu:AriaMenuProps

 AriaMenuProps <T> {
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   autoFocus?: boolean | FocusStrategy
   children: CollectionChildren<T>
   defaultSelectedKeys?: 'all' | Iterable<Key>
   disabledKeys?: Iterable<Key>
   disallowEmptySelection?: boolean
   escapeKeyBehavior?: 'clearSelection' | 'none' = 'clearSelection'
   id?: string
   items?: Iterable<T>
-  onAction?: (Key, T) => void
+  onAction?: (Key) => void
   onClose?: () => void
   onSelectionChange?: (Selection) => void
   selectedKeys?: 'all' | Iterable<Key>
   selectionMode?: SelectionMode
 }

/@react-aria/menu:AriaMenuOptions

 AriaMenuOptions <T> {
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   autoFocus?: boolean | FocusStrategy
   defaultSelectedKeys?: 'all' | Iterable<Key>
   disabledKeys?: Iterable<Key>
   disallowEmptySelection?: boolean
   escapeKeyBehavior?: 'clearSelection' | 'none' = 'clearSelection'
   id?: string
   isVirtualized?: boolean
   items?: Iterable<T>
   keyboardDelegate?: KeyboardDelegate
-  onAction?: (Key, T) => void
+  onAction?: (Key) => void
   onClose?: () => void
   onKeyDown?: (KeyboardEvent) => void
   onKeyUp?: (KeyboardEvent) => void
   onSelectionChange?: (Selection) => void
   selectionMode?: SelectionMode
   shouldFocusWrap?: boolean
   shouldUseVirtualFocus?: 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
-  getTargetRect?: (Element) => DOMRect | null | undefined = target.getBoundingClientRect()
   isOpen?: boolean
   maxHeight?: number
   offset?: number = 0
   onClose?: () => void | null
   placement?: Placement = 'bottom'
   scrollRef?: RefObject<Element | null> = overlayRef
   shouldFlip?: boolean = true
   shouldUpdatePosition?: boolean = true
   targetRef: RefObject<Element | null>
 }

/@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
-  getTargetRect?: (Element) => DOMRect | null | undefined = target.getBoundingClientRect()
   groupRef?: RefObject<Element | null>
   isKeyboardDismissDisabled?: boolean = false
   isNonModal?: boolean
   maxHeight?: number
   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-spectrum/ai

/@react-spectrum/ai:Attachment

 Attachment {
-  UNSAFE_className?: UnsafeClassName
-  UNSAFE_style?: CSSProperties
-  aria-describedby?: string
-  aria-details?: string
-  aria-label?: string
-  aria-labelledby?: string
-  children: ReactNode | (CardRenderProps) => ReactNode
-  density?: 'compact' | 'regular' | 'spacious' = 'regular'
-  download?: boolean | string
-  href?: Href
-  hrefLang?: string
-  id?: Key
-  isDisabled?: boolean
-  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
-  size?: 'XS' | 'S' | 'M' | 'L' | 'XL' = 'M'
-  styles?: StylesProp
-  target?: HTMLAttributeAnchorTarget
-  textValue?: string
-  uploadProgress?: number
-  value?: T
-  variant?: 'primary' | 'secondary' | 'tertiary' | 'quiet' = 'primary'
+
 }

/@react-spectrum/ai:AttachmentList

-AttachmentList <T> {
+AttachmentList {
-  UNSAFE_className?: UnsafeClassName
-  UNSAFE_style?: CSSProperties
-  aria-describedby?: string
-  aria-details?: string
-  aria-label?: string
-  aria-labelledby?: string
-  children?: ReactNode | (T) => ReactNode
-  className?: string = 'react-aria-TagGroup'
-  defaultSelectedKeys?: 'all' | Iterable<Key>
-  dependencies?: ReadonlyArray<any>
-  disabledKeys?: Iterable<Key>
-  disallowEmptySelection?: boolean
-  escapeKeyBehavior?: 'clearSelection' | 'none' = 'clearSelection'
-  id?: string
-  items?: Iterable<T>
-  onAction?: (Key) => void
-  onRemove?: (Set<Key>) => void
-  onSelectionChange?: (Selection) => void
-  render?: DOMRenderFunction<keyof React.JSX.IntrinsicElements, undefined>
-  selectedKeys?: 'all' | Iterable<Key>
-  selectionBehavior?: SelectionBehavior = 'toggle'
-  selectionMode?: SelectionMode
-  shouldSelectOnPressUp?: boolean
-  slot?: string | null
-  style?: CSSProperties
-  styles?: StylesProp
+
 }

@react-spectrum/dnd

/@react-spectrum/dnd:DragTypes

 DragTypes {
-  has: (DragType | Array<DragType>) => boolean
+  has: (string | symbol) => boolean
 }

@react-spectrum/menu

/@react-spectrum/menu:Menu

 Menu <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
   autoFocus?: boolean | FocusStrategy
   bottom?: Responsive<DimensionValue>
   children: CollectionChildren<{}>
   defaultSelectedKeys?: 'all' | Iterable<Key>
   disabledKeys?: Iterable<Key>
   disallowEmptySelection?: boolean
   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>
   items?: Iterable<{}>
   justifySelf?: Responsive<'auto' | 'normal' | 'start' | 'end' | 'flex-start' | 'flex-end' | 'self-start' | 'self-end' | 'center' | 'left' | 'right' | 'stretch'>
   left?: Responsive<DimensionValue>
   margin?: Responsive<DimensionValue>
   marginBottom?: Responsive<DimensionValue>
   marginEnd?: Responsive<DimensionValue>
   marginStart?: Responsive<DimensionValue>
   marginTop?: Responsive<DimensionValue>
   marginX?: Responsive<DimensionValue>
   marginY?: Responsive<DimensionValue>
   maxHeight?: Responsive<DimensionValue>
   maxWidth?: Responsive<DimensionValue>
   minHeight?: Responsive<DimensionValue>
   minWidth?: Responsive<DimensionValue>
-  onAction?: (Key, {}) => void
+  onAction?: (Key) => void
   onClose?: () => void
   onSelectionChange?: (Selection) => void
   order?: Responsive<number>
   position?: Responsive<'static' | 'relative' | 'absolute' | 'fixed' | 'sticky'>
   selectedKeys?: 'all' | Iterable<Key>
   selectionMode?: SelectionMode
   shouldFocusWrap?: boolean
   start?: Responsive<DimensionValue>
   top?: Responsive<DimensionValue>
   width?: Responsive<DimensionValue>
   zIndex?: Responsive<number>
 }

/@react-spectrum/menu:SpectrumMenuProps

 SpectrumMenuProps <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
   autoFocus?: boolean | FocusStrategy
   bottom?: Responsive<DimensionValue>
   children: CollectionChildren<T>
   defaultSelectedKeys?: 'all' | Iterable<Key>
   disabledKeys?: Iterable<Key>
   disallowEmptySelection?: boolean
   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>
   items?: Iterable<T>
   justifySelf?: Responsive<'auto' | 'normal' | 'start' | 'end' | 'flex-start' | 'flex-end' | 'self-start' | 'self-end' | 'center' | 'left' | 'right' | 'stretch'>
   left?: Responsive<DimensionValue>
   margin?: Responsive<DimensionValue>
   marginBottom?: Responsive<DimensionValue>
   marginEnd?: Responsive<DimensionValue>
   marginStart?: Responsive<DimensionValue>
   marginTop?: Responsive<DimensionValue>
   marginX?: Responsive<DimensionValue>
   marginY?: Responsive<DimensionValue>
   maxHeight?: Responsive<DimensionValue>
   maxWidth?: Responsive<DimensionValue>
   minHeight?: Responsive<DimensionValue>
   minWidth?: Responsive<DimensionValue>
-  onAction?: (Key, T) => void
+  onAction?: (Key) => void
   onClose?: () => void
   onSelectionChange?: (Selection) => void
   order?: Responsive<number>
   position?: Responsive<'static' | 'relative' | 'absolute' | 'fixed' | 'sticky'>
   selectedKeys?: 'all' | Iterable<Key>
   selectionMode?: SelectionMode
   shouldFocusWrap?: boolean
   start?: Responsive<DimensionValue>
   top?: Responsive<DimensionValue>
   width?: Responsive<DimensionValue>
   zIndex?: Responsive<number>
 }

@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
   container?: HTMLElement
   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>
-  getTargetRect?: (Element) => DOMRect | null | undefined = target.getBoundingClientRect()
   gridArea?: Responsive<string>
   gridColumn?: Responsive<string>
   gridColumnEnd?: Responsive<string>
   gridColumnStart?: 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:ActionMenu

 ActionMenu <T> {
   UNSAFE_className?: UnsafeClassName
   UNSAFE_style?: CSSProperties
   align?: 'start' | 'end' = 'start'
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   autoFocus?: boolean
   children: ReactNode | (T) => ReactNode
   defaultOpen?: boolean
   direction?: 'bottom' | 'top' | 'left' | 'right' | 'start' | 'end' = 'bottom'
   disabledKeys?: Iterable<Key>
   id?: string
   isDisabled?: boolean
   isOpen?: boolean
   isQuiet?: boolean
   items?: Iterable<T>
   menuSize?: 'S' | 'M' | 'L' | 'XL' = 'M'
-  onAction?: (Key, T) => void
+  onAction?: (Key) => void
   onOpenChange?: (boolean) => void
   shouldCloseOnSelect?: boolean
   shouldFlip?: boolean = true
   size?: 'XS' | 'S' | 'M' | 'L' | 'XL' = 'M'
 }

/@react-spectrum/s2:ContextualHelpPopover

 ContextualHelpPopover {
   UNSAFE_className?: UnsafeClassName
   UNSAFE_style?: CSSProperties
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   children: ReactNode
   containerPadding?: number = 12
   crossOffset?: number = 0
-  getTargetRect?: (Element) => DOMRect | null | undefined = target.getBoundingClientRect()
   hideArrow?: boolean = false
   id?: string
-  isNonModal?: boolean
   isOpen?: boolean
   offset?: number = 8
   onOpenChange?: (boolean) => void
   padding?: 'default' | 'none' = 'default'
   role?: 'dialog' | 'alertdialog' = 'dialog'
   shouldFlip?: boolean = true
   size?: 'S' | 'M' | 'L'
   slot?: string | null
   styles?: PopoverStylesProp
   triggerRef?: RefObject<Element | null>
 }

/@react-spectrum/s2:Menu

 Menu <T> {
   UNSAFE_className?: UnsafeClassName
   UNSAFE_style?: CSSProperties
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   autoFocus?: boolean | FocusStrategy
   children: ReactNode | (T) => ReactNode
   defaultSelectedKeys?: 'all' | Iterable<Key>
-  dependencies?: ReadonlyArray<any>
   disabledKeys?: Iterable<Key>
   disallowEmptySelection?: boolean
   escapeKeyBehavior?: 'clearSelection' | 'none' = 'clearSelection'
   hideLinkOutIcon?: boolean
   id?: string
   items?: Iterable<T>
-  onAction?: (Key, T) => void
+  onAction?: (Key) => void
   onClose?: () => void
   onSelectionChange?: (Selection) => void
   selectedKeys?: 'all' | Iterable<Key>
   selectionMode?: SelectionMode
   shouldFocusWrap?: boolean
   size?: 'S' | 'M' | 'L' | 'XL' = 'M'
   slot?: string | null
   styles?: StylesProp
 }

/@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
   containerPadding?: number = 12
   crossOffset?: number = 0
-  getTargetRect?: (Element) => DOMRect | null | undefined = target.getBoundingClientRect()
   hideArrow?: boolean = false
   id?: string
-  isNonModal?: boolean
   isOpen?: boolean
   offset?: number = 8
   onOpenChange?: (boolean) => void
   padding?: 'default' | 'none' = 'default'
   role?: 'dialog' | 'alertdialog' = 'dialog'
   shouldFlip?: boolean = true
   size?: 'S' | 'M' | 'L'
   slot?: string | null
   styles?: PopoverStylesProp
   triggerRef?: RefObject<Element | null>
 }

/@react-spectrum/s2:ActionMenuProps

 ActionMenuProps <T> {
   UNSAFE_className?: UnsafeClassName
   UNSAFE_style?: CSSProperties
   align?: 'start' | 'end' = 'start'
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   autoFocus?: boolean
   children: ReactNode | (T) => ReactNode
   defaultOpen?: boolean
   direction?: 'bottom' | 'top' | 'left' | 'right' | 'start' | 'end' = 'bottom'
   disabledKeys?: Iterable<Key>
   id?: string
   isDisabled?: boolean
   isOpen?: boolean
   isQuiet?: boolean
   items?: Iterable<T>
   menuSize?: 'S' | 'M' | 'L' | 'XL' = 'M'
-  onAction?: (Key, T) => void
+  onAction?: (Key) => void
   onOpenChange?: (boolean) => void
   shouldCloseOnSelect?: boolean
   shouldFlip?: boolean = true
   size?: 'XS' | 'S' | 'M' | 'L' | 'XL' = 'M'
 }

/@react-spectrum/s2:ContextualHelpPopoverProps

 ContextualHelpPopoverProps {
   UNSAFE_className?: UnsafeClassName
   UNSAFE_style?: CSSProperties
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   children: ReactNode
   containerPadding?: number = 12
   crossOffset?: number = 0
-  getTargetRect?: (Element) => DOMRect | null | undefined = target.getBoundingClientRect()
   hideArrow?: boolean = false
   id?: string
-  isNonModal?: boolean
   isOpen?: boolean
   offset?: number = 8
   onOpenChange?: (boolean) => void
   padding?: 'default' | 'none' = 'default'
   role?: 'dialog' | 'alertdialog' = 'dialog'
   shouldFlip?: boolean = true
   size?: 'S' | 'M' | 'L'
   slot?: string | null
   styles?: PopoverStylesProp
   triggerRef?: RefObject<Element | null>
 }

/@react-spectrum/s2:MenuProps

 MenuProps <T> {
   UNSAFE_className?: UnsafeClassName
   UNSAFE_style?: CSSProperties
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   autoFocus?: boolean | FocusStrategy
   children: ReactNode | (T) => ReactNode
   defaultSelectedKeys?: 'all' | Iterable<Key>
-  dependencies?: ReadonlyArray<any>
   disabledKeys?: Iterable<Key>
   disallowEmptySelection?: boolean
   escapeKeyBehavior?: 'clearSelection' | 'none' = 'clearSelection'
   hideLinkOutIcon?: boolean
   id?: string
   items?: Iterable<T>
-  onAction?: (Key, T) => void
+  onAction?: (Key) => void
   onClose?: () => void
   onSelectionChange?: (Selection) => void
   selectedKeys?: 'all' | Iterable<Key>
   selectionMode?: SelectionMode
   shouldFocusWrap?: boolean
   size?: 'S' | 'M' | 'L' | 'XL' = 'M'
   slot?: string | null
   styles?: StylesProp
 }

/@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>
   containerPadding?: number = 12
   crossOffset?: number = 0
   defaultOpen?: boolean
-  getTargetRect?: (Element) => DOMRect | null | undefined = target.getBoundingClientRect()
   hideArrow?: boolean = false
   isEntering?: boolean
   isExiting?: boolean
-  isNonModal?: boolean
   isOpen?: boolean
   maxHeight?: number
   offset?: number = 8
   onOpenChange?: (boolean) => void
   scrollRef?: RefObject<Element | null> = overlayRef
   shouldFlip?: boolean = true
   size?: 'S' | 'M' | 'L'
   slot?: string | null
   styles?: StyleString
   trigger?: string
   triggerRef?: RefObject<Element | null>
 }

/@react-spectrum/s2:DragTypes

 DragTypes {
-  has: (DragType | Array<DragType>) => boolean
+  has: (string | symbol) => boolean
 }

@rspbot

rspbot commented Jun 13, 2026

Copy link
Copy Markdown

Agent Skills Changes

Modified (2)
Install

React Spectrum S2:

npx skills add https://d1pzu54gtk2aed.cloudfront.net/pr/a66f36e9df8f0166b13d79d4f526c0363a358149/

React Aria:

npx skills add https://d5iwopk28bdhl.cloudfront.net/pr/a66f36e9df8f0166b13d79d4f526c0363a358149/

@devongovett

Copy link
Copy Markdown
Member

ah we'll have to skip those tests in old react versions

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants