From 85486be85334d61a5e03673cb9fca37fc72290e4 Mon Sep 17 00:00:00 2001 From: Albert Yu Date: Wed, 17 Jun 2026 07:06:31 +0800 Subject: [PATCH 1/3] Do not submit form when committing freeSolo value with the Enter key --- .../autocomplete-free-solo-enter.tsx | 89 ++++++++ .../base-ui-autocomplete-enter.module.css | 191 ++++++++++++++++++ .../base-ui-autocomplete-enter.tsx | 68 +++++++ .../src/Autocomplete/Autocomplete.test.js | 34 ++++ .../src/useAutocomplete/useAutocomplete.js | 9 +- 5 files changed, 389 insertions(+), 2 deletions(-) create mode 100644 docs/pages/experiments/autocomplete-free-solo-enter.tsx create mode 100644 docs/pages/experiments/base-ui-autocomplete-enter.module.css create mode 100644 docs/pages/experiments/base-ui-autocomplete-enter.tsx diff --git a/docs/pages/experiments/autocomplete-free-solo-enter.tsx b/docs/pages/experiments/autocomplete-free-solo-enter.tsx new file mode 100644 index 00000000000000..387ca6f2e3d488 --- /dev/null +++ b/docs/pages/experiments/autocomplete-free-solo-enter.tsx @@ -0,0 +1,89 @@ +'use client'; +import * as React from 'react'; +import Autocomplete, { createFilterOptions } from '@mui/material/Autocomplete'; +import Box from '@mui/material/Box'; +import Button from '@mui/material/Button'; +import Stack from '@mui/material/Stack'; +import TextField from '@mui/material/TextField'; +import Typography from '@mui/material/Typography'; + +const options = ['New York', 'New Orleans', 'New Delhi', 'London', 'Paris']; +const filterOptions = createFilterOptions(); +const getOptionLabel = (option: string) => option; + +export default function AutocompleteFreeSoloEnter() { + const [open, setOpen] = React.useState(false); + const [value, setValue] = React.useState(null); + const [inputValue, setInputValue] = React.useState(''); + const [highlightedOption, setHighlightedOption] = React.useState(null); + const [submitCount, setSubmitCount] = React.useState(0); + + const handleKeyDown = ( + event: React.KeyboardEvent & { defaultMuiPrevented?: boolean }, + ) => { + if (event.key !== 'Enter') { + return; + } + + const textbox = event.currentTarget.querySelector('[role="combobox"]'); + const activeDescendantId = textbox?.getAttribute('aria-activedescendant'); + const activeOption = activeDescendantId ? document.getElementById(activeDescendantId) : null; + const activeOptionIndex = Number(activeOption?.getAttribute('data-option-index')); + const filteredOptions = filterOptions(options, { inputValue, getOptionLabel }); + const activeDescendantOption = Number.isNaN(activeOptionIndex) + ? null + : filteredOptions[activeOptionIndex]; + const optionToCommit = activeDescendantOption ?? highlightedOption; + + if (!optionToCommit) { + return; + } + + event.defaultMuiPrevented = true; + event.preventDefault(); + setValue(optionToCommit); + setInputValue(optionToCommit); + setOpen(false); + }; + + return ( + { + event.preventDefault(); + setSubmitCount((count) => count + 1); + }} + sx={{ p: 4, width: 360 }} + > + + + autoHighlight + freeSolo + open={open} + onOpen={() => setOpen(true)} + onClose={() => setOpen(false)} + value={value} + inputValue={inputValue} + onChange={(_event, newValue) => { + setValue(newValue); + }} + onHighlightChange={(_event, option) => { + setHighlightedOption(option); + }} + onInputChange={(_event, newInputValue) => { + setInputValue(newInputValue); + setHighlightedOption(null); + }} + onKeyDown={handleKeyDown} + options={options} + renderInput={(params) => } + /> + + Value: {value ?? '-'} + Submits: {submitCount} + + + ); +} diff --git a/docs/pages/experiments/base-ui-autocomplete-enter.module.css b/docs/pages/experiments/base-ui-autocomplete-enter.module.css new file mode 100644 index 00000000000000..e980a4f3d92bf4 --- /dev/null +++ b/docs/pages/experiments/base-ui-autocomplete-enter.module.css @@ -0,0 +1,191 @@ +.Form { + padding: 2rem; + display: flex; + flex-direction: column; + gap: 0.75rem; + align-items: flex-start; + font-family: system-ui, sans-serif; +} + +.Input { + box-sizing: border-box; + padding: 0 0.5rem; + margin: 0; + border-radius: 0; + border: 1px solid oklch(14.5% 0 0deg); + width: 16rem; + height: 2rem; + font-family: inherit; + font-size: 0.875rem; + line-height: 1.25rem; + font-weight: 400; + background-color: white; + color: oklch(14.5% 0 0deg); + outline: none; + + @media (any-pointer: coarse) { + font-size: 1rem; + line-height: 1.5rem; + } + + @media (prefers-color-scheme: dark) { + border: 1px solid white; + background-color: oklch(14.5% 0 0deg); + color: white; + } + + &::placeholder { + color: oklch(55.6% 0 0deg); + + @media (prefers-color-scheme: dark) { + color: oklch(70.8% 0 0deg); + } + } + + &:focus { + outline: 2px solid oklch(14.5% 0 0deg); + outline-offset: -1px; + + @media (prefers-color-scheme: dark) { + outline-color: white; + } + } +} + +.Label { + display: flex; + flex-direction: column; + gap: 0.25rem; + font-size: 0.875rem; + line-height: 1.25rem; + font-weight: 700; + color: oklch(14.5% 0 0deg); + + @media (prefers-color-scheme: dark) { + color: white; + } +} + +.Positioner { + outline: 0; +} + +.Popup { + box-sizing: border-box; + background-color: white; + color: oklch(14.5% 0 0deg); + width: var(--anchor-width); + max-width: var(--available-width); + border: 1px solid oklch(14.5% 0 0deg); + box-shadow: 0.25rem 0.25rem 0 rgb(0 0 0 / 12%); + + @media (prefers-color-scheme: dark) { + background-color: oklch(14.5% 0 0deg); + color: white; + border: 1px solid white; + box-shadow: none; + } +} + +.List { + box-sizing: border-box; + overflow-y: auto; + overscroll-behavior: contain; + padding-block: 0.25rem; + scroll-padding-block: 0.25rem; + outline: 0; + max-height: min(22.5rem, var(--available-height)); + + &[data-empty] { + padding: 0; + } +} + +.Item { + box-sizing: border-box; + outline: 0; + cursor: default; + -webkit-user-select: none; + user-select: none; + padding-block: 0.5rem; + padding-left: 0.5rem; + padding-right: 0.5rem; + display: flex; + font-size: 0.875rem; + line-height: 1rem; + + &[data-highlighted] { + z-index: 0; + position: relative; + color: white; + + @media (prefers-color-scheme: dark) { + color: oklch(14.5% 0 0deg); + } + } + + &[data-highlighted]::before { + content: ''; + z-index: -1; + position: absolute; + inset-block: 0; + inset-inline: 0; + background-color: oklch(14.5% 0 0deg); + + @media (prefers-color-scheme: dark) { + background-color: white; + } + } +} + +.Separator { + margin: 0.375rem 1rem; + height: 1px; + background-color: oklch(97% 0 0deg); + + @media (prefers-color-scheme: dark) { + background-color: oklch(26.9% 0 0deg); + } +} + +.Empty { + box-sizing: border-box; + padding: 1rem 1rem 1rem 0.5rem; + font-size: 0.875rem; + line-height: 1rem; + color: oklch(55.6% 0 0deg); + + @media (prefers-color-scheme: dark) { + color: oklch(70.8% 0 0deg); + } +} + +.Button { + box-sizing: border-box; + border: 1px solid oklch(14.5% 0 0deg); + border-radius: 0; + background: white; + color: oklch(14.5% 0 0deg); + font: inherit; + font-size: 0.875rem; + line-height: 1.25rem; + font-weight: 700; + padding: 0.25rem 0.5rem; + + @media (prefers-color-scheme: dark) { + border-color: white; + background: oklch(14.5% 0 0deg); + color: white; + } +} + +.Status { + margin: 0; + font-size: 0.875rem; + line-height: 1.25rem; + color: oklch(14.5% 0 0deg); + + @media (prefers-color-scheme: dark) { + color: white; + } +} diff --git a/docs/pages/experiments/base-ui-autocomplete-enter.tsx b/docs/pages/experiments/base-ui-autocomplete-enter.tsx new file mode 100644 index 00000000000000..c9292625febaed --- /dev/null +++ b/docs/pages/experiments/base-ui-autocomplete-enter.tsx @@ -0,0 +1,68 @@ +'use client'; +import * as React from 'react'; +import { Autocomplete } from '@base-ui/react/autocomplete'; +import styles from './base-ui-autocomplete-enter.module.css'; + +const cities = ['New York', 'New Orleans', 'New Delhi', 'London', 'Paris']; + +export default function BaseUIAutocompleteEnter() { + const inputId = React.useId(); + const [value, setValue] = React.useState(''); + const [submittedValue, setSubmittedValue] = React.useState('-'); + const [submitCount, setSubmitCount] = React.useState(0); + + return ( +
{ + event.preventDefault(); + const formData = new FormData(event.currentTarget); + + setSubmittedValue((formData.get('city') as string) || '-'); + setSubmitCount((count) => count + 1); + }} + > + + + + + + + +
No cities found.
+
+ + {(city: string) => ( + + {city} + + )} + +
+
+
+
+ + +

Value: {value || '-'}

+

Submitted: {submittedValue}

+

Submits: {submitCount}

+
+ ); +} diff --git a/packages/mui-material/src/Autocomplete/Autocomplete.test.js b/packages/mui-material/src/Autocomplete/Autocomplete.test.js index c64005ad1c64da..d8816ce90a1df5 100644 --- a/packages/mui-material/src/Autocomplete/Autocomplete.test.js +++ b/packages/mui-material/src/Autocomplete/Autocomplete.test.js @@ -3374,6 +3374,40 @@ describe('', () => { expect(handleChange.args[0][1]).to.equal('The'); }); + it('should prevent form submission when committing typed text over auto-highlighted match', async () => { + const handleChange = spy(); + const handleSubmit = spy(); + const options = ['The Shawshank Redemption', 'The Godfather']; + const { user } = render( +
{ + if (!event.defaultPrevented && event.key === 'Enter') { + handleSubmit(); + } + }} + > + } + /> +
, + ); + + await user.type(screen.getByRole('combobox'), 'The{Enter}'); + + expect(handleChange.callCount).to.equal(1); + expect(handleChange.args[0][1]).to.equal('The'); + expect(handleSubmit.callCount).to.equal(0); + + await user.keyboard('{Enter}'); + + expect(handleSubmit.callCount).to.equal(1); + }); + it('should prefer typed text after editing a selected value', async () => { const handleChange = spy(); const options = ['The Shawshank Redemption', 'The Godfather']; diff --git a/packages/mui-material/src/useAutocomplete/useAutocomplete.js b/packages/mui-material/src/useAutocomplete/useAutocomplete.js index c2251a52b1fbc4..e1af3eb4f8bde0 100644 --- a/packages/mui-material/src/useAutocomplete/useAutocomplete.js +++ b/packages/mui-material/src/useAutocomplete/useAutocomplete.js @@ -1024,6 +1024,11 @@ function useAutocomplete(props) { // deliberate user interactions like hovering a suggestion then pressing Enter. const shouldSelectHighlighted = !freeSolo || inputPristine || highlightReasonRef.current !== null; + const shouldPreventSubmitAfterFreeSoloCommit = + popupOpen && + highlightedIndexRef.current !== -1 && + !shouldSelectHighlighted && + !touchScrolledRef.current; if ( highlightedIndexRef.current !== -1 && @@ -1053,8 +1058,8 @@ function useAutocomplete(props) { ); } } else if (freeSolo && inputValue !== '' && inputValueIsSelectedValue === false) { - if (multiple) { - // Allow people to add new values before they submit the form. + if (multiple || shouldPreventSubmitAfterFreeSoloCommit) { + // Allow people to commit the freeSolo value before they submit the form. event.preventDefault(); } selectNewValue(event, inputValue, 'createOption', 'freeSolo'); From 4a6ddbf891d6e93d8166f08e63755462396f3c9b Mon Sep 17 00:00:00 2001 From: Albert Yu Date: Thu, 18 Jun 2026 19:44:57 +0800 Subject: [PATCH 2/3] cleanup --- .../autocomplete-free-solo-enter.tsx | 89 -------- .../base-ui-autocomplete-enter.module.css | 191 ------------------ .../base-ui-autocomplete-enter.tsx | 68 ------- 3 files changed, 348 deletions(-) delete mode 100644 docs/pages/experiments/autocomplete-free-solo-enter.tsx delete mode 100644 docs/pages/experiments/base-ui-autocomplete-enter.module.css delete mode 100644 docs/pages/experiments/base-ui-autocomplete-enter.tsx diff --git a/docs/pages/experiments/autocomplete-free-solo-enter.tsx b/docs/pages/experiments/autocomplete-free-solo-enter.tsx deleted file mode 100644 index 387ca6f2e3d488..00000000000000 --- a/docs/pages/experiments/autocomplete-free-solo-enter.tsx +++ /dev/null @@ -1,89 +0,0 @@ -'use client'; -import * as React from 'react'; -import Autocomplete, { createFilterOptions } from '@mui/material/Autocomplete'; -import Box from '@mui/material/Box'; -import Button from '@mui/material/Button'; -import Stack from '@mui/material/Stack'; -import TextField from '@mui/material/TextField'; -import Typography from '@mui/material/Typography'; - -const options = ['New York', 'New Orleans', 'New Delhi', 'London', 'Paris']; -const filterOptions = createFilterOptions(); -const getOptionLabel = (option: string) => option; - -export default function AutocompleteFreeSoloEnter() { - const [open, setOpen] = React.useState(false); - const [value, setValue] = React.useState(null); - const [inputValue, setInputValue] = React.useState(''); - const [highlightedOption, setHighlightedOption] = React.useState(null); - const [submitCount, setSubmitCount] = React.useState(0); - - const handleKeyDown = ( - event: React.KeyboardEvent & { defaultMuiPrevented?: boolean }, - ) => { - if (event.key !== 'Enter') { - return; - } - - const textbox = event.currentTarget.querySelector('[role="combobox"]'); - const activeDescendantId = textbox?.getAttribute('aria-activedescendant'); - const activeOption = activeDescendantId ? document.getElementById(activeDescendantId) : null; - const activeOptionIndex = Number(activeOption?.getAttribute('data-option-index')); - const filteredOptions = filterOptions(options, { inputValue, getOptionLabel }); - const activeDescendantOption = Number.isNaN(activeOptionIndex) - ? null - : filteredOptions[activeOptionIndex]; - const optionToCommit = activeDescendantOption ?? highlightedOption; - - if (!optionToCommit) { - return; - } - - event.defaultMuiPrevented = true; - event.preventDefault(); - setValue(optionToCommit); - setInputValue(optionToCommit); - setOpen(false); - }; - - return ( - { - event.preventDefault(); - setSubmitCount((count) => count + 1); - }} - sx={{ p: 4, width: 360 }} - > - - - autoHighlight - freeSolo - open={open} - onOpen={() => setOpen(true)} - onClose={() => setOpen(false)} - value={value} - inputValue={inputValue} - onChange={(_event, newValue) => { - setValue(newValue); - }} - onHighlightChange={(_event, option) => { - setHighlightedOption(option); - }} - onInputChange={(_event, newInputValue) => { - setInputValue(newInputValue); - setHighlightedOption(null); - }} - onKeyDown={handleKeyDown} - options={options} - renderInput={(params) => } - /> - - Value: {value ?? '-'} - Submits: {submitCount} - - - ); -} diff --git a/docs/pages/experiments/base-ui-autocomplete-enter.module.css b/docs/pages/experiments/base-ui-autocomplete-enter.module.css deleted file mode 100644 index e980a4f3d92bf4..00000000000000 --- a/docs/pages/experiments/base-ui-autocomplete-enter.module.css +++ /dev/null @@ -1,191 +0,0 @@ -.Form { - padding: 2rem; - display: flex; - flex-direction: column; - gap: 0.75rem; - align-items: flex-start; - font-family: system-ui, sans-serif; -} - -.Input { - box-sizing: border-box; - padding: 0 0.5rem; - margin: 0; - border-radius: 0; - border: 1px solid oklch(14.5% 0 0deg); - width: 16rem; - height: 2rem; - font-family: inherit; - font-size: 0.875rem; - line-height: 1.25rem; - font-weight: 400; - background-color: white; - color: oklch(14.5% 0 0deg); - outline: none; - - @media (any-pointer: coarse) { - font-size: 1rem; - line-height: 1.5rem; - } - - @media (prefers-color-scheme: dark) { - border: 1px solid white; - background-color: oklch(14.5% 0 0deg); - color: white; - } - - &::placeholder { - color: oklch(55.6% 0 0deg); - - @media (prefers-color-scheme: dark) { - color: oklch(70.8% 0 0deg); - } - } - - &:focus { - outline: 2px solid oklch(14.5% 0 0deg); - outline-offset: -1px; - - @media (prefers-color-scheme: dark) { - outline-color: white; - } - } -} - -.Label { - display: flex; - flex-direction: column; - gap: 0.25rem; - font-size: 0.875rem; - line-height: 1.25rem; - font-weight: 700; - color: oklch(14.5% 0 0deg); - - @media (prefers-color-scheme: dark) { - color: white; - } -} - -.Positioner { - outline: 0; -} - -.Popup { - box-sizing: border-box; - background-color: white; - color: oklch(14.5% 0 0deg); - width: var(--anchor-width); - max-width: var(--available-width); - border: 1px solid oklch(14.5% 0 0deg); - box-shadow: 0.25rem 0.25rem 0 rgb(0 0 0 / 12%); - - @media (prefers-color-scheme: dark) { - background-color: oklch(14.5% 0 0deg); - color: white; - border: 1px solid white; - box-shadow: none; - } -} - -.List { - box-sizing: border-box; - overflow-y: auto; - overscroll-behavior: contain; - padding-block: 0.25rem; - scroll-padding-block: 0.25rem; - outline: 0; - max-height: min(22.5rem, var(--available-height)); - - &[data-empty] { - padding: 0; - } -} - -.Item { - box-sizing: border-box; - outline: 0; - cursor: default; - -webkit-user-select: none; - user-select: none; - padding-block: 0.5rem; - padding-left: 0.5rem; - padding-right: 0.5rem; - display: flex; - font-size: 0.875rem; - line-height: 1rem; - - &[data-highlighted] { - z-index: 0; - position: relative; - color: white; - - @media (prefers-color-scheme: dark) { - color: oklch(14.5% 0 0deg); - } - } - - &[data-highlighted]::before { - content: ''; - z-index: -1; - position: absolute; - inset-block: 0; - inset-inline: 0; - background-color: oklch(14.5% 0 0deg); - - @media (prefers-color-scheme: dark) { - background-color: white; - } - } -} - -.Separator { - margin: 0.375rem 1rem; - height: 1px; - background-color: oklch(97% 0 0deg); - - @media (prefers-color-scheme: dark) { - background-color: oklch(26.9% 0 0deg); - } -} - -.Empty { - box-sizing: border-box; - padding: 1rem 1rem 1rem 0.5rem; - font-size: 0.875rem; - line-height: 1rem; - color: oklch(55.6% 0 0deg); - - @media (prefers-color-scheme: dark) { - color: oklch(70.8% 0 0deg); - } -} - -.Button { - box-sizing: border-box; - border: 1px solid oklch(14.5% 0 0deg); - border-radius: 0; - background: white; - color: oklch(14.5% 0 0deg); - font: inherit; - font-size: 0.875rem; - line-height: 1.25rem; - font-weight: 700; - padding: 0.25rem 0.5rem; - - @media (prefers-color-scheme: dark) { - border-color: white; - background: oklch(14.5% 0 0deg); - color: white; - } -} - -.Status { - margin: 0; - font-size: 0.875rem; - line-height: 1.25rem; - color: oklch(14.5% 0 0deg); - - @media (prefers-color-scheme: dark) { - color: white; - } -} diff --git a/docs/pages/experiments/base-ui-autocomplete-enter.tsx b/docs/pages/experiments/base-ui-autocomplete-enter.tsx deleted file mode 100644 index c9292625febaed..00000000000000 --- a/docs/pages/experiments/base-ui-autocomplete-enter.tsx +++ /dev/null @@ -1,68 +0,0 @@ -'use client'; -import * as React from 'react'; -import { Autocomplete } from '@base-ui/react/autocomplete'; -import styles from './base-ui-autocomplete-enter.module.css'; - -const cities = ['New York', 'New Orleans', 'New Delhi', 'London', 'Paris']; - -export default function BaseUIAutocompleteEnter() { - const inputId = React.useId(); - const [value, setValue] = React.useState(''); - const [submittedValue, setSubmittedValue] = React.useState('-'); - const [submitCount, setSubmitCount] = React.useState(0); - - return ( -
{ - event.preventDefault(); - const formData = new FormData(event.currentTarget); - - setSubmittedValue((formData.get('city') as string) || '-'); - setSubmitCount((count) => count + 1); - }} - > - - - - - - - -
No cities found.
-
- - {(city: string) => ( - - {city} - - )} - -
-
-
-
- - -

Value: {value || '-'}

-

Submitted: {submittedValue}

-

Submits: {submitCount}

-
- ); -} From 7218c263b7f0cb4579bd98a50804edecc5f8caf8 Mon Sep 17 00:00:00 2001 From: Albert Yu Date: Thu, 18 Jun 2026 23:01:35 +0800 Subject: [PATCH 3/3] polish --- .../src/Autocomplete/Autocomplete.test.js | 33 +++++++++++++++++++ .../src/useAutocomplete/useAutocomplete.js | 22 ++++++------- 2 files changed, 44 insertions(+), 11 deletions(-) diff --git a/packages/mui-material/src/Autocomplete/Autocomplete.test.js b/packages/mui-material/src/Autocomplete/Autocomplete.test.js index d8816ce90a1df5..86b7795bdb7ab5 100644 --- a/packages/mui-material/src/Autocomplete/Autocomplete.test.js +++ b/packages/mui-material/src/Autocomplete/Autocomplete.test.js @@ -3408,6 +3408,39 @@ describe('', () => { expect(handleSubmit.callCount).to.equal(1); }); + it('should prevent form submission when committing edited selected text over value-highlighted match', async () => { + const handleChange = spy(); + const handleSubmit = spy(); + const options = ['The Shawshank Redemption', 'The Godfather']; + const { user } = render( +
{ + if (!event.defaultPrevented && event.key === 'Enter') { + handleSubmit(); + } + }} + > + } + /> +
, + ); + + await user.keyboard('{Backspace}{Backspace}{Backspace}{Backspace}{Backspace}{Enter}'); + + expect(handleChange.callCount).to.equal(1); + expect(handleChange.args[0][1]).to.equal('The Godf'); + expect(handleSubmit.callCount).to.equal(0); + + await user.keyboard('{Enter}'); + + expect(handleSubmit.callCount).to.equal(1); + }); + it('should prefer typed text after editing a selected value', async () => { const handleChange = spy(); const options = ['The Shawshank Redemption', 'The Godfather']; diff --git a/packages/mui-material/src/useAutocomplete/useAutocomplete.js b/packages/mui-material/src/useAutocomplete/useAutocomplete.js index e1af3eb4f8bde0..8b524bbc1192bc 100644 --- a/packages/mui-material/src/useAutocomplete/useAutocomplete.js +++ b/packages/mui-material/src/useAutocomplete/useAutocomplete.js @@ -1016,19 +1016,19 @@ function useAutocomplete(props) { } break; case 'Enter': { - // In freeSolo, only select the highlighted option if the user hasn't - // typed new text (inputPristine) or explicitly interacted with an option - // (keyboard, mouse, or touch — any non-null reason). This lets typed - // text win over a programmatic highlight (reason=null, e.g. from - // syncHighlightedIndex matching a previous value) while still honoring - // deliberate user interactions like hovering a suggestion then pressing Enter. - const shouldSelectHighlighted = - !freeSolo || inputPristine || highlightReasonRef.current !== null; - const shouldPreventSubmitAfterFreeSoloCommit = + // In freeSolo, once the user has typed new text, Enter should commit that text instead + // of treating a programmatic highlight as an intentional selection. Programmatic + // highlights include autoHighlight and syncHighlightedIndex matching a previous value. + const hasProgrammaticHighlight = popupOpen && highlightedIndexRef.current !== -1 && - !shouldSelectHighlighted && - !touchScrolledRef.current; + highlightReasonRef.current === null; + const shouldCommitFreeSoloOverProgrammaticHighlight = + freeSolo && !inputPristine && hasProgrammaticHighlight; + const shouldSelectHighlighted = + !freeSolo || inputPristine || !hasProgrammaticHighlight; + const shouldPreventSubmitAfterFreeSoloCommit = + shouldCommitFreeSoloOverProgrammaticHighlight && !touchScrolledRef.current; if ( highlightedIndexRef.current !== -1 &&