diff --git a/packages/mui-base/src/ListboxUnstyled/useListbox.test.tsx b/packages/mui-base/src/ListboxUnstyled/useListbox.test.tsx index 1b510ccf881553..f3109b79987bb1 100644 --- a/packages/mui-base/src/ListboxUnstyled/useListbox.test.tsx +++ b/packages/mui-base/src/ListboxUnstyled/useListbox.test.tsx @@ -1,7 +1,8 @@ import * as React from 'react'; -import { useListbox } from '@mui/base/ListboxUnstyled'; import { expect } from 'chai'; -import { createRenderer } from 'test/utils'; +import { SinonSpy, spy } from 'sinon'; +import { useListbox } from '@mui/base/ListboxUnstyled'; +import { createRenderer, createEvent, fireEvent } from 'test/utils'; describe('useListbox', () => { const { render } = createRenderer(); @@ -56,4 +57,72 @@ describe('useListbox', () => { expect(listboxes[0].id).not.to.equal(listboxes[1].id); }); }); + + describe('preventing default behavior on keyDown', () => { + ['ArrowUp', 'ArrowDown', 'Home', 'End', 'PageUp', 'PageDown', 'Enter', ' '].forEach((key) => + it(`prevents default behavior when ${key} is pressed in activeDescendant focus management mode`, () => { + const Listbox = () => { + const { getRootProps } = useListbox({ options: [], focusManagement: 'activeDescendant' }); + return
; + }; + + const { getByRole } = render(); + const listbox = getByRole('listbox'); + listbox.focus(); + + const event = createEvent.keyDown(listbox, { + key, + }); + + event.preventDefault = spy(); + fireEvent(listbox, event); + + expect((event.preventDefault as SinonSpy).calledOnce).to.equal(true); + }), + ); + + ['ArrowUp', 'ArrowDown', 'Home', 'End', 'PageUp', 'PageDown'].forEach((key) => + it(`prevents default behavior when ${key} is pressed in DOM focus management mode`, () => { + const Listbox = () => { + const { getRootProps } = useListbox({ options: [], focusManagement: 'DOM' }); + return
; + }; + + const { getByRole } = render(); + const listbox = getByRole('listbox'); + listbox.focus(); + + const event = createEvent.keyDown(listbox, { + key, + }); + + event.preventDefault = spy(); + fireEvent(listbox, event); + + expect((event.preventDefault as SinonSpy).calledOnce).to.equal(true); + }), + ); + + ['Enter', ' '].forEach((key) => + it(`does not prevent default behavior when ${key} is pressed in DOM focus management mode`, () => { + const Listbox = () => { + const { getRootProps } = useListbox({ options: [], focusManagement: 'DOM' }); + return
; + }; + + const { getByRole } = render(); + const listbox = getByRole('listbox'); + listbox.focus(); + + const event = createEvent.keyDown(listbox, { + key, + }); + + event.preventDefault = spy(); + fireEvent(listbox, event); + + expect((event.preventDefault as SinonSpy).notCalled).to.equal(true); + }), + ); + }); }); diff --git a/packages/mui-base/src/ListboxUnstyled/useListbox.ts b/packages/mui-base/src/ListboxUnstyled/useListbox.ts index f6d769f103c335..40a96391b717ab 100644 --- a/packages/mui-base/src/ListboxUnstyled/useListbox.ts +++ b/packages/mui-base/src/ListboxUnstyled/useListbox.ts @@ -160,16 +160,15 @@ export default function useListbox(props: UseListboxParameters return; } - const keysToPreventDefault = [ - ' ', - 'Enter', - 'ArrowUp', - 'ArrowDown', - 'Home', - 'End', - 'PageUp', - 'PageDown', - ]; + const keysToPreventDefault = ['ArrowUp', 'ArrowDown', 'Home', 'End', 'PageUp', 'PageDown']; + + if (focusManagement === 'activeDescendant') { + // When the child element is focused using the activeDescendant attribute, + // the listbox handles keyboard events on its behalf. + // We have to `preventDefault()` is this case to prevent the browser from + // scrolling the view when space is pressed or submitting forms when enter is pressed. + keysToPreventDefault.push(' ', 'Enter'); + } if (keysToPreventDefault.includes(event.key)) { event.preventDefault();