diff --git a/docs/data/material/components/autocomplete/GitHubLabel.js b/docs/data/material/components/autocomplete/GitHubLabel.js index 22396f95ee43b1..404687f7112a04 100644 --- a/docs/data/material/components/autocomplete/GitHubLabel.js +++ b/docs/data/material/components/autocomplete/GitHubLabel.js @@ -192,7 +192,6 @@ export default function GitHubLabel() { setPendingValue(newValue); }} disableCloseOnSelect - PopperComponent={PopperComponent} renderTags={() => null} noOptionsText="No labels" renderOption={(props, option, { selected }) => ( @@ -255,6 +254,9 @@ export default function GitHubLabel() { placeholder="Filter labels" /> )} + slots={{ + popper: PopperComponent, + }} /> diff --git a/docs/data/material/components/autocomplete/GitHubLabel.tsx b/docs/data/material/components/autocomplete/GitHubLabel.tsx index 1b93212d7a0cb8..ec4c222d4abc71 100644 --- a/docs/data/material/components/autocomplete/GitHubLabel.tsx +++ b/docs/data/material/components/autocomplete/GitHubLabel.tsx @@ -198,7 +198,6 @@ export default function GitHubLabel() { setPendingValue(newValue); }} disableCloseOnSelect - PopperComponent={PopperComponent} renderTags={() => null} noOptionsText="No labels" renderOption={(props, option, { selected }) => ( @@ -261,6 +260,9 @@ export default function GitHubLabel() { placeholder="Filter labels" /> )} + slots={{ + popper: PopperComponent, + }} /> diff --git a/docs/data/material/components/autocomplete/Virtualize.js b/docs/data/material/components/autocomplete/Virtualize.js index 7b62b6c4f7b938..e5c812f9e366cc 100644 --- a/docs/data/material/components/autocomplete/Virtualize.js +++ b/docs/data/material/components/autocomplete/Virtualize.js @@ -141,13 +141,15 @@ export default function Virtualize() { id="virtualize-demo" sx={{ width: 300 }} disableListWrap - PopperComponent={StyledPopper} - ListboxComponent={ListboxComponent} options={OPTIONS} groupBy={(option) => option[0].toUpperCase()} renderInput={(params) => } renderOption={(props, option, state) => [props, option, state.index]} renderGroup={(params) => params} + slots={{ + popper: StyledPopper, + listbox: ListboxComponent, + }} /> ); } diff --git a/docs/data/material/components/autocomplete/Virtualize.tsx b/docs/data/material/components/autocomplete/Virtualize.tsx index cd6eee98854ff5..3e61456f3f6339 100644 --- a/docs/data/material/components/autocomplete/Virtualize.tsx +++ b/docs/data/material/components/autocomplete/Virtualize.tsx @@ -141,8 +141,6 @@ export default function Virtualize() { id="virtualize-demo" sx={{ width: 300 }} disableListWrap - PopperComponent={StyledPopper} - ListboxComponent={ListboxComponent} options={OPTIONS} groupBy={(option) => option[0].toUpperCase()} renderInput={(params) => } @@ -150,6 +148,10 @@ export default function Virtualize() { [props, option, state.index] as React.ReactNode } renderGroup={(params) => params as any} + slots={{ + popper: StyledPopper, + listbox: ListboxComponent, + }} /> ); } diff --git a/docs/data/material/components/autocomplete/Virtualize.tsx.preview b/docs/data/material/components/autocomplete/Virtualize.tsx.preview index 7aa80fd1fd266e..d243091ed77894 100644 --- a/docs/data/material/components/autocomplete/Virtualize.tsx.preview +++ b/docs/data/material/components/autocomplete/Virtualize.tsx.preview @@ -2,8 +2,6 @@ id="virtualize-demo" sx={{ width: 300 }} disableListWrap - PopperComponent={StyledPopper} - ListboxComponent={ListboxComponent} options={OPTIONS} groupBy={(option) => option[0].toUpperCase()} renderInput={(params) => } @@ -11,4 +9,8 @@ [props, option, state.index] as React.ReactNode } renderGroup={(params) => params as any} + slots={{ + popper: StyledPopper, + listbox: ListboxComponent, + }} /> \ No newline at end of file diff --git a/docs/data/material/migration/migrating-from-deprecated-apis/migrating-from-deprecated-apis.md b/docs/data/material/migration/migrating-from-deprecated-apis/migrating-from-deprecated-apis.md index 43ac60f957e2e2..af17a0b9f66f9a 100644 --- a/docs/data/material/migration/migrating-from-deprecated-apis/migrating-from-deprecated-apis.md +++ b/docs/data/material/migration/migrating-from-deprecated-apis/migrating-from-deprecated-apis.md @@ -227,6 +227,46 @@ Here's how to migrate: }, ``` +## Autocomplete + +Use the [codemod](https://github.com/mui/material-ui/tree/HEAD/packages/mui-codemod#autocomplete-props) below to migrate the code as described in the following sections: + +```bash +npx @mui/codemod@next deprecations/autocomplete-props +``` + +### \*Component props + +All of the Autocomplete's slot (`*Component`) props were deprecated in favor of equivalent `slots` entries: + +```diff + +``` + +### \*Props props + +All of the Autocomplete's slot props (`*Props`) props were deprecated in favor of equivalent `slotProps` entries: + +```diff + +``` + ## Avatar Use the [codemod](https://github.com/mui/material-ui/tree/HEAD/packages/mui-codemod#avatar-props) below to migrate the code as described in the following sections: diff --git a/docs/pages/material-ui/api/autocomplete.json b/docs/pages/material-ui/api/autocomplete.json index e0e4d60b0a7e5a..fcb754234fd1ba 100644 --- a/docs/pages/material-ui/api/autocomplete.json +++ b/docs/pages/material-ui/api/autocomplete.json @@ -16,7 +16,11 @@ }, "default": "false" }, - "ChipProps": { "type": { "name": "object" } }, + "ChipProps": { + "type": { "name": "object" }, + "deprecated": true, + "deprecationInfo": "Use slotProps.chip instead. This prop will be removed in v7. See Migrating from deprecated APIs for more details." + }, "classes": { "type": { "name": "object" }, "additionalInfo": { "cssApi": true } }, "clearIcon": { "type": { "name": "node" }, "default": "" }, "clearOnBlur": { "type": { "name": "bool" }, "default": "!props.freeSolo" }, @@ -92,8 +96,17 @@ } }, "limitTags": { "type": { "name": "custom", "description": "integer" }, "default": "-1" }, - "ListboxComponent": { "type": { "name": "elementType" }, "default": "'ul'" }, - "ListboxProps": { "type": { "name": "object" } }, + "ListboxComponent": { + "type": { "name": "elementType" }, + "default": "'ul'", + "deprecated": true, + "deprecationInfo": "Use slots.listbox instead. This prop will be removed in v7. See Migrating from deprecated APIs for more details." + }, + "ListboxProps": { + "type": { "name": "object" }, + "deprecated": true, + "deprecationInfo": "Use slotProps.listbox instead. This prop will be removed in v7. See Migrating from deprecated APIs for more details." + }, "loading": { "type": { "name": "bool" }, "default": "false" }, "loadingText": { "type": { "name": "node" }, "default": "'Loading…'" }, "multiple": { "type": { "name": "bool" }, "default": "false" }, @@ -136,8 +149,18 @@ "open": { "type": { "name": "bool" } }, "openOnFocus": { "type": { "name": "bool" }, "default": "false" }, "openText": { "type": { "name": "string" }, "default": "'Open'" }, - "PaperComponent": { "type": { "name": "elementType" }, "default": "Paper" }, - "PopperComponent": { "type": { "name": "elementType" }, "default": "Popper" }, + "PaperComponent": { + "type": { "name": "elementType" }, + "default": "Paper", + "deprecated": true, + "deprecationInfo": "Use slots.paper instead. This prop will be removed in v7. See Migrating from deprecated APIs for more details." + }, + "PopperComponent": { + "type": { "name": "elementType" }, + "default": "Popper", + "deprecated": true, + "deprecationInfo": "Use slots.popper instead. This prop will be removed in v7. See Migrating from deprecated APIs for more details." + }, "popupIcon": { "type": { "name": "node" }, "default": "" }, "readOnly": { "type": { "name": "bool" }, "default": "false" }, "renderGroup": { @@ -172,7 +195,14 @@ "slotProps": { "type": { "name": "shape", - "description": "{ clearIndicator?: object, paper?: object, popper?: object, popupIndicator?: object }" + "description": "{ chip?: func
| object, clearIndicator?: func
| object, listbox?: func
| object, paper?: func
| object, popper?: func
| object, popupIndicator?: func
| object }" + }, + "default": "{}" + }, + "slots": { + "type": { + "name": "shape", + "description": "{ listbox?: elementType, paper?: elementType, popper?: elementType }" }, "default": "{}" }, @@ -190,6 +220,26 @@ "import Autocomplete from '@mui/material/Autocomplete';", "import { Autocomplete } from '@mui/material';" ], + "slots": [ + { + "name": "listbox", + "description": "The component used to render the listbox.", + "default": "'ul'", + "class": "MuiAutocomplete-listbox" + }, + { + "name": "paper", + "description": "The component used to render the body of the popup.", + "default": "Paper", + "class": "MuiAutocomplete-paper" + }, + { + "name": "popper", + "description": "The component used to position the popup.", + "default": "Popper", + "class": "MuiAutocomplete-popper" + } + ], "classes": [ { "key": "clearIndicator", @@ -269,12 +319,6 @@ "description": "Styles applied to the Input element.", "isGlobal": false }, - { - "key": "listbox", - "className": "MuiAutocomplete-listbox", - "description": "Styles applied to the listbox component.", - "isGlobal": false - }, { "key": "loading", "className": "MuiAutocomplete-loading", @@ -293,18 +337,6 @@ "description": "Styles applied to the option elements.", "isGlobal": false }, - { - "key": "paper", - "className": "MuiAutocomplete-paper", - "description": "Styles applied to the Paper component.", - "isGlobal": false - }, - { - "key": "popper", - "className": "MuiAutocomplete-popper", - "description": "Styles applied to the popper element.", - "isGlobal": false - }, { "key": "popperDisablePortal", "className": "MuiAutocomplete-popperDisablePortal", @@ -351,7 +383,6 @@ "spread": true, "themeDefaultProps": true, "muiName": "MuiAutocomplete", - "forwardsRefTo": "HTMLDivElement", "filename": "/packages/mui-material/src/Autocomplete/Autocomplete.js", "inheritance": null, "demos": "", diff --git a/docs/translations/api-docs/autocomplete/autocomplete.json b/docs/translations/api-docs/autocomplete/autocomplete.json index b257a40f8d3e01..9eaf631c6855cc 100644 --- a/docs/translations/api-docs/autocomplete/autocomplete.json +++ b/docs/translations/api-docs/autocomplete/autocomplete.json @@ -189,6 +189,7 @@ }, "size": { "description": "The size of the component." }, "slotProps": { "description": "The props used for each slot inside." }, + "slots": { "description": "The components used for each slot inside." }, "sx": { "description": "The system prop that allows defining system overrides as well as additional CSS styles." }, @@ -245,10 +246,6 @@ "description": "Styles applied to {{nodeName}}.", "nodeName": "the Input element" }, - "listbox": { - "description": "Styles applied to {{nodeName}}.", - "nodeName": "the listbox component" - }, "loading": { "description": "Styles applied to {{nodeName}}.", "nodeName": "the loading wrapper" @@ -261,14 +258,6 @@ "description": "Styles applied to {{nodeName}}.", "nodeName": "the option elements" }, - "paper": { - "description": "Styles applied to {{nodeName}}.", - "nodeName": "the Paper component" - }, - "popper": { - "description": "Styles applied to {{nodeName}}.", - "nodeName": "the popper element" - }, "popperDisablePortal": { "description": "Styles applied to {{nodeName}} if {{conditions}}.", "nodeName": "the popper element", @@ -299,5 +288,10 @@ "nodeName": "the tag elements", "conditions": "for example the chips if size=\"small\"" } + }, + "slotDescriptions": { + "listbox": "The component used to render the listbox.", + "paper": "The component used to render the body of the popup.", + "popper": "The component used to position the popup." } } diff --git a/packages/mui-codemod/README.md b/packages/mui-codemod/README.md index 604a054a6e8ac0..0f504c865902a1 100644 --- a/packages/mui-codemod/README.md +++ b/packages/mui-codemod/README.md @@ -259,6 +259,52 @@ npx @mui/codemod@next deprecations/alert-classes npx @mui/codemod@next deprecations/alert-props ``` +#### `autocomplete-props` + +```diff + +``` + +```diff + MuiAutocomplete: { + defaultProps: { +- ChipProps: { height: 10 }, +- PaperComponent: CustomPaper, +- PopperComponent: CustomPopper, +- ListboxComponent: CustomListbox, +- ListboxProps: { height: 12 }, ++ slots: { ++ paper: CustomPaper, ++ popper: CustomPopper, ++ listbox: CustomListbox, ++ }, ++ slotProps: { ++ chip: { height: 10 }, ++ listbox: { height: 12 }, ++ }, + }, + }, +``` + +```bash +npx @mui/codemod@next deprecations/autocomplete-props +``` + #### `avatar-props` ```diff diff --git a/packages/mui-codemod/src/deprecations/all/deprecations-all.js b/packages/mui-codemod/src/deprecations/all/deprecations-all.js index 3bddd0595b93e3..5fcc872a52ac17 100644 --- a/packages/mui-codemod/src/deprecations/all/deprecations-all.js +++ b/packages/mui-codemod/src/deprecations/all/deprecations-all.js @@ -1,4 +1,5 @@ import transformAccordionProps from '../accordion-props'; +import transformAutocompleteProps from '../autocomplete-props'; import transformFormControlLabelProps from '../form-control-label-props'; import transformAvatarProps from '../avatar-props'; import transformDividerProps from '../divider-props'; @@ -19,6 +20,7 @@ import transformStepConnectorClasses from '../step-connector-classes'; */ export default function deprecationsAll(file, api, options) { file.source = transformAccordionProps(file, api, options); + file.source = transformAutocompleteProps(file, api, options); file.source = transformFormControlLabelProps(file, api, options); file.source = transformAvatarProps(file, api, options); file.source = transformDividerProps(file, api, options); diff --git a/packages/mui-codemod/src/deprecations/autocomplete-props/autocomplete-props.js b/packages/mui-codemod/src/deprecations/autocomplete-props/autocomplete-props.js new file mode 100644 index 00000000000000..3b5204f0bd642d --- /dev/null +++ b/packages/mui-codemod/src/deprecations/autocomplete-props/autocomplete-props.js @@ -0,0 +1,49 @@ +import movePropIntoSlots from '../utils/movePropIntoSlots'; +import movePropIntoSlotProps from '../utils/movePropIntoSlotProps'; + +/** + * @param {import('jscodeshift').FileInfo} file + * @param {import('jscodeshift').API} api + */ +export default function transformer(file, api, options) { + const j = api.jscodeshift; + const root = j(file.source); + const printOptions = options.printOptions; + + movePropIntoSlots(j, { + root, + componentName: 'Autocomplete', + propName: 'PaperComponent', + slotName: 'paper', + }); + + movePropIntoSlots(j, { + root, + componentName: 'Autocomplete', + propName: 'PopperComponent', + slotName: 'popper', + }); + + movePropIntoSlots(j, { + root, + componentName: 'Autocomplete', + propName: 'ListboxComponent', + slotName: 'listbox', + }); + + movePropIntoSlotProps(j, { + root, + componentName: 'Autocomplete', + propName: 'ListboxProps', + slotName: 'listbox', + }); + + movePropIntoSlotProps(j, { + root, + componentName: 'Autocomplete', + propName: 'ChipProps', + slotName: 'chip', + }); + + return root.toSource(printOptions); +} diff --git a/packages/mui-codemod/src/deprecations/autocomplete-props/autocomplete-props.test.js b/packages/mui-codemod/src/deprecations/autocomplete-props/autocomplete-props.test.js new file mode 100644 index 00000000000000..e5909cad32dab3 --- /dev/null +++ b/packages/mui-codemod/src/deprecations/autocomplete-props/autocomplete-props.test.js @@ -0,0 +1,53 @@ +import path from 'path'; +import { expect } from 'chai'; +import { jscodeshift } from '../../../testUtils'; +import transform from './autocomplete-props'; +import readFile from '../../util/readFile'; + +function read(fileName) { + return readFile(path.join(__dirname, fileName)); +} + +describe('@mui/codemod', () => { + describe('deprecations', () => { + describe('autocomplete-props', () => { + it('transforms props as needed', () => { + const actual = transform({ source: read('./test-cases/actual.js') }, { jscodeshift }, {}); + + const expected = read('./test-cases/expected.js'); + expect(actual).to.equal(expected, 'The transformed version should be correct'); + }); + + it('should be idempotent', () => { + const actual = transform({ source: read('./test-cases/expected.js') }, { jscodeshift }, {}); + + const expected = read('./test-cases/expected.js'); + expect(actual).to.equal(expected, 'The transformed version should be correct'); + }); + }); + + describe('[theme] autocomplete-props', () => { + it('transforms props as needed', () => { + const actual = transform( + { source: read('./test-cases/theme.actual.js') }, + { jscodeshift }, + {}, + ); + + const expected = read('./test-cases/theme.expected.js'); + expect(actual).to.equal(expected, 'The transformed version should be correct'); + }); + + it('should be idempotent', () => { + const actual = transform( + { source: read('./test-cases/theme.expected.js') }, + { jscodeshift }, + {}, + ); + + const expected = read('./test-cases/theme.expected.js'); + expect(actual).to.equal(expected, 'The transformed version should be correct'); + }); + }); + }); +}); diff --git a/packages/mui-codemod/src/deprecations/autocomplete-props/index.js b/packages/mui-codemod/src/deprecations/autocomplete-props/index.js new file mode 100644 index 00000000000000..b2b9a74aa9c035 --- /dev/null +++ b/packages/mui-codemod/src/deprecations/autocomplete-props/index.js @@ -0,0 +1 @@ +export { default } from './autocomplete-props'; diff --git a/packages/mui-codemod/src/deprecations/autocomplete-props/test-cases/actual.js b/packages/mui-codemod/src/deprecations/autocomplete-props/test-cases/actual.js new file mode 100644 index 00000000000000..8003251b598cf3 --- /dev/null +++ b/packages/mui-codemod/src/deprecations/autocomplete-props/test-cases/actual.js @@ -0,0 +1,37 @@ +import Autocomplete from '@mui/material/Autocomplete'; +import {Autocomplete as MyAutocomplete} from '@mui/material'; + +; + +; + +; + + diff --git a/packages/mui-codemod/src/deprecations/autocomplete-props/test-cases/expected.js b/packages/mui-codemod/src/deprecations/autocomplete-props/test-cases/expected.js new file mode 100644 index 00000000000000..f816b74b646c81 --- /dev/null +++ b/packages/mui-codemod/src/deprecations/autocomplete-props/test-cases/expected.js @@ -0,0 +1,44 @@ +import Autocomplete from '@mui/material/Autocomplete'; +import {Autocomplete as MyAutocomplete} from '@mui/material'; + +; + +; + +; + + diff --git a/packages/mui-codemod/src/deprecations/autocomplete-props/test-cases/theme.actual.js b/packages/mui-codemod/src/deprecations/autocomplete-props/test-cases/theme.actual.js new file mode 100644 index 00000000000000..0bc3ca5dc4d4bf --- /dev/null +++ b/packages/mui-codemod/src/deprecations/autocomplete-props/test-cases/theme.actual.js @@ -0,0 +1,26 @@ +fn({ + MuiAutocomplete: { + defaultProps: { + ChipProps: { height: 10 }, + PaperComponent: CustomPaper, + PopperComponent: CustomPopper, + ListboxComponent: CustomListbox, + ListboxProps: { height: 12 }, + }, + }, +}); + +fn({ + MuiAutocomplete: { + defaultProps: { + ChipProps: { height: 10 }, + PaperComponent: CustomPaper, + PopperComponent: CustomPopper, + ListboxComponent: CustomListbox, + ListboxProps: { height: 12 }, + slotProps: { + popupIndicator: { width: 20 } + }, + }, + }, +}); diff --git a/packages/mui-codemod/src/deprecations/autocomplete-props/test-cases/theme.expected.js b/packages/mui-codemod/src/deprecations/autocomplete-props/test-cases/theme.expected.js new file mode 100644 index 00000000000000..bc7b33f0055781 --- /dev/null +++ b/packages/mui-codemod/src/deprecations/autocomplete-props/test-cases/theme.expected.js @@ -0,0 +1,34 @@ +fn({ + MuiAutocomplete: { + defaultProps: { + slots: { + paper: CustomPaper, + popper: CustomPopper, + listbox: CustomListbox + }, + + slotProps: { + listbox: { height: 12 }, + chip: { height: 10 } + } + }, + }, +}); + +fn({ + MuiAutocomplete: { + defaultProps: { + slotProps: { + popupIndicator: { width: 20 }, + listbox: { height: 12 }, + chip: { height: 10 } + }, + + slots: { + paper: CustomPaper, + popper: CustomPopper, + listbox: CustomListbox + } + }, + }, +}); diff --git a/packages/mui-material/src/Autocomplete/Autocomplete.d.ts b/packages/mui-material/src/Autocomplete/Autocomplete.d.ts index bc93447fab6e13..70772d58b3756e 100644 --- a/packages/mui-material/src/Autocomplete/Autocomplete.d.ts +++ b/packages/mui-material/src/Autocomplete/Autocomplete.d.ts @@ -17,6 +17,7 @@ import { ChipProps, ChipTypeMap } from '@mui/material/Chip'; import { PaperProps } from '@mui/material/Paper'; import { PopperProps } from '@mui/material/Popper'; import { AutocompleteClasses } from './autocompleteClasses'; +import { CreateSlotsAndSlotProps, SlotProps } from '../utils/types'; export { AutocompleteChangeDetails, @@ -84,6 +85,75 @@ export interface AutocompleteRenderInputParams { export interface AutocompletePropsSizeOverrides {} +export interface AutocompleteSlots { + /** + * The component used to render the listbox. + * @default 'ul' + */ + listbox?: React.JSXElementConstructor>; + /** + * The component used to render the body of the popup. + * @default Paper + */ + paper?: React.JSXElementConstructor; + /** + * The component used to position the popup. + * @default Popper + */ + popper?: React.JSXElementConstructor; +} + +export type AutocompleteSlotsAndSlotProps< + Value, + Multiple extends boolean | undefined, + DisableClearable extends boolean | undefined, + FreeSolo extends boolean | undefined, + ChipComponent extends React.ElementType = ChipTypeMap['defaultComponent'], +> = CreateSlotsAndSlotProps< + AutocompleteSlots, + { + chip: SlotProps< + React.ElementType>>, + {}, + AutocompleteOwnerState + >; + clearIndicator: SlotProps< + React.ElementType>, + {}, + AutocompleteOwnerState + >; + /** + * Props applied to the Listbox element. + */ + listbox: SlotProps< + React.ElementType< + ReturnType['getListboxProps']> & { + sx?: SxProps; + ref?: React.Ref; + } + >, + {}, + AutocompleteOwnerState + >; + + paper: SlotProps< + React.ElementType>, + {}, + AutocompleteOwnerState + >; + popper: SlotProps< + React.ElementType>, + {}, + AutocompleteOwnerState + >; + popupIndicator: SlotProps< + React.ElementType>, + {}, + AutocompleteOwnerState + >; + } +>; + export interface AutocompleteProps< Value, Multiple extends boolean | undefined, @@ -91,9 +161,11 @@ export interface AutocompleteProps< FreeSolo extends boolean | undefined, ChipComponent extends React.ElementType = ChipTypeMap['defaultComponent'], > extends UseAutocompleteProps, - StandardProps, 'defaultValue' | 'onChange' | 'children'> { + StandardProps, 'defaultValue' | 'onChange' | 'children'>, + AutocompleteSlotsAndSlotProps { /** * Props applied to the [`Chip`](/material-ui/api/chip/) element. + * @deprecated Use `slotProps.chip` instead. This prop will be removed in v7. See [Migrating from deprecated APIs](/material-ui/migration/migrating-from-deprecated-apis/) for more details. */ ChipProps?: ChipProps; /** @@ -160,10 +232,12 @@ export interface AutocompleteProps< /** * The component used to render the listbox. * @default 'ul' + * @deprecated Use `slots.listbox` instead. This prop will be removed in v7. See [Migrating from deprecated APIs](/material-ui/migration/migrating-from-deprecated-apis/) for more details. */ ListboxComponent?: React.JSXElementConstructor>; /** * Props applied to the Listbox element. + * @deprecated Use `slotProps.listbox` instead. This prop will be removed in v7. See [Migrating from deprecated APIs](/material-ui/migration/migrating-from-deprecated-apis/) for more details. */ ListboxProps?: ReturnType['getListboxProps']> & { sx?: SxProps; @@ -208,11 +282,13 @@ export interface AutocompleteProps< /** * The component used to render the body of the popup. * @default Paper + * @deprecated Use `slots.paper` instead. This prop will be removed in v7. See [Migrating from deprecated APIs](/material-ui/migration/migrating-from-deprecated-apis/) for more details. */ PaperComponent?: React.JSXElementConstructor>; /** * The component used to position the popup. * @default Popper + * @deprecated Use `slots.popper` instead. This prop will be removed in v7. See [Migrating from deprecated APIs](/material-ui/migration/migrating-from-deprecated-apis/) for more details. */ PopperComponent?: React.JSXElementConstructor; /** @@ -272,16 +348,6 @@ export interface AutocompleteProps< * @default 'medium' */ size?: OverridableStringUnion<'small' | 'medium', AutocompletePropsSizeOverrides>; - /** - * The props used for each slot inside. - * @default {} - */ - slotProps?: { - clearIndicator?: Partial; - paper?: PaperProps; - popper?: Partial; - popupIndicator?: Partial; - }; /** * The system prop that allows defining system overrides as well as additional CSS styles. */ diff --git a/packages/mui-material/src/Autocomplete/Autocomplete.js b/packages/mui-material/src/Autocomplete/Autocomplete.js index 2aea5bf347fb6b..f3e1b3e4846606 100644 --- a/packages/mui-material/src/Autocomplete/Autocomplete.js +++ b/packages/mui-material/src/Autocomplete/Autocomplete.js @@ -21,7 +21,7 @@ import ArrowDropDownIcon from '../internal/svg-icons/ArrowDropDown'; import { styled, createUseThemeProps } from '../zero-styled'; import autocompleteClasses, { getAutocompleteUtilityClass } from './autocompleteClasses'; import capitalize from '../utils/capitalize'; -import useForkRef from '../utils/useForkRef'; +import useSlot from '../utils/useSlot'; const useThemeProps = createUseThemeProps('MuiAutocomplete'); @@ -412,7 +412,7 @@ const Autocomplete = React.forwardRef(function Autocomplete(inProps, ref) { autoHighlight = false, autoSelect = false, blurOnSelect = false, - ChipProps, + ChipProps: ChipPropsProp, className, clearIcon = , clearOnBlur = !props.freeSolo, @@ -443,8 +443,8 @@ const Autocomplete = React.forwardRef(function Autocomplete(inProps, ref) { includeInputInList = false, inputValue: inputValueProp, limitTags = -1, - ListboxComponent = 'ul', - ListboxProps, + ListboxComponent: ListboxComponentProp, + ListboxProps: ListboxPropsProp, loading = false, loadingText = 'Loading…', multiple = false, @@ -458,8 +458,8 @@ const Autocomplete = React.forwardRef(function Autocomplete(inProps, ref) { openOnFocus = false, openText = 'Open', options, - PaperComponent = Paper, - PopperComponent = Popper, + PaperComponent: PaperComponentProp, + PopperComponent: PopperComponentProp, popupIcon = , readOnly = false, renderGroup: renderGroupProp, @@ -468,6 +468,7 @@ const Autocomplete = React.forwardRef(function Autocomplete(inProps, ref) { renderTags, selectOnFocus = !props.freeSolo, size = 'medium', + slots = {}, slotProps = {}, value: valueProp, ...other @@ -500,11 +501,8 @@ const Autocomplete = React.forwardRef(function Autocomplete(inProps, ref) { const hasPopupIcon = (!freeSolo || forcePopupIcon === true) && forcePopupIcon !== false; const { onMouseDown: handleInputMouseDown } = getInputProps(); - const { ref: externalListboxRef } = ListboxProps ?? {}; const { ref: listboxRef, ...otherListboxProps } = getListboxProps(); - const combinedListboxRef = useForkRef(listboxRef, externalListboxRef); - const defaultGetOptionLabel = (option) => option.label ?? option; const getOptionLabel = getOptionLabelProp || defaultGetOptionLabel; @@ -525,6 +523,51 @@ const Autocomplete = React.forwardRef(function Autocomplete(inProps, ref) { const classes = useUtilityClasses(ownerState); + const externalForwardedProps = { + slots: { + listbox: ListboxComponentProp, + paper: PaperComponentProp, + popper: PopperComponentProp, + ...slots, + }, + slotProps: { + chip: ChipPropsProp, + listbox: ListboxPropsProp, + ...componentsProps, + ...slotProps, + }, + }; + + const [ListboxSlot, listboxProps] = useSlot('listbox', { + elementType: 'ul', + externalForwardedProps, + ownerState, + className: classes.listbox, + additionalProps: otherListboxProps, + ref: listboxRef, + }); + + const [PaperSlot, paperProps] = useSlot('paper', { + elementType: Paper, + externalForwardedProps, + ownerState, + className: classes.paper, + }); + + const [PopperSlot, popperProps] = useSlot('popper', { + elementType: Popper, + externalForwardedProps, + ownerState, + className: classes.popper, + additionalProps: { + disablePortal, + style: { width: anchorEl ? anchorEl.clientWidth : null }, + role: 'presentation', + anchorEl, + open: popupOpen, + }, + }); + let startAdornment; if (multiple && value.length > 0) { @@ -542,7 +585,7 @@ const Autocomplete = React.forwardRef(function Autocomplete(inProps, ref) { label={getOptionLabel(option)} size={size} {...getCustomizedTagProps({ index })} - {...ChipProps} + {...externalForwardedProps.slotProps.chip} /> )); } @@ -601,29 +644,12 @@ const Autocomplete = React.forwardRef(function Autocomplete(inProps, ref) { ); }; - const clearIndicatorSlotProps = slotProps.clearIndicator ?? componentsProps.clearIndicator; - const paperSlotProps = slotProps.paper ?? componentsProps.paper; - const popperSlotProps = slotProps.popper ?? componentsProps.popper; - const popupIndicatorSlotProps = slotProps.popupIndicator ?? componentsProps.popupIndicator; + const clearIndicatorSlotProps = externalForwardedProps.slotProps.clearIndicator; + const popupIndicatorSlotProps = externalForwardedProps.slotProps.popupIndicator; const renderAutocompletePopperChildren = (children) => ( - - + + {children} @@ -632,14 +658,7 @@ const Autocomplete = React.forwardRef(function Autocomplete(inProps, ref) { let autocompletePopper = null; if (groupedOptions.length > 0) { autocompletePopper = renderAutocompletePopperChildren( - + {groupedOptions.map((option, index) => { if (groupBy) { return renderGroup({ @@ -784,6 +803,7 @@ Autocomplete.propTypes /* remove-proptypes */ = { blurOnSelect: PropTypes.oneOfType([PropTypes.oneOf(['mouse', 'touch']), PropTypes.bool]), /** * Props applied to the [`Chip`](/material-ui/api/chip/) element. + * @deprecated Use `slotProps.chip` instead. This prop will be removed in v7. See [Migrating from deprecated APIs](/material-ui/migration/migrating-from-deprecated-apis/) for more details. */ ChipProps: PropTypes.object, /** @@ -991,10 +1011,12 @@ Autocomplete.propTypes /* remove-proptypes */ = { /** * The component used to render the listbox. * @default 'ul' + * @deprecated Use `slots.listbox` instead. This prop will be removed in v7. See [Migrating from deprecated APIs](/material-ui/migration/migrating-from-deprecated-apis/) for more details. */ ListboxComponent: PropTypes.elementType, /** * Props applied to the Listbox element. + * @deprecated Use `slotProps.listbox` instead. This prop will be removed in v7. See [Migrating from deprecated APIs](/material-ui/migration/migrating-from-deprecated-apis/) for more details. */ ListboxProps: PropTypes.object, /** @@ -1089,11 +1111,13 @@ Autocomplete.propTypes /* remove-proptypes */ = { /** * The component used to render the body of the popup. * @default Paper + * @deprecated Use `slots.paper` instead. This prop will be removed in v7. See [Migrating from deprecated APIs](/material-ui/migration/migrating-from-deprecated-apis/) for more details. */ PaperComponent: PropTypes.elementType, /** * The component used to position the popup. * @default Popper + * @deprecated Use `slots.popper` instead. This prop will be removed in v7. See [Migrating from deprecated APIs](/material-ui/migration/migrating-from-deprecated-apis/) for more details. */ PopperComponent: PropTypes.elementType, /** @@ -1157,11 +1181,22 @@ Autocomplete.propTypes /* remove-proptypes */ = { * The props used for each slot inside. * @default {} */ - slotProps: PropTypes.shape({ - clearIndicator: PropTypes.object, - paper: PropTypes.object, - popper: PropTypes.object, - popupIndicator: PropTypes.object, + slotProps: PropTypes /* @typescript-to-proptypes-ignore */.shape({ + chip: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + clearIndicator: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + listbox: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + paper: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + popper: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + popupIndicator: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + }), + /** + * The components used for each slot inside. + * @default {} + */ + slots: PropTypes.shape({ + listbox: PropTypes.elementType, + paper: PropTypes.elementType, + popper: PropTypes.elementType, }), /** * The system prop that allows defining system overrides as well as additional CSS styles. diff --git a/packages/mui-material/src/Autocomplete/Autocomplete.test.js b/packages/mui-material/src/Autocomplete/Autocomplete.test.js index f6a04adaebb858..26852846f74ddd 100644 --- a/packages/mui-material/src/Autocomplete/Autocomplete.test.js +++ b/packages/mui-material/src/Autocomplete/Autocomplete.test.js @@ -59,20 +59,50 @@ describe('', () => { testStateOverrides: { prop: 'fullWidth', value: true, styleKey: 'fullWidth' }, refInstanceof: window.HTMLDivElement, testComponentPropWith: 'div', - testLegacyComponentsProp: true, slots: { - clearIndicator: { expectedClassName: classes.clearIndicator }, + listbox: { expectedClassName: classes.listbox }, paper: { expectedClassName: classes.paper }, - popper: { expectedClassName: classes.popper }, + popper: { expectedClassName: classes.popper, testWithElement: null }, + }, + skip: ['componentProp', 'componentsProp', 'reactTestRenderer'], + }), + ); + + describeConformance( + } + />, + () => ({ + classes, + render, + muiName: 'MuiAutocomplete', + slots: { + clearIndicator: { expectedClassName: classes.clearIndicator }, popupIndicator: { expectedClassName: classes.popupIndicator }, }, - skip: [ - 'componentProp', - 'componentsProp', - 'slotsProp', - 'reactTestRenderer', - 'slotPropsCallback', // not supported yet - ], + only: ['slotPropsProp'], + }), + ); + + describeConformance( + } + />, + () => ({ + classes, + render, + muiName: 'MuiAutocomplete', + slots: { + chip: {}, + }, + only: ['slotPropsProp'], }), );