From 50d7c7db3a19d948e4cc30fc737f9979633de07b Mon Sep 17 00:00:00 2001 From: Hukumchand-Narwre Date: Sun, 7 Dec 2025 11:14:49 +0530 Subject: [PATCH 1/2] [TextField] Fix Backspacing after line break in multiline causes height issues on Safari --- .../TextareaAutosize.test.tsx | 100 ++++++++++++++++++ .../src/TextareaAutosize/TextareaAutosize.tsx | 16 +++ 2 files changed, 116 insertions(+) diff --git a/packages/mui-material/src/TextareaAutosize/TextareaAutosize.test.tsx b/packages/mui-material/src/TextareaAutosize/TextareaAutosize.test.tsx index dc182cb4ac29e4..97f247f3fc4f6c 100644 --- a/packages/mui-material/src/TextareaAutosize/TextareaAutosize.test.tsx +++ b/packages/mui-material/src/TextareaAutosize/TextareaAutosize.test.tsx @@ -503,4 +503,104 @@ describe('', () => { // 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( + 'some long text that makes the input start with multiple rows', + ); + + const handleChange = (event: React.ChangeEvent) => { + setValue(event.target.value); + }; + + return ; + } + + render(); + const textarea = screen.getByRole('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 ( + + ); + } + + render(); + const textarea = screen.getByRole('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) => { + setValue(event.target.value); + }; + return ( +
+ + +
+ ); + } + + render(); + const textarea = screen.getByRole('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); + }); }); diff --git a/packages/mui-material/src/TextareaAutosize/TextareaAutosize.tsx b/packages/mui-material/src/TextareaAutosize/TextareaAutosize.tsx index b3d460a1d6eaef..3947fdfcf0a9fe 100644 --- a/packages/mui-material/src/TextareaAutosize/TextareaAutosize.tsx +++ b/packages/mui-material/src/TextareaAutosize/TextareaAutosize.tsx @@ -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) { @@ -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]); From 9980173e058ef0944b6bf286342dd45c0c4e0f4a Mon Sep 17 00:00:00 2001 From: Hukumchand-Narwre Date: Wed, 17 Dec 2025 09:03:02 +0530 Subject: [PATCH 2/2] comments resolved --- .../src/TextareaAutosize/TextareaAutosize.tsx | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/packages/mui-material/src/TextareaAutosize/TextareaAutosize.tsx b/packages/mui-material/src/TextareaAutosize/TextareaAutosize.tsx index 3947fdfcf0a9fe..c5f98e72ea80ac 100644 --- a/packages/mui-material/src/TextareaAutosize/TextareaAutosize.tsx +++ b/packages/mui-material/src/TextareaAutosize/TextareaAutosize.tsx @@ -157,17 +157,23 @@ const TextareaAutosize = React.forwardRef(function TextareaAutosize( // 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); + const isWebKit = + typeof CSS === 'undefined' || !CSS.supports + ? false + : CSS.supports('-webkit-backdrop-filter:none'); + + if (isWebKit) { + const containerDocument = ownerDocument(textarea); + const selectionStart = textarea.selectionStart; + const selectionEnd = textarea.selectionEnd; + const tempValue = textarea.value; + textarea.value = ''; + void textarea.offsetHeight; + textarea.value = tempValue; + // Restore selection position + if (containerDocument.activeElement === textarea) { + textarea.setSelectionRange(selectionStart, selectionEnd); + } } } textarea.style.overflow = textareaStyles.overflowing ? 'hidden' : '';