diff --git a/docs/pages/material-ui/api/native-select.json b/docs/pages/material-ui/api/native-select.json
index 6caa6f0..fa37463 100644
--- a/docs/pages/material-ui/api/native-select.json
+++ b/docs/pages/material-ui/api/native-select.json
@@ -70,7 +70,7 @@
{
"key": "iconOpen",
"className": "MuiNativeSelect-iconOpen",
- "description": "Styles applied to the icon component if the popup is open.",
+ "description": "This class is never applied, a native select doesn't expose whether its popup is open.",
"isGlobal": false
},
{
diff --git a/docs/translations/api-docs/native-select/native-select.json b/docs/translations/api-docs/native-select/native-select.json
index 99344b7..2d6ff6f 100644
--- a/docs/translations/api-docs/native-select/native-select.json
+++ b/docs/translations/api-docs/native-select/native-select.json
@@ -48,9 +48,7 @@
"conditions": "variant=\"filled\""
},
"iconOpen": {
- "description": "Styles applied to {{nodeName}} if {{conditions}}.",
- "nodeName": "the icon component",
- "conditions": "the popup is open"
+ "description": "This class is never applied, a native select doesn't expose whether its popup is open."
},
"iconOutlined": {
"description": "Styles applied to {{nodeName}} if {{conditions}}.",
diff --git a/packages/mui-material/src/NativeSelect/NativeSelectInput.js b/packages/mui-material/src/NativeSelect/NativeSelectInput.js
index c7b420d..d08e235 100644
--- a/packages/mui-material/src/NativeSelect/NativeSelectInput.js
+++ b/packages/mui-material/src/NativeSelect/NativeSelectInput.js
@@ -12,17 +12,17 @@ import rootShouldForwardProp from '../styles/rootShouldForwardProp';
import inputAdornmentClasses from '../InputAdornment/inputAdornmentClasses';
const useUtilityClasses = (ownerState) => {
- const { classes, variant, disabled, multiple, open, error } = ownerState;
+ const { classes, variant, disabled, multiple, error } = ownerState;
const slots = {
select: ['select', variant, disabled && 'disabled', multiple && 'multiple', error && 'error'],
- icon: ['icon', `icon${capitalize(variant)}`, open && 'iconOpen', disabled && 'disabled'],
+ icon: ['icon', `icon${capitalize(variant)}`, disabled && 'disabled'],
};
return composeClasses(slots, getNativeSelectUtilityClasses, classes);
};
-export const StyledSelectSelect = styled('select', {
+const StyledSelectSelect = styled('select', {
name: 'MuiNativeSelect',
})(({ theme }) => ({
// Reset
@@ -130,7 +130,7 @@ const NativeSelectSelect = styled(StyledSelectSelect, {
},
})({});
-export const StyledSelectIcon = styled('svg', {
+const StyledSelectIcon = styled('svg', {
name: 'MuiNativeSelect',
})(({ theme }) => ({
// We use a position absolute over a flexbox in order to forward the pointer events
@@ -146,12 +146,6 @@ export const StyledSelectIcon = styled('svg', {
color: (theme.vars || theme).palette.action.disabled,
},
variants: [
- {
- props: ({ ownerState }) => ownerState.open,
- style: {
- transform: 'rotate(180deg)',
- },
- },
{
props: {
variant: 'filled',
@@ -176,11 +170,7 @@ const NativeSelectIcon = styled(StyledSelectIcon, {
slot: 'Icon',
overridesResolver: (props, styles) => {
const { ownerState } = props;
- return [
- styles.icon,
- ownerState.variant && styles[`icon${capitalize(ownerState.variant)}`],
- ownerState.open && styles.iconOpen,
- ];
+ return [styles.icon, ownerState.variant && styles[`icon${capitalize(ownerState.variant)}`]];
},
})({});
diff --git a/packages/mui-material/src/NativeSelect/nativeSelectClasses.ts b/packages/mui-material/src/NativeSelect/nativeSelectClasses.ts
index 8301846..5b7d3ed 100644
--- a/packages/mui-material/src/NativeSelect/nativeSelectClasses.ts
+++ b/packages/mui-material/src/NativeSelect/nativeSelectClasses.ts
@@ -18,7 +18,7 @@ export interface NativeSelectClasses {
disabled: string;
/** Styles applied to the icon component. */
icon: string;
- /** Styles applied to the icon component if the popup is open. */
+ /** This class is never applied, a native select doesn't expose whether its popup is open. */
iconOpen: string;
/** Styles applied to the icon component if `variant="filled"`. */
iconFilled: string;
diff --git a/packages/mui-material/src/Select/Select.test.js b/packages/mui-material/src/Select/Select.test.js
index de50eb1..1459a6d 100644
--- a/packages/mui-material/src/Select/Select.test.js
+++ b/packages/mui-material/src/Select/Select.test.js
@@ -2774,6 +2774,41 @@ describe('', () => {
});
});
+ it('should not apply MuiNativeSelect theme variants', () => {
+ const theme = createTheme({
+ components: {
+ MuiNativeSelect: {
+ variants: [
+ {
+ props: {
+ variant: 'outlined',
+ },
+ style: {
+ textTransform: 'uppercase',
+ },
+ },
+ ],
+ },
+ },
+ });
+
+ const { container } = render(
+
+
+ ,
+ );
+
+ expect(container.querySelector(`.${classes.select}`)).not.toHaveComputedStyle({
+ textTransform: 'uppercase',
+ });
+ expect(container.querySelector(`.${classes.icon}`)).not.toHaveComputedStyle({
+ textTransform: 'uppercase',
+ });
+ });
+
describe('prop: input', () => {
it('merges `ref` of `Select` and `input`', () => {
const Input = React.forwardRef(function Input(props, ref) {
diff --git a/packages/mui-material/src/Select/SelectInput.js b/packages/mui-material/src/Select/SelectInput.js
index 806baf3..30be750 100644
--- a/packages/mui-material/src/Select/SelectInput.js
+++ b/packages/mui-material/src/Select/SelectInput.js
@@ -9,8 +9,9 @@ import refType from '@mui/utils/refType';
import useTimeout from '@mui/utils/useTimeout';
import ownerDocument from '../utils/ownerDocument';
import Menu from '../Menu/Menu';
-import { StyledSelectSelect, StyledSelectIcon } from '../NativeSelect/NativeSelectInput';
import { isFilled } from '../InputBase/utils';
+import inputBaseClasses from '../InputBase/inputBaseClasses';
+import inputAdornmentClasses from '../InputAdornment/inputAdornmentClasses';
import { styled } from '../zero-styled';
import slotShouldForwardProp from '../styles/slotShouldForwardProp';
import useEnhancedEffect from '../utils/useEnhancedEffect';
@@ -72,7 +73,7 @@ function isMouseEventInsideElement(event, element) {
);
}
-const SelectSelect = styled(StyledSelectSelect, {
+const SelectSelect = styled('div', {
name: 'MuiSelect',
slot: 'Select',
overridesResolver: (props, styles) => {
@@ -85,7 +86,29 @@ const SelectSelect = styled(StyledSelectSelect, {
{ [`&.${selectClasses.multiple}`]: styles.multiple },
];
},
-})({
+})(({ theme }) => ({
+ // When interacting quickly, the text can end up selected.
+ // Native select can't be selected either.
+ userSelect: 'none',
+ // Reset
+ borderRadius: 0,
+ cursor: 'pointer',
+ '&:focus': {
+ // Reset Chrome style
+ borderRadius: 0,
+ },
+ [`&.${selectClasses.disabled}`]: {
+ cursor: 'default',
+ },
+ [`& ~ .${inputAdornmentClasses.root}`]: {
+ position: 'absolute',
+ top: '50%',
+ transform: 'translateY(-50%)',
+ right: 'calc(var(--_caret, 24px) + (var(--_endAdornment, 28px) - 1.5rem)/2)', // 1.5rem is the default icon size
+ },
+ [`.${inputBaseClasses.root}:has(> &)`]: {
+ '--_endAdornment': '0px',
+ },
// Win specificity over the input base
[`&.${selectClasses.select}`]: {
height: 'auto', // Resets for multiple select with chips
@@ -94,16 +117,108 @@ const SelectSelect = styled(StyledSelectSelect, {
whiteSpace: 'nowrap',
overflow: 'hidden',
},
-});
+ variants: [
+ {
+ props: ({ ownerState }) =>
+ ownerState.variant !== 'filled' && ownerState.variant !== 'outlined',
+ style: {
+ [`.${inputBaseClasses.root}:has(> &)`]: {
+ '--_caret': '24px',
+ },
+ [`.${inputBaseClasses.root}:has(> & ~ .${inputAdornmentClasses.root})`]: {
+ '--_endAdornment': '28px',
+ },
+ // Bump specificity to allow extending custom inputs
+ '&&&': {
+ paddingRight: 'calc(var(--_caret, 24px) + var(--_endAdornment, 0px))',
+ minWidth: 16, // So it doesn't collapse.
+ },
+ },
+ },
+ {
+ props: {
+ variant: 'filled',
+ },
+ style: {
+ [`.${inputBaseClasses.root}:has(> &)`]: {
+ '--_caret': '32px',
+ },
+ [`.${inputBaseClasses.root}:has(> & ~ .${inputAdornmentClasses.root})`]: {
+ '--_endAdornment': '28px',
+ },
+ '&&&': {
+ paddingRight: 'calc(var(--_caret, 32px) + var(--_endAdornment, 0px))',
+ },
+ },
+ },
+ {
+ props: {
+ variant: 'outlined',
+ },
+ style: {
+ [`.${inputBaseClasses.root}:has(> &)`]: {
+ '--_caret': '32px',
+ },
+ [`.${inputBaseClasses.root}:has(> & ~ .${inputAdornmentClasses.root})`]: {
+ '--_endAdornment': '28px',
+ },
+ borderRadius: (theme.vars || theme).shape.borderRadius,
+ '&:focus': {
+ borderRadius: (theme.vars || theme).shape.borderRadius, // Reset the reset for Chrome style
+ },
+ '&&&': {
+ paddingRight: 'calc(var(--_caret, 32px) + var(--_endAdornment, 0px))',
+ },
+ },
+ },
+ ],
+}));
-const SelectIcon = styled(StyledSelectIcon, {
+const SelectIcon = styled('svg', {
name: 'MuiSelect',
slot: 'Icon',
overridesResolver: (props, styles) => {
const { ownerState } = props;
return [styles.icon, ownerState.open && styles.iconOpen];
},
-})({});
+})(({ theme }) => ({
+ // We use a position absolute over a flexbox in order to forward the pointer events
+ // to the input and to support wrapping tags..
+ position: 'absolute',
+ right: 0,
+ // Center vertically, height is 1em
+ top: 'calc(50% - .5em)',
+ // Don't block pointer events on the select under the icon.
+ pointerEvents: 'none',
+ color: (theme.vars || theme).palette.action.active,
+ [`&.${selectClasses.disabled}`]: {
+ color: (theme.vars || theme).palette.action.disabled,
+ },
+ variants: [
+ {
+ props: ({ ownerState }) => ownerState.open,
+ style: {
+ transform: 'rotate(180deg)',
+ },
+ },
+ {
+ props: {
+ variant: 'filled',
+ },
+ style: {
+ right: 7,
+ },
+ },
+ {
+ props: {
+ variant: 'outlined',
+ },
+ style: {
+ right: 7,
+ },
+ },
+ ],
+}));
const SelectNativeInput = styled('input', {
shouldForwardProp: (prop) => slotShouldForwardProp(prop) && prop !== 'classes',
@@ -838,7 +953,6 @@ const SelectInput = React.forwardRef(function SelectInput(props, ref) {
return (