diff --git a/packages/combobox/.size-snapshot.json b/packages/combobox/.size-snapshot.json
index c74341e29..9ef801005 100644
--- a/packages/combobox/.size-snapshot.json
+++ b/packages/combobox/.size-snapshot.json
@@ -1,20 +1,20 @@
{
"index.cjs.js": {
- "bundled": 26329,
- "minified": 14211,
+ "bundled": 26260,
+ "minified": 14174,
"gzipped": 4002
},
"index.esm.js": {
- "bundled": 25261,
- "minified": 13144,
- "gzipped": 3979,
+ "bundled": 25192,
+ "minified": 13107,
+ "gzipped": 3980,
"treeshaked": {
"rollup": {
"code": 1296,
"import_statements": 177
},
"webpack": {
- "code": 13081
+ "code": 13044
}
}
}
diff --git a/packages/combobox/package.json b/packages/combobox/package.json
index a6ff36bfe..58387754d 100644
--- a/packages/combobox/package.json
+++ b/packages/combobox/package.json
@@ -23,7 +23,7 @@
"@babel/runtime": "^7.8.4",
"@zendeskgarden/container-field": "^3.0.7",
"@zendeskgarden/container-utilities": "^1.0.8",
- "downshift": "^7.6.0"
+ "downshift": "^8.0.0"
},
"peerDependencies": {
"prop-types": "^15.6.1",
diff --git a/packages/combobox/src/ComboboxContainer.spec.tsx b/packages/combobox/src/ComboboxContainer.spec.tsx
index 8892898eb..82a29d49d 100644
--- a/packages/combobox/src/ComboboxContainer.spec.tsx
+++ b/packages/combobox/src/ComboboxContainer.spec.tsx
@@ -6,6 +6,7 @@
*/
import React, { createRef, PropsWithChildren } from 'react';
+import { act } from 'react-dom/test-utils';
import { render, RenderResult } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { ComboboxContainer, useCombobox } from './';
@@ -279,11 +280,26 @@ describe('ComboboxContainer', () => {
}
});
+ it('expands and collapses the listbox on input click', async () => {
+ const { getByTestId } = render();
+ const input = getByTestId('input');
+
+ expect(input).toHaveAttribute('aria-expanded', 'false');
+
+ await user.click(input);
+
+ expect(input).toHaveAttribute('aria-expanded', 'true');
+
+ await user.click(input);
+
+ expect(input).toHaveAttribute('aria-expanded', 'false');
+ });
+
describe('when focused', () => {
let input: HTMLElement;
let listboxOptions: HTMLElement[];
- beforeEach(async () => {
+ beforeEach(() => {
const { getByTestId, getAllByRole } = render(
);
@@ -291,7 +307,7 @@ describe('ComboboxContainer', () => {
input = getByTestId('input');
listboxOptions = getAllByRole('option');
- await user.click(input);
+ input.focus();
});
it('expands and activates the first option on down arrow', async () => {
@@ -395,7 +411,7 @@ describe('ComboboxContainer', () => {
let listboxOptions: HTMLElement[];
let rerender: RenderResult['rerender'];
- beforeEach(async () => {
+ beforeEach(() => {
const {
getByTestId,
getAllByRole,
@@ -415,7 +431,7 @@ describe('ComboboxContainer', () => {
listboxOptions = getAllByRole('option');
rerender = _rerender;
- await user.click(input);
+ input.focus();
});
it('applies the correct accessibility attributes', () => {
@@ -548,7 +564,9 @@ describe('ComboboxContainer', () => {
expect(listboxOptions[1]).toHaveAttribute('aria-selected', 'false');
expect(listboxOptions[3]).toHaveAttribute('aria-selected', 'true');
- await user.click(tags[1]);
+ await act(async () => {
+ await user.click(tags[1]);
+ });
expect(tags[1]).toHaveFocus();
@@ -667,6 +685,19 @@ describe('ComboboxContainer', () => {
expect(trigger).not.toHaveAttribute('aria-expanded');
}
});
+
+ it('remains collapsed on input click', async () => {
+ const { getByTestId } = render(
+
+ );
+ const input = getByTestId('input');
+
+ expect(input).toHaveAttribute('aria-expanded', 'false');
+
+ await user.click(input);
+
+ expect(input).toHaveAttribute('aria-expanded', 'false');
+ });
});
describe('with disabled options', () => {
@@ -694,7 +725,7 @@ describe('ComboboxContainer', () => {
listboxOptions = getAllByRole('option');
rerender = _rerender;
- await user.click(input);
+ input.focus();
await user.keyboard('{ArrowDown}');
});
@@ -769,11 +800,11 @@ describe('ComboboxContainer', () => {
await user.click(input);
await user.keyboard('{ArrowDown}');
- expect(handleChange).toHaveBeenCalledTimes(1);
+ expect(handleChange).toHaveBeenCalledTimes(2);
const changeTypes = handleChange.mock.calls.map(([change]) => change.type);
- expect(changeTypes).toMatchObject(['input:keyDown:ArrowDown']);
+ expect(changeTypes).toMatchObject(['input:click', 'input:keyDown:ArrowDown']);
});
it('handles controlled selection as expected', () => {
@@ -864,7 +895,12 @@ describe('ComboboxContainer', () => {
const triggerRef = createRef();
const inputRef = createRef();
const listboxRef = createRef();
- const { selection: _selection, removeSelection: _removeSelection } = useCombobox({
+ const {
+ selection: _selection,
+ removeSelection: _removeSelection,
+ getInputProps,
+ getListboxProps
+ } = useCombobox({
triggerRef,
inputRef,
listboxRef,
@@ -875,7 +911,13 @@ describe('ComboboxContainer', () => {
selection = _selection;
removeSelection = _removeSelection;
- return <>{children}>;
+ return (
+ <>
+
+ {children}
+
+ >
+ );
};
it('clears multiselectable values', async () => {
@@ -1014,7 +1056,10 @@ describe('ComboboxContainer', () => {
const trigger = getByTestId('trigger');
await user.click(trigger);
- await user.click(document.body);
+
+ await act(async () => {
+ await user.click(document.body);
+ });
expect(input).toHaveAttribute('aria-expanded', 'false');
});
@@ -1092,7 +1137,7 @@ describe('ComboboxContainer', () => {
);
const tag = getByTestId('tag');
@@ -1123,7 +1168,7 @@ describe('ComboboxContainer', () => {
layout="Garden"
isEditable={false}
isMultiselectable
- options={[{ value: 'test-1' }, { value: 'test-2', selected: true }, { value: 'test-2' }]}
+ options={[{ value: 'test-1' }, { value: 'test-2', selected: true }, { value: 'test-3' }]}
/>
);
const tag = getByTestId('tag');
diff --git a/packages/combobox/src/useCombobox.ts b/packages/combobox/src/useCombobox.ts
index 689587acb..bf921a957 100644
--- a/packages/combobox/src/useCombobox.ts
+++ b/packages/combobox/src/useCombobox.ts
@@ -181,9 +181,13 @@ export const useCombobox = <
false
};
- case useDownshift.stateChangeTypes.InputFocus:
- // Prevent expansion on focus.
- return { ...state, isOpen: false };
+ case useDownshift.stateChangeTypes.InputClick:
+ if (!isAutocomplete) {
+ // Prevent input click listbox expansion on non-autocomplete comboboxes.
+ changes.isOpen = state.isOpen;
+ }
+
+ break;
case useDownshift.stateChangeTypes.InputKeyDownArrowDown:
case useDownshift.stateChangeTypes.FunctionOpenMenu:
@@ -300,7 +304,7 @@ export const useCombobox = <
initialHighlightedIndex: initialActiveIndex,
onStateChange: handleDownshiftStateChange,
stateReducer,
- environment: win
+ environment: win as any /* HACK around Downshift's addition of Node to environment */
});
const closeListbox = useCallback(() => {
@@ -413,7 +417,7 @@ export const useCombobox = <
previousStateRef.current = {
...previousStateRef.current,
- type: useDownshift.stateChangeTypes.InputFocus
+ type: useDownshift.stateChangeTypes.InputClick
};
}
});
@@ -449,11 +453,11 @@ export const useCombobox = <
};
if (isEditable && triggerContainsInput) {
- const handleClick = (event: MouseEvent) => {
+ const handleClick = (event: React.MouseEvent) => {
if (disabled) {
event.preventDefault();
} else if (isAutocomplete) {
- triggerProps.onClick(event);
+ triggerProps.onClick && triggerProps.onClick(event);
} else {
inputRef.current?.focus();
}
@@ -591,13 +595,7 @@ export const useCombobox = <
);
const getInputProps = useCallback(
- ({
- role = isEditable ? 'combobox' : null,
- 'aria-labelledby': ariaLabeledBy = null,
- onClick,
- onFocus,
- ...other
- } = {}) => {
+ ({ role = isEditable ? 'combobox' : null, onClick, onFocus, ...other } = {}) => {
const inputProps = {
'data-garden-container-id': 'containers.combobox.input',
'data-garden-container-version': PACKAGE_VERSION,
@@ -613,11 +611,10 @@ export const useCombobox = <
triggerRef.current?.contains(event.target) &&
event.stopPropagation();
- return getDownshiftInputProps({
+ return getDownshiftInputProps({
...inputProps,
disabled,
role,
- 'aria-labelledby': ariaLabeledBy,
'aria-autocomplete': isAutocomplete ? 'list' : undefined,
onClick: composeEventHandlers(onClick, handleClick),
...getFieldInputProps(),
@@ -672,7 +669,7 @@ export const useCombobox = <
triggerRef.current?.contains(event.target) &&
event.stopPropagation();
- const handleKeyDown = (event: KeyboardEvent) => {
+ const handleKeyDown = (event: React.KeyboardEvent) => {
if (event.key === KEYS.BACKSPACE || event.key === KEYS.DELETE) {
setDownshiftSelection(option.value);
} else {
@@ -699,7 +696,7 @@ export const useCombobox = <
triggerRef.current?.focus();
}
- inputProps.onKeyDown(event);
+ inputProps.onKeyDown && inputProps.onKeyDown(event);
}
}
};
@@ -716,13 +713,12 @@ export const useCombobox = <
);
const getListboxProps = useCallback(
- ({ role = 'listbox', 'aria-labelledby': ariaLabeledBy = null, ...other }) =>
- getDownshiftListboxProps({
+ ({ role = 'listbox', ...other }) =>
+ getDownshiftListboxProps({
'data-garden-container-id': 'containers.combobox.listbox',
'data-garden-container-version': PACKAGE_VERSION,
ref: listboxRef,
role,
- 'aria-labelledby': ariaLabeledBy,
'aria-multiselectable': isMultiselectable ? true : undefined,
...other
} as IDownshiftListboxProps),
@@ -772,9 +768,10 @@ export const useCombobox = <
};
}
- return getDownshiftOptionProps({
+ return getDownshiftOptionProps({
item: option.value,
index: values.indexOf(option.value),
+ 'aria-disabled': undefined,
'aria-selected': ariaSelected,
...optionProps
} as IDownshiftOptionProps);
diff --git a/packages/combobox/src/utils.ts b/packages/combobox/src/utils.ts
index 085472bf2..426ba4fc2 100644
--- a/packages/combobox/src/utils.ts
+++ b/packages/combobox/src/utils.ts
@@ -20,7 +20,7 @@ const typeMap: Record = {
[useDownshift.stateChangeTypes.FunctionSetInputValue]: 'fn:setInputValue',
[useDownshift.stateChangeTypes.InputBlur]: 'input:blur',
[useDownshift.stateChangeTypes.InputChange]: 'input:change',
- [useDownshift.stateChangeTypes.InputFocus]: 'input:focus',
+ [useDownshift.stateChangeTypes.InputClick]: 'input:click',
[useDownshift.stateChangeTypes.InputKeyDownArrowDown]: `input:keyDown:${KEYS.DOWN}`,
[useDownshift.stateChangeTypes.InputKeyDownArrowUp]: `input:keyDown:${KEYS.UP}`,
[useDownshift.stateChangeTypes.InputKeyDownEnd]: `input:keyDown:${KEYS.END}`,
diff --git a/packages/combobox/yarn.lock b/packages/combobox/yarn.lock
index 13c3402e7..ee90783bf 100644
--- a/packages/combobox/yarn.lock
+++ b/packages/combobox/yarn.lock
@@ -14,10 +14,10 @@ compute-scroll-into-view@^2.0.4:
resolved "https://registry.yarnpkg.com/compute-scroll-into-view/-/compute-scroll-into-view-2.0.4.tgz#2b444b2b9e4724819d2531efacb7ac094155fdf6"
integrity sha512-y/ZA3BGnxoM/QHHQ2Uy49CLtnWPbt4tTPpEEZiEmmiWBFKjej7nEyH8Ryz54jH0MLXflUYA3Er2zUxPSJu5R+g==
-downshift@^7.6.0:
- version "7.6.2"
- resolved "https://registry.yarnpkg.com/downshift/-/downshift-7.6.2.tgz#16fc951b7ff8f9c1c47d0f71b5ff10d78fb06e4c"
- integrity sha512-iOv+E1Hyt3JDdL9yYcOgW7nZ7GQ2Uz6YbggwXvKUSleetYhU2nXD482Rz6CzvM4lvI1At34BYruKAL4swRGxaA==
+downshift@^8.0.0:
+ version "8.1.0"
+ resolved "https://registry.yarnpkg.com/downshift/-/downshift-8.1.0.tgz#72023513256134723fe807a54168ebc64f9ddf6c"
+ integrity sha512-e9EBBLZvB2G73qT272x3hExttGCH1q1usbjirm+1aMcFXuzSWhgBdbnAHPlFI2rEq61cU/kDrEIMrY+ozMhvmg==
dependencies:
"@babel/runtime" "^7.14.8"
compute-scroll-into-view "^2.0.4"