Repository navigation
Conversation
SelectInput reused the styled select and icon from NativeSelectInput, so NativeSelect carried the open icon rotation and the iconOpen class even though a native select never reports being open, and MuiNativeSelect theme variants also styled the Select display and icon. Give SelectInput its own styles, drop the open handling from NativeSelectInput and document that NativeSelect never applies iconOpen.
Deploy previewBundle size
PerformanceNo regressions in 32 benchmarks · ran 3m 25s · details Check out the code infra dashboard for more information about this PR. |
|
michaldudak suggested keeping it with an updated description in the issue, since removing it from |
But the class is not applied to native select, right? |
|
right, NativeSelect never has an open state so it's never applied there. removed it |
| } | ||
|
|
||
| const SelectSelect = styled(StyledSelectSelect, { | ||
| const SelectSelect = styled('div', { |
There was a problem hiding this comment.
We don't need to copy NativeSelect's styles here. The issue only needs the open-icon rotation to live in Select, since Select is the only one with an open state. Can we keep extending StyledSelectSelect like on master (restoring the import and as="div")?
Copying ~110 lines puts the adornment/--_caret logic in two places that have to stay in sync, and it adds +1.41KB parsed to the bundle.
The MuiNativeSelect theme variants leaking into Select is a real bug, but it's separate from #35781. Let's handle it in its own PR if we want to fix it.
| 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, | ||
| }, | ||
| }, | ||
| ], | ||
| })); |
There was a problem hiding this comment.
Same here: keep extending StyledSelectIcon and only add the rotation you removed from it:
| 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 SelectIcon = styled(StyledSelectIcon, { | |
| name: 'MuiSelect', | |
| slot: 'Icon', | |
| overridesResolver: (props, styles) => { | |
| const { ownerState } = props; | |
| return [styles.icon, ownerState.open && styles.iconOpen]; | |
| }, | |
| })({ | |
| variants: [ | |
| { | |
| props: ({ ownerState }) => ownerState.open, | |
| style: { | |
| transform: 'rotate(180deg)', | |
| }, | |
| }, | |
| ], | |
| }); |
| }; | ||
|
|
||
| export const StyledSelectSelect = styled('select', { | ||
| const StyledSelectSelect = styled('select', { |
There was a problem hiding this comment.
Let's keep these exported, since SelectInput keeps extending them.
| const StyledSelectSelect = styled('select', { | |
| export const StyledSelectSelect = styled('select', { |
| })({}); | ||
|
|
||
| export const StyledSelectIcon = styled('svg', { | ||
| const StyledSelectIcon = styled('svg', { |
There was a problem hiding this comment.
Same here.
| const StyledSelectIcon = styled('svg', { | |
| export const StyledSelectIcon = styled('svg', { |
| it('should not apply MuiNativeSelect theme variants', () => { | ||
| const theme = createTheme({ | ||
| components: { | ||
| MuiNativeSelect: { | ||
| variants: [ | ||
| { | ||
| props: { | ||
| variant: 'outlined', | ||
| }, | ||
| style: { | ||
| textTransform: 'uppercase', | ||
| }, | ||
| }, | ||
| ], | ||
| }, | ||
| }, | ||
| }); | ||
|
|
||
| const { container } = render( | ||
| <ThemeProvider theme={theme}> | ||
| <Select variant="outlined" value="first"> | ||
| <MenuItem value="first" /> | ||
| <MenuItem value="second" /> | ||
| </Select> | ||
| </ThemeProvider>, | ||
| ); | ||
|
|
||
| expect(container.querySelector(`.${classes.select}`)).not.toHaveComputedStyle({ | ||
| textTransform: 'uppercase', | ||
| }); | ||
| expect(container.querySelector(`.${classes.icon}`)).not.toHaveComputedStyle({ | ||
| textTransform: 'uppercase', | ||
| }); | ||
| }); |
There was a problem hiding this comment.
This test covers the theme variant leak, not the issue. Since the rotation is the only style that moves, let's test that instead. Select has no test for it today. This passes in both jsdom and Chromium:
| it('should not apply MuiNativeSelect theme variants', () => { | |
| const theme = createTheme({ | |
| components: { | |
| MuiNativeSelect: { | |
| variants: [ | |
| { | |
| props: { | |
| variant: 'outlined', | |
| }, | |
| style: { | |
| textTransform: 'uppercase', | |
| }, | |
| }, | |
| ], | |
| }, | |
| }, | |
| }); | |
| const { container } = render( | |
| <ThemeProvider theme={theme}> | |
| <Select variant="outlined" value="first"> | |
| <MenuItem value="first" /> | |
| <MenuItem value="second" /> | |
| </Select> | |
| </ThemeProvider>, | |
| ); | |
| expect(container.querySelector(`.${classes.select}`)).not.toHaveComputedStyle({ | |
| textTransform: 'uppercase', | |
| }); | |
| expect(container.querySelector(`.${classes.icon}`)).not.toHaveComputedStyle({ | |
| textTransform: 'uppercase', | |
| }); | |
| }); | |
| it('should rotate the icon when open', () => { | |
| const { container } = render(<Select open value="" />); | |
| const icon = container.querySelector(`.${classes.icon}`); | |
| expect(icon).to.have.class(classes.iconOpen); | |
| expect(icon).not.toHaveComputedStyle({ transform: 'none' }); | |
| }); |
|
done, back to extending StyledSelectSelect/StyledSelectIcon with only the rotation moved to Select, and swapped in your test. ill do the theme variant leak in a separate pr |
a native select doesnt expose its open state, so
NativeSelectcan never applyiconOpen. went with michaldudak's suggestion from the issue: the class description now says that, andSelectInputhas its own select and icon styles instead of reusingNativeSelectInput's, so the open rotation only lives in Select andMuiNativeSelecttheme variants stop styling itadded
should not apply MuiNativeSelect theme variantstoSelect.test.js(fails on master), computed styles of both components in chromium are the same before and after. the copy skips the rules that can only match a native<select>resolves #35781