Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,89 @@ describe('<TextareaAutosize />', () => {
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(<TextareaAutosize style={{ maxHeight }} />);
const input = screen.getByRole<HTMLTextAreaElement>('textbox', {
hidden: false,
});
const shadow = screen.getAllByRole<HTMLTextAreaElement>('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(<TextareaAutosize style={{ maxHeight }} />);
const input = screen.getByRole<HTMLTextAreaElement>('textbox', {
hidden: false,
});
const shadow = screen.getAllByRole<HTMLTextAreaElement>('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(<TextareaAutosize style={{ maxHeight: '50%' }} />);
const input = screen.getByRole<HTMLTextAreaElement>('textbox', {
hidden: false,
});
const shadow = screen.getAllByRole<HTMLTextAreaElement>('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(<TextareaAutosize maxRows={3} />);
Expand Down
16 changes: 15 additions & 1 deletion packages/mui-material/src/TextareaAutosize/TextareaAutosize.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
} = {
Expand Down Expand Up @@ -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]);
Expand Down
Loading