Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/@react-aria/combobox/src/useComboBox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,8 @@ export function useComboBox<T, M extends SelectionMode = 'single'>(props: AriaCo
let {isInvalid, validationErrors, validationDetails} = state.displayValidation;
let {labelProps, inputProps, descriptionProps, errorMessageProps} = useTextField({
...props,
// In multi-select mode, only set required if the selection is empty.
isRequired: props.selectionMode === 'multiple' ? props.isRequired && state.selectionManager.isEmpty : props.isRequired,
onChange: state.setInputValue,
onKeyDown: !isReadOnly ? chain(state.isOpen && collectionProps.onKeyDown, onKeyDown, props.onKeyDown) : props.onKeyDown,
onBlur,
Expand Down
7 changes: 4 additions & 3 deletions packages/@react-stately/combobox/src/useComboBoxState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -481,10 +481,10 @@ export function useComboBoxState<T extends object, M extends SelectionMode = 'si
}
};

let valueOnFocus = useRef(inputValue);
let valueOnFocus = useRef([inputValue, displayValue]);
let setFocused = (isFocused: boolean) => {
if (isFocused) {
valueOnFocus.current = inputValue;
valueOnFocus.current = [inputValue, displayValue];
if (menuTrigger === 'focus' && !props.isReadOnly) {
open(null, 'focus');
}
Expand All @@ -493,7 +493,8 @@ export function useComboBoxState<T extends object, M extends SelectionMode = 'si
commitValue();
}

if (inputValue !== valueOnFocus.current) {
// Commit validation if the input value or selected items changed.
if (inputValue !== valueOnFocus.current[0] || displayValue !== valueOnFocus.current[1]) {
validation.commitValidation();
}
}
Expand Down
56 changes: 56 additions & 0 deletions packages/react-aria-components/test/ComboBox.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -819,6 +819,62 @@ describe('ComboBox', () => {
expect(onChange).toHaveBeenLastCalledWith(['1']);
});

it('should support isRequired with multiple selection', async () => {
let {container, getByTestId} = render(
<Form data-testid="form">
<ComboBox name="combobox" selectionMode="multiple" isRequired>
<Label>Favorite Animal</Label>
<Input />
<Button />
<FieldError />
<Popover>
<ListBox>
<ListBoxItem id="1">Cat</ListBoxItem>
<ListBoxItem id="2">Dog</ListBoxItem>
<ListBoxItem id="3">Kangaroo</ListBoxItem>
</ListBox>
</Popover>
</ComboBox>
</Form>
);
let comboboxTester = testUtilUser.createTester('ComboBox', {root: container});
let combobox = comboboxTester.combobox;

expect(combobox).toHaveAttribute('required');
expect(combobox.validity.valid).toBe(false);

act(() => {getByTestId('form').checkValidity();});
expect(combobox).toHaveAttribute('aria-describedby');
expect(container.querySelector('.react-aria-ComboBox')).toHaveAttribute('data-invalid');

await comboboxTester.open();
let options = comboboxTester.options();
await user.click(options[0]);

act(() => combobox.blur());
expect(combobox).not.toHaveAttribute('required');
expect(combobox.validity.valid).toBe(true);
expect(container.querySelector('.react-aria-ComboBox')).not.toHaveAttribute('data-invalid');

let hiddenInputs = container.querySelectorAll('input[type="hidden"]');
expect(hiddenInputs).toHaveLength(1);
expect(hiddenInputs[0]).toHaveAttribute('name', 'combobox');
expect(hiddenInputs[0]).toHaveAttribute('value', '1');

await comboboxTester.open();
options = comboboxTester.options();
await user.click(options[0]);
act(() => combobox.blur());
expect(combobox).toHaveAttribute('required');
expect(combobox.validity.valid).toBe(false);
expect(combobox).toHaveAttribute('aria-describedby');

hiddenInputs = container.querySelectorAll('input[type="hidden"]');
expect(hiddenInputs).toHaveLength(1);
expect(hiddenInputs[0]).toHaveAttribute('name', 'combobox');
expect(hiddenInputs[0]).toHaveAttribute('value', '');
});

it('should not close the combobox when clicking on the input', async () => {
let onOpenChange = jest.fn();
let {container, getByRole} = render(<TestComboBox onOpenChange={onOpenChange} />);
Expand Down
Loading