From cb51569770158459ac4531f70c5ef9cb296a1ede Mon Sep 17 00:00:00 2001 From: Silviu Alexandru Avram Date: Thu, 19 Feb 2026 16:39:14 +0200 Subject: [PATCH 1/6] [useAutocomplete] Improve isOptionEqualToValue value argument type --- .../components/autocomplete/CustomizedHook.js | 2 +- .../components/autocomplete/FreeSolo.js | 17 +++++++++++ .../components/autocomplete/FreeSolo.tsx | 17 +++++++++++ .../migration/upgrade-to-v9/upgrade-to-v9.md | 30 +++++++++++++++++++ docs/pages/material-ui/api/autocomplete.json | 4 +-- .../src/Autocomplete/Autocomplete.js | 4 +-- .../src/useAutocomplete/useAutocomplete.d.ts | 14 ++++++--- 7 files changed, 79 insertions(+), 9 deletions(-) diff --git a/docs/data/material/components/autocomplete/CustomizedHook.js b/docs/data/material/components/autocomplete/CustomizedHook.js index 9f14a28e99a18d..df4234c4d8df98 100644 --- a/docs/data/material/components/autocomplete/CustomizedHook.js +++ b/docs/data/material/components/autocomplete/CustomizedHook.js @@ -221,7 +221,7 @@ CustomAutocomplete.propTypes = { * * If used in free solo mode, it must accept both the type of the options and a string. * - * @param {Value} option + * @param {Value|string} option * @returns {string} * @default (option) => option.label ?? option */ diff --git a/docs/data/material/components/autocomplete/FreeSolo.js b/docs/data/material/components/autocomplete/FreeSolo.js index 2a5c67e0f5f58a..6afea2fee8fcc2 100644 --- a/docs/data/material/components/autocomplete/FreeSolo.js +++ b/docs/data/material/components/autocomplete/FreeSolo.js @@ -29,6 +29,23 @@ export default function FreeSolo() { /> )} /> + } + getOptionLabel={(option) => + typeof option === 'string' ? option : option.title + } + // this demo demonstrates how the value parameter can be either an object (same type as option) or a string + // it could become a string if, for example, you press "Enter" in the input field + isOptionEqualToValue={(option, value) => { + if (typeof value === 'string') { + return option.title === value; + } + return option.title === value?.title; + }} + /> ); } diff --git a/docs/data/material/components/autocomplete/FreeSolo.tsx b/docs/data/material/components/autocomplete/FreeSolo.tsx index 2a5c67e0f5f58a..6afea2fee8fcc2 100644 --- a/docs/data/material/components/autocomplete/FreeSolo.tsx +++ b/docs/data/material/components/autocomplete/FreeSolo.tsx @@ -29,6 +29,23 @@ export default function FreeSolo() { /> )} /> + } + getOptionLabel={(option) => + typeof option === 'string' ? option : option.title + } + // this demo demonstrates how the value parameter can be either an object (same type as option) or a string + // it could become a string if, for example, you press "Enter" in the input field + isOptionEqualToValue={(option, value) => { + if (typeof value === 'string') { + return option.title === value; + } + return option.title === value?.title; + }} + /> ); } diff --git a/docs/data/material/migration/upgrade-to-v9/upgrade-to-v9.md b/docs/data/material/migration/upgrade-to-v9/upgrade-to-v9.md index f81b57a140cd4f..567282be5dd034 100644 --- a/docs/data/material/migration/upgrade-to-v9/upgrade-to-v9.md +++ b/docs/data/material/migration/upgrade-to-v9/upgrade-to-v9.md @@ -64,3 +64,33 @@ in the ButtonBase keyboard handlers. This is actually the expected behavior. #### Listbox toggle on right click The listbox does not toggle anymore when using right click on the input. The left click toggle behavior remains unchanged. + +#### freeSolo type related changes + +When the `freeSolo` prop is passed as `true`, the `getOptionLabel` and `isOptionEqualToValue` props +accept `string` as well for their `option` and, respectively, `value` arguments: + +```diff +- isOptionEqualToValue?: (option: Value, value: Value) => boolean; ++ isOptionEqualToValue?: ( ++ option: Value, ++ value: AutocompleteValueOrFreeSoloValueMapping, ++ ) => boolean; +``` + +```diff +- getOptionLabel?: (option: Value | AutocompleteFreeSoloValueMapping) => string; ++ getOptionLabel?: (option: AutocompleteValueOrFreeSoloValueMapping) => string; +``` + +For reference: + +```ts +type AutocompleteFreeSoloValueMapping = FreeSolo extends true + ? string + : never; + +type AutocompleteValueOrFreeSoloValueMapping = FreeSolo extends true + ? Value | string + : Value; +``` diff --git a/docs/pages/material-ui/api/autocomplete.json b/docs/pages/material-ui/api/autocomplete.json index 2ed4d05fa466ac..da3cbf36390693 100644 --- a/docs/pages/material-ui/api/autocomplete.json +++ b/docs/pages/material-ui/api/autocomplete.json @@ -79,7 +79,7 @@ "getOptionLabel": { "type": { "name": "func" }, "default": "(option) => option.label ?? option", - "signature": { "type": "function(option: Value) => string", "describedArgs": [] } + "signature": { "type": "function(option: Value | string) => string", "describedArgs": [] } }, "groupBy": { "type": { "name": "func" }, @@ -92,7 +92,7 @@ "isOptionEqualToValue": { "type": { "name": "func" }, "signature": { - "type": "function(option: Value, value: Value) => boolean", + "type": "function(option: Value, value: Value | string) => boolean", "describedArgs": ["option", "value"] } }, diff --git a/packages/mui-material/src/Autocomplete/Autocomplete.js b/packages/mui-material/src/Autocomplete/Autocomplete.js index 07ffc0ff2ec46f..8b33987e37ce91 100644 --- a/packages/mui-material/src/Autocomplete/Autocomplete.js +++ b/packages/mui-material/src/Autocomplete/Autocomplete.js @@ -970,7 +970,7 @@ Autocomplete.propTypes /* remove-proptypes */ = { * * If used in free solo mode, it must accept both the type of the options and a string. * - * @param {Value} option + * @param {Value|string} option * @returns {string} * @default (option) => option.label ?? option */ @@ -1009,7 +1009,7 @@ Autocomplete.propTypes /* remove-proptypes */ = { * ⚠️ Both arguments need to be handled, an option can only match with one value. * * @param {Value} option The option to test. - * @param {Value} value The value to test against. + * @param {Value|string} value The value to test against. * @returns {boolean} */ isOptionEqualToValue: PropTypes.func, diff --git a/packages/mui-material/src/useAutocomplete/useAutocomplete.d.ts b/packages/mui-material/src/useAutocomplete/useAutocomplete.d.ts index 8d5adc4a347538..48eb5e2f551cb5 100644 --- a/packages/mui-material/src/useAutocomplete/useAutocomplete.d.ts +++ b/packages/mui-material/src/useAutocomplete/useAutocomplete.d.ts @@ -28,6 +28,10 @@ export function createFilterOptions( export type AutocompleteFreeSoloValueMapping = FreeSolo extends true ? string : never; +export type AutocompleteValueOrFreeSoloValueMapping = FreeSolo extends true + ? Value | string + : Value; + export type AutocompleteValue = Multiple extends true ? Array> : DisableClearable extends true @@ -175,12 +179,12 @@ export interface UseAutocompleteProps< * * If used in free solo mode, it must accept both the type of the options and a string. * - * @param {Value} option + * @param {Value|string} option * @returns {string} * @default (option) => option.label ?? option */ getOptionLabel?: - | ((option: Value | AutocompleteFreeSoloValueMapping) => string) + | ((option: AutocompleteValueOrFreeSoloValueMapping) => string) | undefined; /** * If provided, the options will be grouped under the returned string. @@ -217,10 +221,12 @@ export interface UseAutocompleteProps< * ⚠️ Both arguments need to be handled, an option can only match with one value. * * @param {Value} option The option to test. - * @param {Value} value The value to test against. + * @param {Value|string} value The value to test against. * @returns {boolean} */ - isOptionEqualToValue?: ((option: Value, value: Value) => boolean) | undefined; + isOptionEqualToValue?: + | ((option: Value, value: AutocompleteValueOrFreeSoloValueMapping) => boolean) + | undefined; /** * If `true`, `value` must be an array and the menu will support multiple selections. * @default false From 99fd84a270f1f58ba45d1b85be82f5eac3ee58a5 Mon Sep 17 00:00:00 2001 From: Silviu Alexandru Avram Date: Fri, 20 Feb 2026 11:14:23 +0200 Subject: [PATCH 2/6] change the type in AutocompleteOwnerState --- packages/mui-material/src/Autocomplete/Autocomplete.d.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/mui-material/src/Autocomplete/Autocomplete.d.ts b/packages/mui-material/src/Autocomplete/Autocomplete.d.ts index 6d002fc5bf77f2..8e68d61da48095 100644 --- a/packages/mui-material/src/Autocomplete/Autocomplete.d.ts +++ b/packages/mui-material/src/Autocomplete/Autocomplete.d.ts @@ -16,6 +16,7 @@ import useAutocomplete, { createFilterOptions, UseAutocompleteProps, AutocompleteFreeSoloValueMapping, + AutocompleteValueOrFreeSoloValueMapping, } from '../useAutocomplete'; import { AutocompleteClasses } from './autocompleteClasses'; import { CreateSlotsAndSlotProps, SlotProps } from '../utils/types'; @@ -43,7 +44,7 @@ export type AutocompleteOwnerState< expanded: boolean; focused: boolean; fullWidth: boolean; - getOptionLabel: (option: Value | AutocompleteFreeSoloValueMapping) => string; + getOptionLabel: (option: AutocompleteValueOrFreeSoloValueMapping) => string; hasClearIcon: boolean; hasPopupIcon: boolean; inputFocused: boolean; From 73a8c3dc0f397f659a1e613f5f6b746dc6fc3ae6 Mon Sep 17 00:00:00 2001 From: Silviu Alexandru Avram Date: Fri, 20 Feb 2026 11:14:28 +0200 Subject: [PATCH 3/6] add types tests --- .../src/Autocomplete/Autocomplete.spec.tsx | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/packages/mui-material/src/Autocomplete/Autocomplete.spec.tsx b/packages/mui-material/src/Autocomplete/Autocomplete.spec.tsx index 871fc64a1c5bcc..afaa0061919c15 100644 --- a/packages/mui-material/src/Autocomplete/Autocomplete.spec.tsx +++ b/packages/mui-material/src/Autocomplete/Autocomplete.spec.tsx @@ -7,6 +7,7 @@ import Autocomplete, { } from '@mui/material/Autocomplete'; import TextField from '@mui/material/TextField'; import { ChipTypeMap } from '@mui/material/Chip'; +import { AutocompleteValueOrFreeSoloValueMapping } from '../useAutocomplete'; interface MyAutocompleteProps< T, @@ -182,3 +183,38 @@ function CustomListboxRef() { >(event); }} />; + +// freeSolo prop adds string to the getOptionLabel and isOptionEqualToValue value argument type + null} + freeSolo + getOptionLabel={(option) => { + expectType, typeof option>(option); + + return option; + }} + isOptionEqualToValue={(option, value) => { + expectType, typeof value>(value); + expectType(option); + + return option === value; + }} +/>; + +// getOptionLabel and isOptionEqualToValue value argument type should not include string when freeSolo is false + null} + getOptionLabel={(option) => { + expectType(option); + + return option; + }} + isOptionEqualToValue={(option, value) => { + expectType(value); + expectType(option); + + return option === value; + }} +/>; From 303e209cc6f2c9f3c30ccb5cc50fa2cc930540e4 Mon Sep 17 00:00:00 2001 From: Silviu Alexandru Avram Date: Fri, 20 Feb 2026 14:44:52 +0200 Subject: [PATCH 4/6] improve type tests --- .../src/Autocomplete/Autocomplete.spec.tsx | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/packages/mui-material/src/Autocomplete/Autocomplete.spec.tsx b/packages/mui-material/src/Autocomplete/Autocomplete.spec.tsx index afaa0061919c15..9b03c023e5f64c 100644 --- a/packages/mui-material/src/Autocomplete/Autocomplete.spec.tsx +++ b/packages/mui-material/src/Autocomplete/Autocomplete.spec.tsx @@ -186,35 +186,39 @@ function CustomListboxRef() { // freeSolo prop adds string to the getOptionLabel and isOptionEqualToValue value argument type null} freeSolo getOptionLabel={(option) => { - expectType, typeof option>(option); + expectType, typeof option>( + option, + ); - return option; + return typeof option === 'string' ? option : option.label; }} isOptionEqualToValue={(option, value) => { - expectType, typeof value>(value); - expectType(option); + expectType, typeof value>( + value, + ); + expectType<{ label: string }, typeof option>(option); - return option === value; + return typeof value === 'string' ? option.label === value : option.label === value.label; }} />; // getOptionLabel and isOptionEqualToValue value argument type should not include string when freeSolo is false null} getOptionLabel={(option) => { - expectType(option); + expectType<{ label: string }, typeof option>(option); - return option; + return option.label; }} isOptionEqualToValue={(option, value) => { - expectType(value); - expectType(option); + expectType<{ label: string }, typeof value>(value); + expectType<{ label: string }, typeof option>(option); - return option === value; + return option.label === value.label; }} />; From d8d327fbeeb87c0c62aa50a57920cf244970d9f4 Mon Sep 17 00:00:00 2001 From: Silviu Alexandru Avram Date: Fri, 20 Feb 2026 14:48:11 +0200 Subject: [PATCH 5/6] remove unnecessary nullish check --- docs/data/material/components/autocomplete/FreeSolo.js | 2 +- docs/data/material/components/autocomplete/FreeSolo.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/data/material/components/autocomplete/FreeSolo.js b/docs/data/material/components/autocomplete/FreeSolo.js index 6afea2fee8fcc2..c3af028b265394 100644 --- a/docs/data/material/components/autocomplete/FreeSolo.js +++ b/docs/data/material/components/autocomplete/FreeSolo.js @@ -43,7 +43,7 @@ export default function FreeSolo() { if (typeof value === 'string') { return option.title === value; } - return option.title === value?.title; + return option.title === value.title; }} /> diff --git a/docs/data/material/components/autocomplete/FreeSolo.tsx b/docs/data/material/components/autocomplete/FreeSolo.tsx index 6afea2fee8fcc2..c3af028b265394 100644 --- a/docs/data/material/components/autocomplete/FreeSolo.tsx +++ b/docs/data/material/components/autocomplete/FreeSolo.tsx @@ -43,7 +43,7 @@ export default function FreeSolo() { if (typeof value === 'string') { return option.title === value; } - return option.title === value?.title; + return option.title === value.title; }} /> From a200669b2fb959a5c3275b302d96b45349c7f2bc Mon Sep 17 00:00:00 2001 From: Silviu Alexandru Avram Date: Mon, 23 Feb 2026 10:40:24 +0200 Subject: [PATCH 6/6] improve the examples label --- docs/data/material/components/autocomplete/FreeSolo.js | 4 +++- docs/data/material/components/autocomplete/FreeSolo.tsx | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/data/material/components/autocomplete/FreeSolo.js b/docs/data/material/components/autocomplete/FreeSolo.js index c3af028b265394..90ce14a1d8d095 100644 --- a/docs/data/material/components/autocomplete/FreeSolo.js +++ b/docs/data/material/components/autocomplete/FreeSolo.js @@ -33,7 +33,9 @@ export default function FreeSolo() { id="free-solo-demo3" freeSolo options={top100Films} - renderInput={(params) => } + renderInput={(params) => ( + + )} getOptionLabel={(option) => typeof option === 'string' ? option : option.title } diff --git a/docs/data/material/components/autocomplete/FreeSolo.tsx b/docs/data/material/components/autocomplete/FreeSolo.tsx index c3af028b265394..90ce14a1d8d095 100644 --- a/docs/data/material/components/autocomplete/FreeSolo.tsx +++ b/docs/data/material/components/autocomplete/FreeSolo.tsx @@ -33,7 +33,9 @@ export default function FreeSolo() { id="free-solo-demo3" freeSolo options={top100Films} - renderInput={(params) => } + renderInput={(params) => ( + + )} getOptionLabel={(option) => typeof option === 'string' ? option : option.title }