feat: Add support for multi-select ComboBox#9525
Conversation
|
Build successful! 🎉 |
|
Build successful! 🎉 |
|
Super excited for this! |
…ct-combobox # Conflicts: # packages/@react-spectrum/s2/src/ComboBox.tsx
|
Build successful! 🎉 |
| }); | ||
|
|
||
| // Announce when a selection occurs for VoiceOver. Other screen readers typically do this automatically. | ||
| // TODO: do we need to do this for multi-select? |
There was a problem hiding this comment.
Right now I just hear "51 options available", which interrupts the default selection announcement from VO. But I also don't hear this with single select. We should discuss whether we can just remove the custom announcements entirely at this point.
There was a problem hiding this comment.
That makes sense, I think ComboBox is overdue for an accessibility sweep anyways since there have been reported issues against Talkback, the whole autocomplete announcement issue with auto focusing the first option on focus, etc
|
Build successful! 🎉 |
|
Build successful! 🎉 |
## 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
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
render?: DOMRenderFunction<keyof React.JSX.IntrinsicElements, ComboBoxRenderProps>
- 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:Provider-Provider <A, B, C, D, E, F, G, H, I, J, K> {
+Provider <A, B, C, D, E, F, G, H, I, J, K, L> {
children: ReactNode
- values: ProviderValues<A, B, C, D, E, F, G, H, I, J, K>
+ values: ProviderValues<A, B, C, D, E, F, G, H, I, J, K, L>
}/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
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
render?: DOMRenderFunction<keyof React.JSX.IntrinsicElements, ComboBoxRenderProps>
- 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:HeaderProps-HeaderProps {
- render?: DOMRenderFunction<keyof React.JSX.IntrinsicElements, undefined>
-}/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-components:NumberFieldState NumberFieldState {
canDecrement: boolean
canIncrement: boolean
- commit: (string) => void
+ commit: () => void
commitValidation: () => void
decrement: () => void
decrementToMin: () => void
defaultNumberValue: number
increment: () => void
incrementToMax: () => void
inputValue: string
maxValue?: number
minValue?: number
numberValue: number
realtimeValidation: ValidationResult
resetValidation: () => void
setInputValue: (string) => void
setNumberValue: (number) => void
updateValidation: (ValidationResult) => void
validate: (string) => boolean
}/react-aria-components:ComboBoxValue+ComboBoxValue <T extends {}> extends HTMLAttributes {
+ children?: ChildrenOrFunction<ComboBoxValueRenderProps<{}>>
+ className?: ClassNameOrFunction<ComboBoxValueRenderProps<{}>> = 'react-aria-ComboBoxValue'
+ placeholder?: ReactNode
+ render?: DOMRenderFunction<keyof React.JSX.IntrinsicElements, ComboBoxValueRenderProps<{}>>
+ style?: StyleOrFunction<ComboBoxValueRenderProps<{}>>
+}/react-aria-components:ComboBoxValueContext+ComboBoxValueContext {
+ UNTYPED
+}/react-aria-components:ComboBoxValueProps+ComboBoxValueProps <T extends {}> {
+ children?: ChildrenOrFunction<ComboBoxValueRenderProps<{}>>
+ className?: ClassNameOrFunction<ComboBoxValueRenderProps<{}>> = 'react-aria-ComboBoxValue'
+ placeholder?: ReactNode
+ render?: DOMRenderFunction<keyof React.JSX.IntrinsicElements, ComboBoxValueRenderProps<{}>>
+ style?: StyleOrFunction<ComboBoxValueRenderProps<{}>>
+}/react-aria-components:ComboBoxValueRenderProps+ComboBoxValueRenderProps <T> {
+ isPlaceholder: boolean
+ selectedItems: Array<T | null>
+ selectedText: string
+ state: ComboBoxState<T, 'single' | 'multiple'>
+}@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:ComboBoxAria ComboBoxAria <T> {
buttonProps: AriaButtonProps
descriptionProps: DOMAttributes
errorMessageProps: DOMAttributes
inputProps: InputHTMLAttributes<HTMLInputElement>
isInvalid: boolean
labelProps: DOMAttributes
listBoxProps: AriaListBoxOptions<T>
validationDetails: ValidityState
validationErrors: Array<string>
+ valueProps: DOMAttributes
}/@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-stately/color/@react-stately/color:ColorChannelFieldState ColorChannelFieldState {
canDecrement: boolean
canIncrement: boolean
colorValue: Color
- commit: (string) => void
+ commit: () => void
commitValidation: () => void
decrement: () => void
decrementToMin: () => void
defaultColorValue: Color | null
displayValidation: ValidationResult
increment: () => void
incrementToMax: () => void
inputValue: string
maxValue?: number
minValue?: number
numberValue: number
realtimeValidation: ValidationResult
resetValidation: () => void
setColorValue: (Color | null) => void
setInputValue: (string) => void
setNumberValue: (number) => void
updateValidation: (ValidationResult) => void
validate: (string) => boolean
}@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>
}@react-stately/numberfield/@react-stately/numberfield:NumberFieldState NumberFieldState {
canDecrement: boolean
canIncrement: boolean
- commit: (string) => void
+ commit: () => void
commitValidation: () => void
decrement: () => void
decrementToMin: () => void
defaultNumberValue: number
increment: () => void
incrementToMax: () => void
inputValue: string
maxValue?: number
minValue?: number
numberValue: number
realtimeValidation: ValidationResult
resetValidation: () => void
setInputValue: (string) => void
setNumberValue: (number) => void
updateValidation: (ValidationResult) => void
validate: (string) => boolean
} |
LFDanLu
left a comment
There was a problem hiding this comment.
initial review as I digest the combobox state changes
| }); | ||
|
|
||
| // Announce when a selection occurs for VoiceOver. Other screen readers typically do this automatically. | ||
| // TODO: do we need to do this for multi-select? |
There was a problem hiding this comment.
That makes sense, I think ComboBox is overdue for an accessibility sweep anyways since there have been reported issues against Talkback, the whole autocomplete announcement issue with auto focusing the first option on focus, etc
There was a problem hiding this comment.
what do you think about allowing users to deselect from the combobox menu via Enter on a selected item? Ariakit's combobox has similar behavior to that, and it would be more flexible for keyboard users in case a taggroup wasn't rendered alongside the ComboBox (though I'm not sure how common that would be)
There was a problem hiding this comment.
mmm yeah we should support that.
| values: { | ||
| selectedItems: useMemo(() => state.selectedItems.map(item => item.value as T ?? null), [state.selectedItems]), | ||
| selectedText, | ||
| isPlaceholder: state.selectedItems.length === 0, |
There was a problem hiding this comment.
would there ever be a case where custom value submission is supported alongside multi select? I suppose for that case the user needs to track those custom created tags externally and use that in combination with this render prop if they wanted the proper styling
There was a problem hiding this comment.
yeah I feel like allowsCustomValue doesn't really make sense with multi-select. You could combine it with the onAction example in the docs to create new items on demand instead.
Closes #2140
Similar to #8734, this adds support for the
selectionModeprop toComboBox, deprecatesselectedKey/onSelectionChange, and addsvalue/onChangewhich 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:
ComboBoxStateContextto get the selected items but we could add a more first-class API for this.selectionMode/value/onChangefrom S2 props for now? Do we want to support this case or wait for TagField?