diff --git a/packages/core/src/components/Combobox/Combobox.tsx b/packages/core/src/components/Combobox/Combobox.tsx index 93ccf1f661..7aef24369a 100644 --- a/packages/core/src/components/Combobox/Combobox.tsx +++ b/packages/core/src/components/Combobox/Combobox.tsx @@ -17,7 +17,12 @@ import { StickyCategoryHeader } from "./components/StickyCategoryHeader/StickyCa import { useItemsData, useKeyboardNavigation } from "./ComboboxHelpers/ComboboxHelpers"; import { getOptionId } from "./helpers"; import { ElementContent, SubIcon, VibeComponentProps, withStaticProps } from "../../types"; -import { IComboboxCategoryMap, IComboboxItem, IComboboxOption } from "./components/ComboboxConstants"; +import { + IComboboxCategoryMap, + IComboboxItem, + IComboboxOption, + COMBOBOX_LISTBOX_ID +} from "./components/ComboboxConstants"; import styles from "./Combobox.module.scss"; export interface ComboboxProps extends VibeComponentProps { @@ -282,7 +287,7 @@ const Combobox: React.FC & { data-testid={dataTestId || getTestId(ComponentDefaultTestId.COMBOBOX, id)} ellipsis={false} > -
+
& { autoFocus={autoFocus} loading={loading} searchIconName={searchIcon} + ariaExpanded={hasFilter || hasResults} + ariaHasPopup="listbox" + searchResultsContainerId={id ? `${id}-listbox` : COMBOBOX_LISTBOX_ID} /> {stickyCategories && } & { renderOnlyVisibleOptions={renderOnlyVisibleOptions} maxOptionsWithoutScroll={maxOptionsWithoutScroll} visualFocusItemIndex={visualFocusItemIndex} + id={id ? `${id}-listbox` : COMBOBOX_LISTBOX_ID} />
{hasFilter && !hasResults && !loading && renderNoResults()} diff --git a/packages/core/src/components/Combobox/__tests__/__snapshots__/combobox-snapshot-tests.test.js.snap b/packages/core/src/components/Combobox/__tests__/__snapshots__/combobox-snapshot-tests.test.js.snap index 58139c5084..5f663c73db 100644 --- a/packages/core/src/components/Combobox/__tests__/__snapshots__/combobox-snapshot-tests.test.js.snap +++ b/packages/core/src/components/Combobox/__tests__/__snapshots__/combobox-snapshot-tests.test.js.snap @@ -8,7 +8,6 @@ exports[`Combobox renders correctly when disabled 1`] = ` >
@@ -54,7 +56,8 @@ exports[`Combobox renders correctly when disabled 1`] = `
@@ -68,7 +71,6 @@ exports[`Combobox renders correctly with another noResultsMessage 1`] = ` >
@@ -114,7 +119,8 @@ exports[`Combobox renders correctly with another noResultsMessage 1`] = `
@@ -128,7 +134,6 @@ exports[`Combobox renders correctly with autoFocus 1`] = ` >
@@ -174,7 +182,8 @@ exports[`Combobox renders correctly with autoFocus 1`] = `
@@ -188,7 +197,6 @@ exports[`Combobox renders correctly with className 1`] = ` >
@@ -234,7 +245,8 @@ exports[`Combobox renders correctly with className 1`] = `
@@ -248,7 +260,6 @@ exports[`Combobox renders correctly with custom search icon 1`] = ` >
@@ -294,7 +308,8 @@ exports[`Combobox renders correctly with custom search icon 1`] = `
@@ -308,7 +323,6 @@ exports[`Combobox renders correctly with divider 1`] = ` >
@@ -354,7 +372,8 @@ exports[`Combobox renders correctly with divider 1`] = `
@@ -512,7 +534,8 @@ exports[`Combobox renders correctly with divider and colored categories 1`] = `
@@ -680,7 +705,8 @@ exports[`Combobox renders correctly with empty props 1`] = `
@@ -694,7 +720,6 @@ exports[`Combobox renders correctly with id 1`] = ` >
@@ -740,7 +768,8 @@ exports[`Combobox renders correctly with id 1`] = `
@@ -754,7 +783,6 @@ exports[`Combobox renders correctly with loading 1`] = ` >
@@ -828,7 +859,8 @@ exports[`Combobox renders correctly with loading 1`] = `
@@ -842,7 +874,6 @@ exports[`Combobox renders correctly with optionClassName 1`] = ` >
@@ -888,7 +922,8 @@ exports[`Combobox renders correctly with optionClassName 1`] = `
@@ -902,7 +937,6 @@ exports[`Combobox renders correctly with optionLineHeight 1`] = ` >
@@ -948,7 +985,8 @@ exports[`Combobox renders correctly with optionLineHeight 1`] = `
@@ -962,7 +1000,6 @@ exports[`Combobox renders correctly with optionRenderer 1`] = ` >
@@ -1008,7 +1049,8 @@ exports[`Combobox renders correctly with optionRenderer 1`] = `
@@ -1121,7 +1165,8 @@ exports[`Combobox renders correctly with optionsListHeight 1`] = `
@@ -1135,7 +1180,6 @@ exports[`Combobox renders correctly with placeholder 1`] = ` >
@@ -1181,7 +1228,8 @@ exports[`Combobox renders correctly with placeholder 1`] = `
@@ -1195,7 +1243,6 @@ exports[`Combobox renders correctly with size 1`] = ` >
@@ -1241,7 +1291,8 @@ exports[`Combobox renders correctly with size 1`] = `
diff --git a/packages/core/src/components/Combobox/__tests__/combobox-tests.test.js b/packages/core/src/components/Combobox/__tests__/combobox-tests.test.js index 576a249d19..72a863dd80 100644 --- a/packages/core/src/components/Combobox/__tests__/combobox-tests.test.js +++ b/packages/core/src/components/Combobox/__tests__/combobox-tests.test.js @@ -71,8 +71,8 @@ describe("Combobox tests", () => { ); const input = getByTestId("search_combobox-search"); expect(input.value).toBe(defaultFilter); - const treegrid = getByRole("treegrid"); - expect(treegrid.childElementCount).toBe(1); + const listbox = getByRole("listbox"); + expect(listbox.childElementCount).toBe(1); const orangeOption = getByLabelText(mockOptions[0].label); expect(orangeOption).toBeTruthy(); }); diff --git a/packages/core/src/components/Combobox/components/ComboboxConstants.ts b/packages/core/src/components/Combobox/components/ComboboxConstants.ts index 934f3e5102..dc916702e5 100644 --- a/packages/core/src/components/Combobox/components/ComboboxConstants.ts +++ b/packages/core/src/components/Combobox/components/ComboboxConstants.ts @@ -5,6 +5,7 @@ import { MutableRef } from "preact/hooks"; export const COMBOBOX_DIVIDER_ITEM = "combobox-divider"; export const COMBOBOX_CATEGORY_ITEM = "combobox-category"; export const COMBOBOX_OPTION_ITEM = "combobox-option"; +export const COMBOBOX_LISTBOX_ID = "combobox-listbox"; export enum ComboboxOptionIconType { DEFAULT = "default", diff --git a/packages/core/src/components/Combobox/components/ComboboxItems/ComboboxItems.tsx b/packages/core/src/components/Combobox/components/ComboboxItems/ComboboxItems.tsx index 321f847b5f..109849ecb5 100644 --- a/packages/core/src/components/Combobox/components/ComboboxItems/ComboboxItems.tsx +++ b/packages/core/src/components/Combobox/components/ComboboxItems/ComboboxItems.tsx @@ -27,6 +27,7 @@ export interface ComboboxItemsProps extends IComboboxOptionEvents { maxOptionsWithoutScroll?: number; itemsMap?: Map; stickyCategories?: boolean; + id?: string; } export const ComboboxItems: React.FC = forwardRef( @@ -48,7 +49,8 @@ export const ComboboxItems: React.FC = forwardRef( onActiveCategoryChanged, maxOptionsWithoutScroll, itemsMap, - stickyCategories + stickyCategories, + id }, ref: RefObject ) => { @@ -128,17 +130,19 @@ export const ComboboxItems: React.FC = forwardRef( className={cx(styles.optionsContainer, className)} items={options} itemRenderer={createItemElementRenderer} - role="treegrid" + role="listbox" scrollableClassName={styles.scrollableContainer} onItemsRendered={onItemsRender} style={style} + id={id} /> ); } else { itemsElements = (
diff --git a/packages/core/src/components/Search/Search.tsx b/packages/core/src/components/Search/Search.tsx index b1c9e7d542..f8af4f15bc 100644 --- a/packages/core/src/components/Search/Search.tsx +++ b/packages/core/src/components/Search/Search.tsx @@ -35,6 +35,8 @@ const Search = forwardRef( onFocus, onBlur, className, + ariaExpanded, + ariaHasPopup, id, "data-testid": dataTestId }: SearchProps, @@ -116,8 +118,10 @@ const Search = forwardRef( inputRole={searchResultsContainerId ? "combobox" : undefined} aria-label={inputAriaLabel} aria-busy={loading} - aria-owns={searchResultsContainerId} + aria-controls={ariaExpanded ? searchResultsContainerId : undefined} aria-activedescendant={currentAriaResultId} + aria-haspopup={ariaHasPopup} + aria-expanded={ariaExpanded} /> ); } diff --git a/packages/core/src/components/Search/Search.types.ts b/packages/core/src/components/Search/Search.types.ts index ba94499c48..f95af33f46 100644 --- a/packages/core/src/components/Search/Search.types.ts +++ b/packages/core/src/components/Search/Search.types.ts @@ -57,6 +57,14 @@ export interface SearchProps extends VibeComponentProps { * Aria-label for the search input, important for accessibility. */ inputAriaLabel?: React.AriaAttributes["aria-label"]; + /** + * aria to be set if the popup is open. + */ + ariaExpanded?: React.AriaAttributes["aria-expanded"]; + /** + * aria to be set the sarch result popup's type. + */ + ariaHasPopup?: React.AriaAttributes["aria-haspopup"]; /** * Rate at which search input changes are debounced. */ diff --git a/packages/core/src/components/Search/__tests__/Search.test.tsx b/packages/core/src/components/Search/__tests__/Search.test.tsx index 7f46cd7d90..15518c437d 100644 --- a/packages/core/src/components/Search/__tests__/Search.test.tsx +++ b/packages/core/src/components/Search/__tests__/Search.test.tsx @@ -118,9 +118,9 @@ describe("Search", () => { expect(getByRole("combobox")).toBeInTheDocument(); }); - it("should set aria-owns when searchResultsContainerId is provided", () => { - const { getByRole } = renderSearch({ searchResultsContainerId: "search-results" }); - expect(getByRole("combobox")).toHaveAttribute("aria-owns", "search-results"); + it("should set aria-controls when searchResultsContainerId is provided and ariaExpanded is true", () => { + const { getByRole } = renderSearch({ searchResultsContainerId: "search-results", ariaExpanded: true }); + expect(getByRole("combobox")).toHaveAttribute("aria-controls", "search-results"); }); it("should set aria-activedescendant when activeDescendant is provided", () => {