From aa2d4352a0b045894971622df2425d5ab58f2104 Mon Sep 17 00:00:00 2001 From: Pareder Date: Fri, 12 Jun 2026 12:26:45 +0300 Subject: [PATCH] fix: option position announcement for virtual mode --- src/OptionList.tsx | 66 ++++++++++++- tests/Accessibility.test.tsx | 99 ++++++++++++++++++++ tests/__snapshots__/OptionList.test.tsx.snap | 26 +++-- tests/__snapshots__/Select.test.tsx.snap | 12 +++ tests/__snapshots__/Tags.test.tsx.snap | 27 +++--- 5 files changed, 195 insertions(+), 35 deletions(-) diff --git a/src/OptionList.tsx b/src/OptionList.tsx index 21e693083..784602dbb 100644 --- a/src/OptionList.tsx +++ b/src/OptionList.tsx @@ -275,6 +275,12 @@ const OptionList: React.ForwardRefRenderFunction = (_, r const getLabel = (item: Record) => item.label; + // Skip group headers to match native , + ); + + // The hidden accessibility container is the listbox itself in virtual mode + const getHiddenOptions = () => + Array.from(document.querySelectorAll('#virtual-select_list div[role="option"]')); + + // Active index is 0, so the hidden container renders options 0 and 1 + let hiddenOptions = getHiddenOptions(); + expect(hiddenOptions.map((option) => option.getAttribute('aria-posinset'))).toEqual([ + '1', + '2', + ]); + hiddenOptions.forEach((option) => { + expect(option).toHaveAttribute('aria-setsize', '5'); + }); + + // Move active option to the middle of the list + keyDown(container.querySelector('input')!, KeyCode.DOWN); + keyDown(container.querySelector('input')!, KeyCode.DOWN); + + // Active index is 2, so the hidden container renders options 1, 2 and 3 + hiddenOptions = getHiddenOptions(); + expect(hiddenOptions.map((option) => option.getAttribute('aria-posinset'))).toEqual([ + '2', + '3', + '4', + ]); + hiddenOptions.forEach((option) => { + expect(option).toHaveAttribute('aria-setsize', '5'); + }); + }); + + it('aria-posinset and aria-setsize should skip group headers like native optgroup', () => { + const { container } = render( +