Skip to content

Conversation

@devongovett
Copy link
Member

@devongovett devongovett commented Jan 26, 2026

Closes #2140

Similar to #8734, this adds support for the selectionMode prop to ComboBox, deprecates selectedKey/onSelectionChange, and adds value/onChange which can be arrays in multi-select mode. When multi-select is enabled, the input value is cleared when an item is selected. It's the dev's responsibility to render the selected items somehow, e.g. using a TagGroup. Eventually we will have TagField which will improve this further to support tags that are "inline" with the input, but using an adjacent TagGroup is fine for now.

To do:

  • Tests
  • Docs
  • Do we want a ComboBoxValue component similar to SelectValue? Right now I'm using ComboBoxStateContext to get the selected items but we could add a more first-class API for this.
  • Omit selectionMode/value/onChange from S2 props for now? Do we want to support this case or wait for TagField?
  • Ability to submit multiple selected values in a form

@rspbot
Copy link

rspbot commented Jan 26, 2026

<input key={i} type="hidden" name={name} form={props.form} value={value ?? ''} />
));
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Member Author

Choose a reason for hiding this comment

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

I think that only works on a <select> element or certain input types?

@rspbot
Copy link

rspbot commented Jan 27, 2026

@rspbot
Copy link

rspbot commented Jan 27, 2026

## API Changes

react-aria-components

/react-aria-components:ComboBox

-ComboBox <T extends {}> {
+ComboBox <M extends SelectionMode = 'single', T extends {}> {
   allowsCustomValue?: boolean
   allowsEmptyCollection?: boolean
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   autoFocus?: boolean
   children?: ChildrenOrFunction<ComboBoxRenderProps>
   className?: ClassNameOrFunction<ComboBoxRenderProps> = 'react-aria-ComboBox'
   defaultFilter?: (string, string) => boolean
   defaultInputValue?: string
   defaultItems?: Iterable<T>
-  defaultSelectedKey?: Key
+  defaultValue?: ValueType<SelectionMode>
   disabledKeys?: Iterable<Key>
   form?: string
   formValue?: 'text' | 'key' = 'key'
   id?: string
   inputValue?: string
   isDisabled?: boolean
   isInvalid?: boolean
   isReadOnly?: boolean
   isRequired?: boolean
   isTriggerUpWhenOpen?: boolean
   items?: Iterable<T>
   menuTrigger?: MenuTriggerAction = 'input'
   name?: string
   onBlur?: (FocusEvent<HTMLInputElement>) => void
+  onChange?: (T) => void
   onFocus?: (FocusEvent<HTMLInputElement>) => void
   onFocusChange?: (boolean) => void
   onInputChange?: (string) => void
   onKeyDown?: (KeyboardEvent) => void
   onKeyUp?: (KeyboardEvent) => void
   onOpenChange?: (boolean, MenuTriggerAction) => void
-  onSelectionChange?: (Key | null) => void
-  selectedKey?: Key | null
+  selectionMode?: SelectionMode = 'single'
   shouldFocusWrap?: boolean
   slot?: string | null
   style?: StyleOrFunction<ComboBoxRenderProps>
   validate?: (ComboBoxValidationValue) => ValidationError | boolean | null | undefined
   validationBehavior?: 'native' | 'aria' = 'native'
+  value?: ValueType<SelectionMode>
 }

/react-aria-components:ComboBoxProps

-ComboBoxProps <T extends {}> {
+ComboBoxProps <M extends SelectionMode = 'single', T extends {}> {
   allowsCustomValue?: boolean
   allowsEmptyCollection?: boolean
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   autoFocus?: boolean
   children?: ChildrenOrFunction<ComboBoxRenderProps>
   className?: ClassNameOrFunction<ComboBoxRenderProps> = 'react-aria-ComboBox'
   defaultFilter?: (string, string) => boolean
   defaultInputValue?: string
   defaultItems?: Iterable<T>
-  defaultSelectedKey?: Key
+  defaultValue?: ValueType<SelectionMode>
   disabledKeys?: Iterable<Key>
   form?: string
   formValue?: 'text' | 'key' = 'key'
   id?: string
   inputValue?: string
   isDisabled?: boolean
   isInvalid?: boolean
   isReadOnly?: boolean
   isRequired?: boolean
   isTriggerUpWhenOpen?: boolean
   items?: Iterable<T>
   menuTrigger?: MenuTriggerAction = 'input'
   name?: string
   onBlur?: (FocusEvent<HTMLInputElement>) => void
+  onChange?: (T) => void
   onFocus?: (FocusEvent<HTMLInputElement>) => void
   onFocusChange?: (boolean) => void
   onInputChange?: (string) => void
   onKeyDown?: (KeyboardEvent) => void
   onKeyUp?: (KeyboardEvent) => void
   onOpenChange?: (boolean, MenuTriggerAction) => void
-  onSelectionChange?: (Key | null) => void
-  selectedKey?: Key | null
+  selectionMode?: SelectionMode = 'single'
   shouldFocusWrap?: boolean
   slot?: string | null
   style?: StyleOrFunction<ComboBoxRenderProps>
   validate?: (ComboBoxValidationValue) => ValidationError | boolean | null | undefined
   validationBehavior?: 'native' | 'aria' = 'native'
+  value?: ValueType<SelectionMode>
 }

/react-aria-components:ComboBoxState

-ComboBoxState <T> {
+ComboBoxState <M extends SelectionMode = 'single', T> {
   close: () => void
   collection: Collection<Node<T>>
   commit: () => void
   commitValidation: () => void
   defaultInputValue: string
-  defaultSelectedKey: Key | null
+  defaultValue: ValueType<SelectionMode>
   disabledKeys: Set<Key>
   displayValidation: ValidationResult
   focusStrategy: FocusStrategy | null
   inputValue: string
   isFocused: boolean
   isOpen: boolean
   open: (FocusStrategy | null, MenuTriggerAction) => void
   realtimeValidation: ValidationResult
   resetValidation: () => void
   revert: () => void
-  selectedItem: Node<T> | null
-  selectedKey: Key | null
+  selectedItems: Array<Node<T>>
   selectionManager: SelectionManager
   setFocused: (boolean) => void
   setInputValue: (string) => void
   setOpen: (boolean) => void
-  setSelectedKey: (Key | null) => void
+  setValue: (Key | readonly Array<Key> | null) => void
   toggle: (FocusStrategy | null, MenuTriggerAction) => void
   updateValidation: (ValidationResult) => void
+  value: ValueType<SelectionMode>
 }

@react-aria/autocomplete

/@react-aria/autocomplete:AriaSearchAutocompleteOptions

 AriaSearchAutocompleteOptions <T> {
   aria-activedescendant?: string
   aria-autocomplete?: 'none' | 'inline' | 'list' | 'both'
   aria-controls?: string
   aria-describedby?: string
   aria-details?: string
   aria-errormessage?: string
   aria-haspopup?: boolean | 'false' | 'true' | 'menu' | 'listbox' | 'tree' | 'grid' | 'dialog'
   aria-label?: string
   aria-labelledby?: string
   autoComplete?: string
   autoCorrect?: string
   autoFocus?: boolean
   children: CollectionChildren<T>
   defaultInputValue?: string
   defaultItems?: Iterable<T>
   description?: ReactNode
   disabledKeys?: Iterable<Key>
   enterKeyHint?: 'enter' | 'done' | 'go' | 'next' | 'previous' | 'search' | 'send'
   errorMessage?: ReactNode | (ValidationResult) => ReactNode
   excludeFromTabOrder?: boolean
   form?: string
   id?: string
   inputMode?: 'none' | 'text' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search'
   inputRef: RefObject<HTMLInputElement | null>
   inputValue?: string
   isDisabled?: boolean
   isInvalid?: boolean
   isReadOnly?: boolean
   isRequired?: boolean
   items?: Iterable<T>
   keyboardDelegate?: KeyboardDelegate
   label?: ReactNode
   layoutDelegate?: LayoutDelegate
   listBoxRef: RefObject<HTMLElement | null>
   maxLength?: number
   menuTrigger?: MenuTriggerAction = 'input'
   minLength?: number
   name?: string
   onBeforeInput?: FormEventHandler<HTMLInputElement>
   onBlur?: (FocusEvent<T>) => void
-  onChange?: (T) => void
   onClear?: () => void
   onCompositionEnd?: CompositionEventHandler<HTMLInputElement>
   onCompositionStart?: CompositionEventHandler<HTMLInputElement>
   onCompositionUpdate?: CompositionEventHandler<HTMLInputElement>
   onCut?: ClipboardEventHandler<HTMLInputElement>
   onFocus?: (FocusEvent<T>) => void
   onFocusChange?: (boolean) => void
   onInput?: FormEventHandler<HTMLInputElement>
   onInputChange?: (string) => void
   onKeyDown?: (KeyboardEvent) => void
   onKeyUp?: (KeyboardEvent) => void
   onOpenChange?: (boolean, MenuTriggerAction) => void
   onPaste?: ClipboardEventHandler<HTMLInputElement>
   onSelect?: ReactEventHandler<HTMLInputElement>
   onSubmit?: (string | null, Key | null) => void
   pattern?: string
   placeholder?: string
   popoverRef: RefObject<HTMLDivElement | null>
   spellCheck?: string
   type?: 'text' | 'search' | 'url' | 'tel' | 'email' | 'password' | (string & {
   
 }) = 'search'
   validate?: (string) => ValidationError | boolean | null | undefined
   validationBehavior?: 'aria' | 'native' = 'aria'
 }

/@react-aria/autocomplete:AriaSearchAutocompleteProps

 AriaSearchAutocompleteProps <T> {
   aria-activedescendant?: string
   aria-autocomplete?: 'none' | 'inline' | 'list' | 'both'
   aria-controls?: string
   aria-describedby?: string
   aria-details?: string
   aria-errormessage?: string
   aria-haspopup?: boolean | 'false' | 'true' | 'menu' | 'listbox' | 'tree' | 'grid' | 'dialog'
   aria-label?: string
   aria-labelledby?: string
   autoComplete?: string
   autoCorrect?: string
   autoFocus?: boolean
   children: CollectionChildren<T>
   defaultInputValue?: string
   defaultItems?: Iterable<T>
   description?: ReactNode
   disabledKeys?: Iterable<Key>
   enterKeyHint?: 'enter' | 'done' | 'go' | 'next' | 'previous' | 'search' | 'send'
   errorMessage?: ReactNode | (ValidationResult) => ReactNode
   excludeFromTabOrder?: boolean
   form?: string
   id?: string
   inputMode?: 'none' | 'text' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search'
   inputValue?: string
   isDisabled?: boolean
   isInvalid?: boolean
   isReadOnly?: boolean
   isRequired?: boolean
   items?: Iterable<T>
   label?: ReactNode
   maxLength?: number
   menuTrigger?: MenuTriggerAction = 'input'
   minLength?: number
   name?: string
   onBeforeInput?: FormEventHandler<HTMLInputElement>
   onBlur?: (FocusEvent<T>) => void
-  onChange?: (T) => void
   onClear?: () => void
   onCompositionEnd?: CompositionEventHandler<HTMLInputElement>
   onCompositionStart?: CompositionEventHandler<HTMLInputElement>
   onCompositionUpdate?: CompositionEventHandler<HTMLInputElement>
   onCut?: ClipboardEventHandler<HTMLInputElement>
   onFocus?: (FocusEvent<T>) => void
   onFocusChange?: (boolean) => void
   onInput?: FormEventHandler<HTMLInputElement>
   onInputChange?: (string) => void
   onKeyDown?: (KeyboardEvent) => void
   onKeyUp?: (KeyboardEvent) => void
   onOpenChange?: (boolean, MenuTriggerAction) => void
   onPaste?: ClipboardEventHandler<HTMLInputElement>
   onSelect?: ReactEventHandler<HTMLInputElement>
   onSubmit?: (string | null, Key | null) => void
   pattern?: string
   placeholder?: string
   spellCheck?: string
   type?: 'text' | 'search' | 'url' | 'tel' | 'email' | 'password' | (string & {
   
 }) = 'search'
   validate?: (string) => ValidationError | boolean | null | undefined
   validationBehavior?: 'aria' | 'native' = 'aria'
 }

@react-aria/combobox

/@react-aria/combobox:useComboBox

-useComboBox <T> {
+useComboBox <M extends SelectionMode = 'single', T> {
-  props: AriaComboBoxOptions<T>
-  state: ComboBoxState<T>
+  props: AriaComboBoxOptions<T, M>
+  state: ComboBoxState<T, M>
   returnVal: undefined
 }

/@react-aria/combobox:AriaComboBoxOptions

-AriaComboBoxOptions <T> {
+AriaComboBoxOptions <M extends SelectionMode = 'single', T> {
   allowsCustomValue?: boolean
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   autoFocus?: boolean
   buttonRef?: RefObject<Element | null>
   defaultInputValue?: string
   defaultItems?: Iterable<T>
-  defaultSelectedKey?: Key
+  defaultValue?: ValueType<SelectionMode>
   description?: ReactNode
   disabledKeys?: Iterable<Key>
   errorMessage?: ReactNode | (ValidationResult) => ReactNode
   form?: string
   id?: string
   inputRef: RefObject<HTMLInputElement | null>
   inputValue?: string
   isDisabled?: boolean
   isInvalid?: boolean
   isReadOnly?: boolean
   isRequired?: boolean
   items?: Iterable<T>
   keyboardDelegate?: KeyboardDelegate
   label?: ReactNode
   layoutDelegate?: LayoutDelegate
   listBoxRef: RefObject<HTMLElement | null>
   menuTrigger?: MenuTriggerAction = 'input'
   name?: string
   onBlur?: (FocusEvent<HTMLInputElement>) => void
+  onChange?: (T) => void
   onFocus?: (FocusEvent<HTMLInputElement>) => void
   onFocusChange?: (boolean) => void
   onInputChange?: (string) => void
   onKeyDown?: (KeyboardEvent) => void
   onKeyUp?: (KeyboardEvent) => void
   onOpenChange?: (boolean, MenuTriggerAction) => void
-  onSelectionChange?: (Key | null) => void
   placeholder?: string
   popoverRef: RefObject<Element | null>
-  selectedKey?: Key | null
+  selectionMode?: SelectionMode = 'single'
   shouldFocusWrap?: boolean
   validate?: (ComboBoxValidationValue) => ValidationError | boolean | null | undefined
   validationBehavior?: 'aria' | 'native' = 'aria'
+  value?: ValueType<SelectionMode>
 }

/@react-aria/combobox:AriaComboBoxProps

-AriaComboBoxProps <T> {
+AriaComboBoxProps <M extends SelectionMode = 'single', T> {
   allowsCustomValue?: boolean
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   autoFocus?: boolean
   children: CollectionChildren<T>
   defaultInputValue?: string
   defaultItems?: Iterable<T>
-  defaultSelectedKey?: Key
+  defaultValue?: ValueType<SelectionMode>
   description?: ReactNode
   disabledKeys?: Iterable<Key>
   errorMessage?: ReactNode | (ValidationResult) => ReactNode
   form?: string
   id?: string
   inputValue?: string
   isDisabled?: boolean
   isInvalid?: boolean
   isReadOnly?: boolean
   isRequired?: boolean
   items?: Iterable<T>
   label?: ReactNode
   menuTrigger?: MenuTriggerAction = 'input'
   name?: string
   onBlur?: (FocusEvent<HTMLInputElement>) => void
+  onChange?: (T) => void
   onFocus?: (FocusEvent<HTMLInputElement>) => void
   onFocusChange?: (boolean) => void
   onInputChange?: (string) => void
   onKeyDown?: (KeyboardEvent) => void
   onKeyUp?: (KeyboardEvent) => void
   onOpenChange?: (boolean, MenuTriggerAction) => void
-  onSelectionChange?: (Key | null) => void
   placeholder?: string
-  selectedKey?: Key | null
+  selectionMode?: SelectionMode = 'single'
   shouldFocusWrap?: boolean
   validate?: (ComboBoxValidationValue) => ValidationError | boolean | null | undefined
   validationBehavior?: 'aria' | 'native' = 'aria'
+  value?: ValueType<SelectionMode>
 }

@react-spectrum/autocomplete

/@react-spectrum/autocomplete:SearchAutocomplete

 SearchAutocomplete <T extends {}> {
   UNSAFE_className?: string
   UNSAFE_style?: CSSProperties
   align?: 'start' | 'end' = 'start'
   alignSelf?: Responsive<'auto' | 'normal' | 'start' | 'end' | 'center' | 'flex-start' | 'flex-end' | 'self-start' | 'self-end' | 'stretch'>
   aria-activedescendant?: string
   aria-autocomplete?: 'none' | 'inline' | 'list' | 'both'
   aria-controls?: string
   aria-describedby?: string
   aria-details?: string
   aria-errormessage?: string
   aria-haspopup?: boolean | 'false' | 'true' | 'menu' | 'listbox' | 'tree' | 'grid' | 'dialog'
   aria-label?: string
   aria-labelledby?: string
   autoComplete?: string
   autoCorrect?: string
   autoFocus?: boolean
   bottom?: Responsive<DimensionValue>
   children: CollectionChildren<{}>
   contextualHelp?: ReactNode
   defaultInputValue?: string
   defaultItems?: Iterable<{}>
   description?: ReactNode
   direction?: 'bottom' | 'top' = 'bottom'
   disabledKeys?: Iterable<Key>
   end?: Responsive<DimensionValue>
   enterKeyHint?: 'enter' | 'done' | 'go' | 'next' | 'previous' | 'search' | 'send'
   errorMessage?: ReactNode | (ValidationResult) => ReactNode
   excludeFromTabOrder?: boolean
   flex?: Responsive<string | number | boolean>
   flexBasis?: Responsive<number | string>
   flexGrow?: Responsive<number>
   flexShrink?: Responsive<number>
   form?: string
   gridArea?: Responsive<string>
   gridColumn?: Responsive<string>
   gridColumnEnd?: Responsive<string>
   gridColumnStart?: Responsive<string>
   gridRow?: Responsive<string>
   gridRowEnd?: Responsive<string>
   gridRowStart?: Responsive<string>
   height?: Responsive<DimensionValue>
   icon?: ReactElement | null
   id?: string
   inputMode?: 'none' | 'text' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search'
   inputValue?: string
   isDisabled?: boolean
   isHidden?: Responsive<boolean>
   isQuiet?: boolean
   isReadOnly?: boolean
   isRequired?: boolean
   items?: Iterable<{}>
   justifySelf?: Responsive<'auto' | 'normal' | 'start' | 'end' | 'flex-start' | 'flex-end' | 'self-start' | 'self-end' | 'center' | 'left' | 'right' | 'stretch'>
   label?: ReactNode
   labelAlign?: Alignment = 'start'
   labelPosition?: LabelPosition = 'top'
   left?: Responsive<DimensionValue>
   loadingState?: LoadingState
   margin?: Responsive<DimensionValue>
   marginBottom?: Responsive<DimensionValue>
   marginEnd?: Responsive<DimensionValue>
   marginStart?: Responsive<DimensionValue>
   marginTop?: Responsive<DimensionValue>
   marginX?: Responsive<DimensionValue>
   marginY?: Responsive<DimensionValue>
   maxHeight?: Responsive<DimensionValue>
   maxLength?: number
   maxWidth?: Responsive<DimensionValue>
   menuTrigger?: MenuTriggerAction = 'input'
   menuWidth?: DimensionValue
   minHeight?: Responsive<DimensionValue>
   minLength?: number
   minWidth?: Responsive<DimensionValue>
   name?: string
   necessityIndicator?: NecessityIndicator = 'icon'
   onBeforeInput?: FormEventHandler<HTMLInputElement>
   onBlur?: (FocusEvent<{}>) => void
-  onChange?: ({}) => void
   onClear?: () => void
   onCompositionEnd?: CompositionEventHandler<HTMLInputElement>
   onCompositionStart?: CompositionEventHandler<HTMLInputElement>
   onCompositionUpdate?: CompositionEventHandler<HTMLInputElement>
   onCut?: ClipboardEventHandler<HTMLInputElement>
   onFocus?: (FocusEvent<{}>) => void
   onFocusChange?: (boolean) => void
   onInput?: FormEventHandler<HTMLInputElement>
   onInputChange?: (string) => void
   onKeyDown?: (KeyboardEvent) => void
   onKeyUp?: (KeyboardEvent) => void
   onLoadMore?: () => void
   onOpenChange?: (boolean, MenuTriggerAction) => void
   onPaste?: ClipboardEventHandler<HTMLInputElement>
   onSelect?: ReactEventHandler<HTMLInputElement>
   onSubmit?: (string | null, Key | null) => void
   order?: Responsive<number>
   pattern?: string
   position?: Responsive<'static' | 'relative' | 'absolute' | 'fixed' | 'sticky'>
   right?: Responsive<DimensionValue>
   shouldFlip?: boolean = true
   spellCheck?: string
   start?: Responsive<DimensionValue>
   top?: Responsive<DimensionValue>
   type?: 'text' | 'search' | 'url' | 'tel' | 'email' | 'password' | (string & {
   
 }) = 'search'
   validate?: (string) => ValidationError | boolean | null | undefined
   validationBehavior?: 'aria' | 'native' = 'aria'
   validationState?: ValidationState
   width?: Responsive<DimensionValue>
   zIndex?: Responsive<number>
 }

/@react-spectrum/autocomplete:SpectrumSearchAutocompleteProps

 SpectrumSearchAutocompleteProps <T> {
   UNSAFE_className?: string
   UNSAFE_style?: CSSProperties
   align?: 'start' | 'end' = 'start'
   alignSelf?: Responsive<'auto' | 'normal' | 'start' | 'end' | 'center' | 'flex-start' | 'flex-end' | 'self-start' | 'self-end' | 'stretch'>
   aria-activedescendant?: string
   aria-autocomplete?: 'none' | 'inline' | 'list' | 'both'
   aria-controls?: string
   aria-describedby?: string
   aria-details?: string
   aria-errormessage?: string
   aria-haspopup?: boolean | 'false' | 'true' | 'menu' | 'listbox' | 'tree' | 'grid' | 'dialog'
   aria-label?: string
   aria-labelledby?: string
   autoComplete?: string
   autoCorrect?: string
   autoFocus?: boolean
   bottom?: Responsive<DimensionValue>
   children: CollectionChildren<T>
   contextualHelp?: ReactNode
   defaultInputValue?: string
   defaultItems?: Iterable<T>
   description?: ReactNode
   direction?: 'bottom' | 'top' = 'bottom'
   disabledKeys?: Iterable<Key>
   end?: Responsive<DimensionValue>
   enterKeyHint?: 'enter' | 'done' | 'go' | 'next' | 'previous' | 'search' | 'send'
   errorMessage?: ReactNode | (ValidationResult) => ReactNode
   excludeFromTabOrder?: boolean
   flex?: Responsive<string | number | boolean>
   flexBasis?: Responsive<number | string>
   flexGrow?: Responsive<number>
   flexShrink?: Responsive<number>
   form?: string
   gridArea?: Responsive<string>
   gridColumn?: Responsive<string>
   gridColumnEnd?: Responsive<string>
   gridColumnStart?: Responsive<string>
   gridRow?: Responsive<string>
   gridRowEnd?: Responsive<string>
   gridRowStart?: Responsive<string>
   height?: Responsive<DimensionValue>
   icon?: ReactElement | null
   id?: string
   inputMode?: 'none' | 'text' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search'
   inputValue?: string
   isDisabled?: boolean
   isHidden?: Responsive<boolean>
   isQuiet?: boolean
   isReadOnly?: boolean
   isRequired?: boolean
   items?: Iterable<T>
   justifySelf?: Responsive<'auto' | 'normal' | 'start' | 'end' | 'flex-start' | 'flex-end' | 'self-start' | 'self-end' | 'center' | 'left' | 'right' | 'stretch'>
   label?: ReactNode
   labelAlign?: Alignment = 'start'
   labelPosition?: LabelPosition = 'top'
   left?: Responsive<DimensionValue>
   loadingState?: LoadingState
   margin?: Responsive<DimensionValue>
   marginBottom?: Responsive<DimensionValue>
   marginEnd?: Responsive<DimensionValue>
   marginStart?: Responsive<DimensionValue>
   marginTop?: Responsive<DimensionValue>
   marginX?: Responsive<DimensionValue>
   marginY?: Responsive<DimensionValue>
   maxHeight?: Responsive<DimensionValue>
   maxLength?: number
   maxWidth?: Responsive<DimensionValue>
   menuTrigger?: MenuTriggerAction = 'input'
   menuWidth?: DimensionValue
   minHeight?: Responsive<DimensionValue>
   minLength?: number
   minWidth?: Responsive<DimensionValue>
   name?: string
   necessityIndicator?: NecessityIndicator = 'icon'
   onBeforeInput?: FormEventHandler<HTMLInputElement>
   onBlur?: (FocusEvent<T>) => void
-  onChange?: (T) => void
   onClear?: () => void
   onCompositionEnd?: CompositionEventHandler<HTMLInputElement>
   onCompositionStart?: CompositionEventHandler<HTMLInputElement>
   onCompositionUpdate?: CompositionEventHandler<HTMLInputElement>
   onCut?: ClipboardEventHandler<HTMLInputElement>
   onFocus?: (FocusEvent<T>) => void
   onFocusChange?: (boolean) => void
   onInput?: FormEventHandler<HTMLInputElement>
   onInputChange?: (string) => void
   onKeyDown?: (KeyboardEvent) => void
   onKeyUp?: (KeyboardEvent) => void
   onLoadMore?: () => void
   onOpenChange?: (boolean, MenuTriggerAction) => void
   onPaste?: ClipboardEventHandler<HTMLInputElement>
   onSelect?: ReactEventHandler<HTMLInputElement>
   onSubmit?: (string | null, Key | null) => void
   order?: Responsive<number>
   pattern?: string
   position?: Responsive<'static' | 'relative' | 'absolute' | 'fixed' | 'sticky'>
   right?: Responsive<DimensionValue>
   shouldFlip?: boolean = true
   spellCheck?: string
   start?: Responsive<DimensionValue>
   top?: Responsive<DimensionValue>
   type?: 'text' | 'search' | 'url' | 'tel' | 'email' | 'password' | (string & {
   
 }) = 'search'
   validate?: (string) => ValidationError | boolean | null | undefined
   validationBehavior?: 'aria' | 'native' = 'aria'
   validationState?: ValidationState
   width?: Responsive<DimensionValue>
   zIndex?: Responsive<number>
 }

@react-spectrum/combobox

/@react-spectrum/combobox:ComboBox

 ComboBox <T extends {}> {
   UNSAFE_className?: string
   UNSAFE_style?: CSSProperties
   align?: 'start' | 'end' = 'start'
   alignSelf?: Responsive<'auto' | 'normal' | 'start' | 'end' | 'center' | 'flex-start' | 'flex-end' | 'self-start' | 'self-end' | 'stretch'>
   allowsCustomValue?: boolean
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   autoFocus?: boolean
   bottom?: Responsive<DimensionValue>
   children: CollectionChildren<{}>
   contextualHelp?: ReactNode
   defaultInputValue?: string
   defaultItems?: Iterable<{}>
-  defaultSelectedKey?: Key
+  defaultValue?: ValueType<SelectionMode>
   description?: ReactNode
   direction?: 'bottom' | 'top' = 'bottom'
   disabledKeys?: Iterable<Key>
   end?: Responsive<DimensionValue>
   errorMessage?: ReactNode | (ValidationResult) => ReactNode
   flex?: Responsive<string | number | boolean>
   flexBasis?: Responsive<number | string>
   flexGrow?: Responsive<number>
   flexShrink?: Responsive<number>
   form?: string
   formValue?: 'text' | 'key' = 'text'
   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
   inputValue?: string
   isDisabled?: boolean
   isHidden?: Responsive<boolean>
   isQuiet?: boolean
   isReadOnly?: boolean
   isRequired?: boolean
   items?: Iterable<{}>
   justifySelf?: Responsive<'auto' | 'normal' | 'start' | 'end' | 'flex-start' | 'flex-end' | 'self-start' | 'self-end' | 'center' | 'left' | 'right' | 'stretch'>
   label?: ReactNode
   labelAlign?: Alignment = 'start'
   labelPosition?: LabelPosition = 'top'
   left?: Responsive<DimensionValue>
   loadingState?: LoadingState
   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>
   menuTrigger?: MenuTriggerAction = 'input'
   menuWidth?: DimensionValue
   minHeight?: Responsive<DimensionValue>
   minWidth?: Responsive<DimensionValue>
   name?: string
   necessityIndicator?: NecessityIndicator = 'icon'
   onBlur?: (FocusEvent<HTMLInputElement>) => void
+  onChange?: ({}) => void
   onFocus?: (FocusEvent<HTMLInputElement>) => void
   onFocusChange?: (boolean) => void
   onInputChange?: (string) => void
   onKeyDown?: (KeyboardEvent) => void
   onKeyUp?: (KeyboardEvent) => void
   onLoadMore?: () => any
   onOpenChange?: (boolean, MenuTriggerAction) => void
-  onSelectionChange?: (Key | null) => void
   order?: Responsive<number>
   position?: Responsive<'static' | 'relative' | 'absolute' | 'fixed' | 'sticky'>
   right?: Responsive<DimensionValue>
-  selectedKey?: Key | null
+  selectionMode?: SelectionMode = 'single'
   shouldFlip?: boolean = true
   shouldFocusWrap?: boolean
   start?: Responsive<DimensionValue>
   top?: Responsive<DimensionValue>
   validate?: (ComboBoxValidationValue) => ValidationError | boolean | null | undefined
   validationBehavior?: 'aria' | 'native' = 'aria'
   validationState?: ValidationState
+  value?: ValueType<SelectionMode>
   width?: Responsive<DimensionValue>
   zIndex?: Responsive<number>
 }

/@react-spectrum/combobox:SpectrumComboBoxProps

 SpectrumComboBoxProps <T> {
   UNSAFE_className?: string
   UNSAFE_style?: CSSProperties
   align?: 'start' | 'end' = 'start'
   alignSelf?: Responsive<'auto' | 'normal' | 'start' | 'end' | 'center' | 'flex-start' | 'flex-end' | 'self-start' | 'self-end' | 'stretch'>
   allowsCustomValue?: boolean
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   autoFocus?: boolean
   bottom?: Responsive<DimensionValue>
   children: CollectionChildren<T>
   contextualHelp?: ReactNode
   defaultInputValue?: string
   defaultItems?: Iterable<T>
-  defaultSelectedKey?: Key
+  defaultValue?: ValueType<SelectionMode>
   description?: ReactNode
   direction?: 'bottom' | 'top' = 'bottom'
   disabledKeys?: Iterable<Key>
   end?: Responsive<DimensionValue>
   errorMessage?: ReactNode | (ValidationResult) => ReactNode
   flex?: Responsive<string | number | boolean>
   flexBasis?: Responsive<number | string>
   flexGrow?: Responsive<number>
   flexShrink?: Responsive<number>
   form?: string
   formValue?: 'text' | 'key' = 'text'
   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
   inputValue?: string
   isDisabled?: boolean
   isHidden?: Responsive<boolean>
   isQuiet?: boolean
   isReadOnly?: boolean
   isRequired?: boolean
   items?: Iterable<T>
   justifySelf?: Responsive<'auto' | 'normal' | 'start' | 'end' | 'flex-start' | 'flex-end' | 'self-start' | 'self-end' | 'center' | 'left' | 'right' | 'stretch'>
   label?: ReactNode
   labelAlign?: Alignment = 'start'
   labelPosition?: LabelPosition = 'top'
   left?: Responsive<DimensionValue>
   loadingState?: LoadingState
   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>
   menuTrigger?: MenuTriggerAction = 'input'
   menuWidth?: DimensionValue
   minHeight?: Responsive<DimensionValue>
   minWidth?: Responsive<DimensionValue>
   name?: string
   necessityIndicator?: NecessityIndicator = 'icon'
   onBlur?: (FocusEvent<HTMLInputElement>) => void
+  onChange?: (T) => void
   onFocus?: (FocusEvent<HTMLInputElement>) => void
   onFocusChange?: (boolean) => void
   onInputChange?: (string) => void
   onKeyDown?: (KeyboardEvent) => void
   onKeyUp?: (KeyboardEvent) => void
   onLoadMore?: () => any
   onOpenChange?: (boolean, MenuTriggerAction) => void
-  onSelectionChange?: (Key | null) => void
   order?: Responsive<number>
   position?: Responsive<'static' | 'relative' | 'absolute' | 'fixed' | 'sticky'>
   right?: Responsive<DimensionValue>
-  selectedKey?: Key | null
+  selectionMode?: SelectionMode = 'single'
   shouldFlip?: boolean = true
   shouldFocusWrap?: boolean
   start?: Responsive<DimensionValue>
   top?: Responsive<DimensionValue>
   validate?: (ComboBoxValidationValue) => ValidationError | boolean | null | undefined
   validationBehavior?: 'aria' | 'native' = 'aria'
   validationState?: ValidationState
+  value?: ValueType<SelectionMode>
   width?: Responsive<DimensionValue>
   zIndex?: Responsive<number>
 }

@react-spectrum/s2

/@react-spectrum/s2:ComboBox

 ComboBox <T extends {}> {
   UNSAFE_className?: UnsafeClassName
   UNSAFE_style?: CSSProperties
   align?: 'start' | 'end' = 'start'
   allowsCustomValue?: boolean
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   autoFocus?: boolean
   children: ReactNode | ({}) => ReactNode
   contextualHelp?: ReactNode
   defaultInputValue?: string
   defaultItems?: Iterable<T>
-  defaultSelectedKey?: Key
+  defaultValue?: ValueType<SelectionMode>
   dependencies?: ReadonlyArray<any>
   description?: ReactNode
   direction?: 'bottom' | 'top' = 'bottom'
   disabledKeys?: Iterable<Key>
   errorMessage?: ReactNode | (ValidationResult) => ReactNode
   form?: string
   formValue?: 'text' | 'key' = 'key'
   id?: string
   inputValue?: string
   isDisabled?: boolean
   isInvalid?: boolean
   isReadOnly?: boolean
   isRequired?: boolean
   items?: Iterable<T>
   label?: ReactNode
   labelAlign?: Alignment = 'start'
   labelPosition?: LabelPosition = 'top'
   loadingState?: LoadingState
   menuTrigger?: MenuTriggerAction = 'input'
   menuWidth?: number
   name?: string
   necessityIndicator?: NecessityIndicator = 'icon'
   onBlur?: (FocusEvent<HTMLInputElement>) => void
+  onChange?: (T) => void
   onFocus?: (FocusEvent<HTMLInputElement>) => void
   onFocusChange?: (boolean) => void
   onInputChange?: (string) => void
   onKeyDown?: (KeyboardEvent) => void
   onKeyUp?: (KeyboardEvent) => void
   onLoadMore?: () => any
   onOpenChange?: (boolean, MenuTriggerAction) => void
-  onSelectionChange?: (Key | null) => void
   placeholder?: string
-  selectedKey?: Key | null
+  selectionMode?: SelectionMode = 'single'
   shouldFlip?: boolean = true
   shouldFocusWrap?: boolean
   size?: 'S' | 'M' | 'L' | 'XL' = 'M'
   slot?: string | null
   styles?: StylesProp
   validate?: (ComboBoxValidationValue) => ValidationError | boolean | null | undefined
   validationBehavior?: 'native' | 'aria' = 'native'
+  value?: ValueType<SelectionMode>
 }

/@react-spectrum/s2:ComboBoxProps

 ComboBoxProps <T extends {}> {
   UNSAFE_className?: UnsafeClassName
   UNSAFE_style?: CSSProperties
   align?: 'start' | 'end' = 'start'
   allowsCustomValue?: boolean
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   autoFocus?: boolean
   children: ReactNode | ({}) => ReactNode
   contextualHelp?: ReactNode
   defaultInputValue?: string
   defaultItems?: Iterable<T>
-  defaultSelectedKey?: Key
+  defaultValue?: ValueType<SelectionMode>
   dependencies?: ReadonlyArray<any>
   description?: ReactNode
   direction?: 'bottom' | 'top' = 'bottom'
   disabledKeys?: Iterable<Key>
   errorMessage?: ReactNode | (ValidationResult) => ReactNode
   form?: string
   formValue?: 'text' | 'key' = 'key'
   id?: string
   inputValue?: string
   isDisabled?: boolean
   isInvalid?: boolean
   isReadOnly?: boolean
   isRequired?: boolean
   items?: Iterable<T>
   label?: ReactNode
   labelAlign?: Alignment = 'start'
   labelPosition?: LabelPosition = 'top'
   loadingState?: LoadingState
   menuTrigger?: MenuTriggerAction = 'input'
   menuWidth?: number
   name?: string
   necessityIndicator?: NecessityIndicator = 'icon'
   onBlur?: (FocusEvent<HTMLInputElement>) => void
+  onChange?: (T) => void
   onFocus?: (FocusEvent<HTMLInputElement>) => void
   onFocusChange?: (boolean) => void
   onInputChange?: (string) => void
   onKeyDown?: (KeyboardEvent) => void
   onKeyUp?: (KeyboardEvent) => void
   onLoadMore?: () => any
   onOpenChange?: (boolean, MenuTriggerAction) => void
-  onSelectionChange?: (Key | null) => void
   placeholder?: string
-  selectedKey?: Key | null
+  selectionMode?: SelectionMode = 'single'
   shouldFlip?: boolean = true
   shouldFocusWrap?: boolean
   size?: 'S' | 'M' | 'L' | 'XL' = 'M'
   slot?: string | null
   styles?: StylesProp
   validate?: (ComboBoxValidationValue) => ValidationError | boolean | null | undefined
   validationBehavior?: 'native' | 'aria' = 'native'
+  value?: ValueType<SelectionMode>
 }

@react-stately/combobox

/@react-stately/combobox:useComboBoxState

-useComboBoxState <T extends {}> {
+useComboBoxState <M extends SelectionMode = 'single', T extends {}> {
-  props: ComboBoxStateOptions<T>
+  props: ComboBoxStateOptions<T, M>
   returnVal: undefined
 }

/@react-stately/combobox:ComboBoxStateOptions

-ComboBoxStateOptions <T> {
+ComboBoxStateOptions <M extends SelectionMode = 'single', T> {
   allowsCustomValue?: boolean
   allowsEmptyCollection?: boolean
   autoFocus?: boolean
   collection?: Collection<Node<T>>
   defaultFilter?: FilterFn
   defaultInputValue?: string
   defaultItems?: Iterable<T>
-  defaultSelectedKey?: Key
+  defaultValue?: ValueType<SelectionMode>
   description?: ReactNode
   disabledKeys?: Iterable<Key>
   errorMessage?: ReactNode | (ValidationResult) => ReactNode
   inputValue?: string
   isDisabled?: boolean
   isInvalid?: boolean
   isReadOnly?: boolean
   isRequired?: boolean
   items?: Iterable<T>
   label?: ReactNode
   menuTrigger?: MenuTriggerAction = 'input'
   onBlur?: (FocusEvent<HTMLInputElement>) => void
+  onChange?: (T) => void
   onFocus?: (FocusEvent<HTMLInputElement>) => void
   onFocusChange?: (boolean) => void
   onInputChange?: (string) => void
   onKeyDown?: (KeyboardEvent) => void
   onKeyUp?: (KeyboardEvent) => void
   onOpenChange?: (boolean, MenuTriggerAction) => void
-  onSelectionChange?: (Key | null) => void
   placeholder?: string
-  selectedKey?: Key | null
+  selectionMode?: SelectionMode = 'single'
   shouldCloseOnBlur?: boolean
   validate?: (ComboBoxValidationValue) => ValidationError | boolean | null | undefined
   validationBehavior?: 'aria' | 'native' = 'aria'
+  value?: ValueType<SelectionMode>
 }

/@react-stately/combobox:ComboBoxState

-ComboBoxState <T> {
+ComboBoxState <M extends SelectionMode = 'single', T> {
   close: () => void
   collection: Collection<Node<T>>
   commit: () => void
   commitValidation: () => void
   defaultInputValue: string
-  defaultSelectedKey: Key | null
+  defaultValue: ValueType<SelectionMode>
   disabledKeys: Set<Key>
   displayValidation: ValidationResult
   focusStrategy: FocusStrategy | null
   inputValue: string
   isFocused: boolean
   isOpen: boolean
   open: (FocusStrategy | null, MenuTriggerAction) => void
   realtimeValidation: ValidationResult
   resetValidation: () => void
   revert: () => void
-  selectedItem: Node<T> | null
-  selectedKey: Key | null
+  selectedItems: Array<Node<T>>
   selectionManager: SelectionManager
   setFocused: (boolean) => void
   setInputValue: (string) => void
   setOpen: (boolean) => void
-  setSelectedKey: (Key | null) => void
+  setValue: (Key | readonly Array<Key> | null) => void
   toggle: (FocusStrategy | null, MenuTriggerAction) => void
   updateValidation: (ValidationResult) => void
+  value: ValueType<SelectionMode>
 }

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.

Multiple selection support for useComboBox

4 participants