Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
5 changes: 5 additions & 0 deletions .changeset/big-corners-type.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@primer/react": patch
---

Don't focus Autocomplete menu items while menu is closed: fix SR announcement bug and menu item skipping bug
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,18 @@
}

.OverlayInputBar {
/* stylelint-disable-next-line primer/spacing */
padding: 1px;
border-width: 0;
border-bottom-width: var(--borderWidth-default);
border-color: var(--borderColor-default);
border-style: solid;
}
Comment thread
llastflowers marked this conversation as resolved.

.OverlayInput {
display: flex;
border: 0;
padding-left: var(--base-size-16);
padding-right: var(--base-size-16);
/* stylelint-disable-next-line primer/borders */
border-radius: calc(var(--borderRadius-large) - 1px);
padding-top: var(--base-size-4);
padding-bottom: var(--base-size-4);
box-shadow: none;
Expand Down
42 changes: 41 additions & 1 deletion packages/react/src/Autocomplete/Autocomplete.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {render, fireEvent, screen, waitFor} from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import React from 'react'
import {createRef} from 'react'
import {describe, expect, it, vi} from 'vitest'
import type {AutocompleteInputProps} from '../Autocomplete'
import Autocomplete from '../Autocomplete'
Expand Down Expand Up @@ -269,6 +269,46 @@ describe('Autocomplete', () => {
})

describe('Autocomplete.Menu', () => {
it('only sets an active descendant while a menu rendered without an overlay is open', async () => {
const user = userEvent.setup()
const ancestorRef = createRef<HTMLDivElement>()
const handleAncestorKeyDown = (event: KeyboardEvent) => {
if (!event.defaultPrevented && event.key === 'ArrowDown') {
ancestorRef.current?.querySelector<HTMLElement>('[role="option"]')?.focus()
}
}

render(
<BaseStyles>
<div ref={ancestorRef}>
<label htmlFor="autocompleteInput" id="autocompleteLabel">
Autocomplete field
</label>
<Autocomplete id="autocompleteId">
<Autocomplete.Input id="autocompleteInput" />
<Autocomplete.Menu items={mockItems} selectedItemIds={[]} aria-labelledby="autocompleteLabel" />
</Autocomplete>
</div>
</BaseStyles>,
)
const inputNode = screen.getByRole('combobox', {name: AUTOCOMPLETE_LABEL})
ancestorRef.current?.addEventListener('keydown', handleAncestorKeyDown)

await user.click(inputNode)
expect(inputNode).not.toHaveAttribute('aria-activedescendant')

await user.keyboard('{ArrowDown}')
await waitFor(() => {
expect(inputNode).toHaveFocus()
expect(inputNode).toHaveAttribute('aria-activedescendant', mockItems[0].id)
expect(screen.getByRole('option', {name: mockItems[0].text})).not.toHaveFocus()
})

// eslint-disable-next-line github/no-blur
fireEvent.blur(inputNode)
await waitFor(() => expect(inputNode).not.toHaveAttribute('aria-activedescendant'))
})

it('calls a custom filter function', async () => {
const user = userEvent.setup()
const filterFnMock = vi.fn()
Expand Down
10 changes: 10 additions & 0 deletions packages/react/src/Autocomplete/AutocompleteInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const AutocompleteInput = React.forwardRef(
onBlur,
onChange,
onKeyDown,
onKeyDownCapture,
onKeyUp,
onKeyPress,
value,
Expand Down Expand Up @@ -111,6 +112,14 @@ const AutocompleteInput = React.forwardRef(
[inputRef, setInputValue, setHighlightRemainingText, onKeyDown, showMenu, setShowMenu],
)

const handleInputKeyDownCapture: KeyboardEventHandler<HTMLInputElement> = event => {
onKeyDownCapture?.(event)

if (!showMenu && ARROW_KEYS_NAV.has(event.key) && !event.altKey) {
event.preventDefault()
}
}

const handleInputKeyUp: KeyboardEventHandler<HTMLInputElement> = useCallback(
event => {
onKeyUp?.(event)
Expand Down Expand Up @@ -174,6 +183,7 @@ const AutocompleteInput = React.forwardRef(
onBlur={handleInputBlur}
onChange={handleInputChange}
onKeyDown={handleInputKeyDown}
onKeyDownCapture={handleInputKeyDownCapture}
onKeyPress={onInputKeyPress}
onKeyUp={handleInputKeyUp}
ref={mergedRef}
Expand Down
1 change: 1 addition & 0 deletions packages/react/src/Autocomplete/AutocompleteMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ function AutocompleteMenu<T extends AutocompleteItemProps>(props: AutocompleteMe
useFocusZone(
{
containerRef: listContainerRef,
disabled: !showMenu,
focusOutBehavior: 'wrap',
focusableElementFilter: element => {
return !(element instanceof HTMLInputElement)
Expand Down
Loading