diff --git a/packages/mui-material/src/TextareaAutosize/TextareaAutosize.test.tsx b/packages/mui-material/src/TextareaAutosize/TextareaAutosize.test.tsx index 1647deca7afe1d..e7aae18afa703d 100644 --- a/packages/mui-material/src/TextareaAutosize/TextareaAutosize.test.tsx +++ b/packages/mui-material/src/TextareaAutosize/TextareaAutosize.test.tsx @@ -366,6 +366,89 @@ describe('', () => { expect(input.style).to.have.property('overflow', ''); }); + it('should show scrollbar when the content is taller than the CSS "max-height"', () => { + const lineHeight = 15; + const maxHeight = lineHeight * 3; + const { forceUpdate } = render(); + const input = screen.getByRole('textbox', { + hidden: false, + }); + const shadow = screen.getAllByRole('textbox', { + hidden: true, + })[1]; + setLayout(input, shadow, { + getComputedStyle: { + boxSizing: 'content-box', + maxHeight: `${maxHeight}px`, + }, + scrollHeight: lineHeight * 2, + lineHeight, + }); + forceUpdate(); + expect(input.style).to.have.property('height', `${lineHeight * 2}px`); + expect(input.style).to.have.property('overflow', 'hidden'); + + setLayout(input, shadow, { + getComputedStyle: { + boxSizing: 'content-box', + maxHeight: `${maxHeight}px`, + }, + scrollHeight: lineHeight * 4, + lineHeight, + }); + forceUpdate(); + expect(input.style).to.have.property('overflow', ''); + }); + + it('should take the box sizing into account when comparing with the CSS "max-height"', () => { + const border = 5; + const lineHeight = 15; + // The content on its own fits, the border pushes the border box past the limit. + const maxHeight = lineHeight * 2 + border - 1; + const { forceUpdate } = render(); + const input = screen.getByRole('textbox', { + hidden: false, + }); + const shadow = screen.getAllByRole('textbox', { + hidden: true, + })[1]; + setLayout(input, shadow, { + getComputedStyle: { + boxSizing: 'border-box', + borderBottomWidth: `${border}px`, + maxHeight: `${maxHeight}px`, + }, + scrollHeight: lineHeight * 2, + lineHeight, + }); + forceUpdate(); + expect(input.style).to.have.property('height', `${lineHeight * 2 + border}px`); + expect(input.style).to.have.property('overflow', ''); + }); + + it('should keep hiding the overflow when the CSS "max-height" is not an absolute length', () => { + const lineHeight = 15; + const { forceUpdate } = render(); + const input = screen.getByRole('textbox', { + hidden: false, + }); + const shadow = screen.getAllByRole('textbox', { + hidden: true, + })[1]; + setLayout(input, shadow, { + getComputedStyle: { + boxSizing: 'content-box', + // `getComputedStyle` does not resolve percentages for `max-height`. + maxHeight: '50%', + }, + scrollHeight: lineHeight * 4, + lineHeight, + }); + forceUpdate(); + expect(input.style).to.have.property('height', `${lineHeight * 4}px`); + expect(input.style).to.have.property('overflow', 'hidden'); + }); + it('should update its height when the "maxRows" prop changes', () => { const lineHeight = 15; const { forceUpdate, setProps } = render(); diff --git a/packages/mui-material/src/TextareaAutosize/TextareaAutosize.tsx b/packages/mui-material/src/TextareaAutosize/TextareaAutosize.tsx index b3d460a1d6eaef..59d809c79efbdb 100644 --- a/packages/mui-material/src/TextareaAutosize/TextareaAutosize.tsx +++ b/packages/mui-material/src/TextareaAutosize/TextareaAutosize.tsx @@ -12,6 +12,17 @@ function getStyleValue(value: string) { return parseInt(value, 10) || 0; } +function getMaxHeight(value: string) { + // `getComputedStyle` resolves `max-height` to an absolute length, except for values that + // depend on the layout, for example percentages or `calc()` expressions mixing both. + // Those can't be compared with the computed height, so they are treated as unbounded. + if (!value || !value.endsWith('px')) { + return Infinity; + } + + return getStyleValue(value); +} + const styles: { shadow: React.CSSProperties; } = { @@ -123,7 +134,10 @@ const TextareaAutosize = React.forwardRef(function TextareaAutosize( // Take the box sizing into account for applying this value as a style. const outerHeightStyle = outerHeight + (boxSizing === 'border-box' ? padding + border : 0); - const overflowing = Math.abs(outerHeight - innerHeight) <= 1; + // A CSS `max-height` clips the textarea before it reaches `outerHeightStyle`, so the content + // has to stay scrollable, the same way it does once `maxRows` is reached. + const clippedByMaxHeight = outerHeightStyle > getMaxHeight(computedStyle.maxHeight); + const overflowing = Math.abs(outerHeight - innerHeight) <= 1 && !clippedByMaxHeight; return { outerHeightStyle, overflowing }; }, [maxRows, minRows, props.placeholder]);