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
100 changes: 100 additions & 0 deletions packages/mui-material/src/TextareaAutosize/TextareaAutosize.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -503,4 +503,104 @@ describe('<TextareaAutosize />', () => {
// and 2 times in a real browser
expect(handleSelectionChange.callCount).to.lessThanOrEqual(3);
});

it('should not lose textarea value during reflow workaround for controlled component', async () => {
function App() {
const [value, setValue] = React.useState<string>(
'some long text that makes the input start with multiple rows',
);

const handleChange = (event: React.ChangeEvent<any>) => {
setValue(event.target.value);
};

return <TextareaAutosize value={value} onChange={handleChange} />;
}

render(<App />);
const textarea = screen.getByRole<HTMLTextAreaElement>('textbox', {
hidden: false,
});

const originalValue = textarea.value;

act(() => {
textarea.focus();
});

// Trigger textarea height changes
fireEvent.change(textarea, {
target: { value: 'some short text' },
});
await raf();

fireEvent.change(textarea, {
target: { value: originalValue },
});
await raf();

expect(textarea.value).to.equal(originalValue);
});
it('should not lose textarea value during reflow workaround for uncontrolled component', async () => {
function App() {
return (
<TextareaAutosize defaultValue="some long text that makes the input start with multiple rows" />
);
}

render(<App />);
const textarea = screen.getByRole<HTMLTextAreaElement>('textbox', {
hidden: false,
});

const originalValue = textarea.value;

act(() => {
textarea.focus();
});

// Trigger textarea height changes
fireEvent.change(textarea, {
target: { value: 'some short text' },
});
await raf();

fireEvent.change(textarea, {
target: { value: originalValue },
});
await raf();

expect(textarea.value).to.equal(originalValue);
});
it('should not restore selection when textarea is not focused', async () => {
function App() {
const [value, setValue] = React.useState('Initial long text');
const handleChange = (event: React.ChangeEvent<any>) => {
setValue(event.target.value);
};
return (
<div>
<TextareaAutosize value={value} onChange={handleChange} />
<button onClick={() => setValue('Short')}>Change</button>
</div>
);
}

render(<App />);
const textarea = screen.getByRole<HTMLTextAreaElement>('textbox', {
hidden: false,
});
const button = screen.getByRole('button');

// Don't focus the textarea
expect(document.activeElement).not.to.equal(textarea);

// Change value programmatically
fireEvent.click(button);
await raf();

// Should update without error
expect(textarea.value).to.equal('Short');
expect(document.activeElement).not.to.equal(textarea);
});
});
16 changes: 16 additions & 0 deletions packages/mui-material/src/TextareaAutosize/TextareaAutosize.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import useForkRef from '@mui/utils/useForkRef';
import useEnhancedEffect from '@mui/utils/useEnhancedEffect';
import useEventCallback from '@mui/utils/useEventCallback';
import ownerWindow from '@mui/utils/ownerWindow';
import ownerDocument from '@mui/utils/ownerDocument';
import { TextareaAutosizeProps } from './TextareaAutosize.types';

function getStyleValue(value: string) {
Expand Down Expand Up @@ -153,6 +154,21 @@ const TextareaAutosize = React.forwardRef(function TextareaAutosize(
if (heightRef.current !== outerHeightStyle) {
heightRef.current = outerHeightStyle;
textarea.style.height = `${outerHeightStyle}px`;

// This is a workaround for Safari/WebKit not reflowing text when the textarea height changes
// Force Safari to reflow the text by manipulating the textarea value
const containerDocument = ownerDocument(textarea);
const selectionStart = textarea.selectionStart;
const selectionEnd = textarea.selectionEnd;
const tempValue = textarea.value;
textarea.value = '';
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
textarea.offsetHeight;
textarea.value = tempValue;
// Restore selection position
if (containerDocument.activeElement === textarea) {
textarea.setSelectionRange(selectionStart, selectionEnd);
}
}
textarea.style.overflow = textareaStyles.overflowing ? 'hidden' : '';
}, [calculateTextareaStyles]);
Expand Down
Loading