diff --git a/packages/mui-material/src/Select/Select.test.js b/packages/mui-material/src/Select/Select.test.js index 100b69b1ca4a9f..8452ede6fbdc3a 100644 --- a/packages/mui-material/src/Select/Select.test.js +++ b/packages/mui-material/src/Select/Select.test.js @@ -152,6 +152,36 @@ describe(' + + Zero + + + One + + + Two + + , + ); + + const trigger = getByRole('combobox'); + fireEvent.mouseDown(trigger); + + const options = getAllByRole('option'); + fireEvent.keyDown(options[0], { key: 'ArrowDown' }); + fireEvent.keyDown(options[1], { key: 'ArrowDown' }); + fireEvent.keyDown(options[2], { key: ' ' }); + + expect(handleChange.callCount).to.equal(1); + expect(handleKeyDown.callCount).to.equal(3); + expect(handleChange.firstCall.args[0].target.value).to.equal('2'); + }); + [' ', 'ArrowUp', 'ArrowDown', 'Enter'].forEach((key) => { it(`should open menu when pressed ${key} key on select`, async () => { render( diff --git a/packages/mui-material/src/Select/SelectInput.js b/packages/mui-material/src/Select/SelectInput.js index a1acee8485330f..2af01b392cbccf 100644 --- a/packages/mui-material/src/Select/SelectInput.js +++ b/packages/mui-material/src/Select/SelectInput.js @@ -258,6 +258,21 @@ const SelectInput = React.forwardRef(function SelectInput(props, ref) { } }; + const clonedOnChange = (event, newValue, child) => { + // Redefine target to allow name and value to be read. + // This allows seamless integration with the most popular form libraries. + // https://github.com/mui/material-ui/issues/13485#issuecomment-676048492 + // Clone the event to not override `target` of the original event. + const nativeEvent = event.nativeEvent || event; + const clonedEvent = new nativeEvent.constructor(nativeEvent.type, nativeEvent); + + Object.defineProperty(clonedEvent, 'target', { + writable: true, + value: { value: newValue, name }, + }); + onChange(clonedEvent, child); + }; + const handleItemClick = (child) => (event) => { let newValue; @@ -286,18 +301,7 @@ const SelectInput = React.forwardRef(function SelectInput(props, ref) { setValueState(newValue); if (onChange) { - // Redefine target to allow name and value to be read. - // This allows seamless integration with the most popular form libraries. - // https://github.com/mui/material-ui/issues/13485#issuecomment-676048492 - // Clone the event to not override `target` of the original event. - const nativeEvent = event.nativeEvent || event; - const clonedEvent = new nativeEvent.constructor(nativeEvent.type, nativeEvent); - - Object.defineProperty(clonedEvent, 'target', { - writable: true, - value: { value: newValue, name }, - }); - onChange(clonedEvent, child); + clonedOnChange(event, newValue, child); } } @@ -324,6 +328,22 @@ const SelectInput = React.forwardRef(function SelectInput(props, ref) { } }; + const handleItemKeyDown = (child) => (event) => { + if (event.key === ' ') { + const newValue = child.props.value; + if (newValue !== value) { + setValueState(newValue); + + if (onChange) { + clonedOnChange(event, newValue, child); + } + } + update(false, event); + } + + child?.props?.onKeyDown?.(event); + }; + const open = displayNode !== null && openState; const handleBlur = (event) => { @@ -408,6 +428,7 @@ const SelectInput = React.forwardRef(function SelectInput(props, ref) { child.props.onKeyUp(event); } }, + onKeyDown: handleItemKeyDown(child), role: 'option', selected, value: undefined, // The value is most likely not a valid HTML attribute.