Skip to content

Commit 1b7a9ee

Browse files
committed
Revert "[material-ui][TextareaAutosize] Temporarily disconnect ResizeObserver to avoid loop error (mui#44540)"
This reverts commit cce1222.
1 parent f273fa5 commit 1b7a9ee

File tree

1 file changed

+48
-55
lines changed

1 file changed

+48
-55
lines changed

packages/mui-material/src/TextareaAutosize/TextareaAutosize.tsx

+48-55
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,13 @@ type TextareaStyles = {
3636
overflowing: boolean;
3737
};
3838

39-
function isObjectEmpty(object: TextareaStyles) {
40-
// eslint-disable-next-line
41-
for (const _ in object) {
42-
return false;
43-
}
44-
return true;
45-
}
46-
4739
function isEmpty(obj: TextareaStyles) {
48-
return isObjectEmpty(obj) || (obj.outerHeightStyle === 0 && !obj.overflowing);
40+
return (
41+
obj === undefined ||
42+
obj === null ||
43+
Object.keys(obj).length === 0 ||
44+
(obj.outerHeightStyle === 0 && !obj.overflowing)
45+
);
4946
}
5047

5148
/**
@@ -65,21 +62,16 @@ const TextareaAutosize = React.forwardRef(function TextareaAutosize(
6562
const { onChange, maxRows, minRows = 1, style, value, ...other } = props;
6663

6764
const { current: isControlled } = React.useRef(value != null);
68-
const textareaRef = React.useRef<HTMLTextAreaElement>(null);
69-
const handleRef = useForkRef(forwardedRef, textareaRef);
65+
const inputRef = React.useRef<HTMLTextAreaElement>(null);
66+
const handleRef = useForkRef(forwardedRef, inputRef);
7067
const heightRef = React.useRef<number>(null);
71-
const hiddenTextareaRef = React.useRef<HTMLTextAreaElement>(null);
68+
const shadowRef = React.useRef<HTMLTextAreaElement>(null);
7269

7370
const calculateTextareaStyles = React.useCallback(() => {
74-
const textarea = textareaRef.current;
75-
const hiddenTextarea = hiddenTextareaRef.current;
71+
const input = inputRef.current!;
7672

77-
if (!textarea || !hiddenTextarea) {
78-
return undefined;
79-
}
80-
81-
const containerWindow = ownerWindow(textarea);
82-
const computedStyle = containerWindow.getComputedStyle(textarea);
73+
const containerWindow = ownerWindow(input);
74+
const computedStyle = containerWindow.getComputedStyle(input);
8375

8476
// If input's width is shrunk and it's not visible, don't sync height.
8577
if (computedStyle.width === '0px') {
@@ -89,13 +81,15 @@ const TextareaAutosize = React.forwardRef(function TextareaAutosize(
8981
};
9082
}
9183

92-
hiddenTextarea.style.width = computedStyle.width;
93-
hiddenTextarea.value = textarea.value || props.placeholder || 'x';
94-
if (hiddenTextarea.value.slice(-1) === '\n') {
84+
const inputShallow = shadowRef.current!;
85+
86+
inputShallow.style.width = computedStyle.width;
87+
inputShallow.value = input.value || props.placeholder || 'x';
88+
if (inputShallow.value.slice(-1) === '\n') {
9589
// Certain fonts which overflow the line height will cause the textarea
9690
// to report a different scrollHeight depending on whether the last line
9791
// is empty. Make it non-empty to avoid this issue.
98-
hiddenTextarea.value += ' ';
92+
inputShallow.value += ' ';
9993
}
10094

10195
const boxSizing = computedStyle.boxSizing;
@@ -105,11 +99,11 @@ const TextareaAutosize = React.forwardRef(function TextareaAutosize(
10599
getStyleValue(computedStyle.borderBottomWidth) + getStyleValue(computedStyle.borderTopWidth);
106100

107101
// The height of the inner content
108-
const innerHeight = hiddenTextarea.scrollHeight;
102+
const innerHeight = inputShallow.scrollHeight;
109103

110104
// Measure height of a textarea with a single row
111-
hiddenTextarea.value = 'x';
112-
const singleRowHeight = hiddenTextarea.scrollHeight;
105+
inputShallow.value = 'x';
106+
const singleRowHeight = inputShallow.scrollHeight;
113107

114108
// The height of the outer content
115109
let outerHeight = innerHeight;
@@ -130,55 +124,54 @@ const TextareaAutosize = React.forwardRef(function TextareaAutosize(
130124
}, [maxRows, minRows, props.placeholder]);
131125

132126
const syncHeight = React.useCallback(() => {
133-
const textarea = textareaRef.current;
134127
const textareaStyles = calculateTextareaStyles();
135128

136-
if (!textarea || !textareaStyles || isEmpty(textareaStyles)) {
129+
if (isEmpty(textareaStyles)) {
137130
return;
138131
}
139132

140133
const outerHeightStyle = textareaStyles.outerHeightStyle;
134+
const input = inputRef.current!;
141135
if (heightRef.current !== outerHeightStyle) {
142136
heightRef.current = outerHeightStyle;
143-
textarea.style.height = `${outerHeightStyle}px`;
137+
input.style.height = `${outerHeightStyle}px`;
144138
}
145-
textarea.style.overflow = textareaStyles.overflowing ? 'hidden' : '';
139+
input.style.overflow = textareaStyles.overflowing ? 'hidden' : '';
146140
}, [calculateTextareaStyles]);
147141

148-
const frameRef = React.useRef(-1);
149-
150142
useEnhancedEffect(() => {
151-
const debounceHandleResize = debounce(() => syncHeight());
152-
const textarea = textareaRef?.current;
153-
154-
if (!textarea) {
155-
return undefined;
156-
}
157-
158-
const containerWindow = ownerWindow(textarea);
143+
const handleResize = () => {
144+
syncHeight();
145+
};
146+
// Workaround a "ResizeObserver loop completed with undelivered notifications" error
147+
// in test.
148+
// Note that we might need to use this logic in production per https://github.com/WICG/resize-observer/issues/38
149+
// Also see https://github.com/mui/mui-x/issues/8733
150+
let rAF: any;
151+
const rAFHandleResize = () => {
152+
cancelAnimationFrame(rAF);
153+
rAF = requestAnimationFrame(() => {
154+
handleResize();
155+
});
156+
};
157+
const debounceHandleResize = debounce(handleResize);
158+
const input = inputRef.current!;
159+
const containerWindow = ownerWindow(input);
159160

160161
containerWindow.addEventListener('resize', debounceHandleResize);
161162

162163
let resizeObserver: ResizeObserver;
163164

164165
if (typeof ResizeObserver !== 'undefined') {
165-
resizeObserver = new ResizeObserver(() => {
166-
// avoid "ResizeObserver loop completed with undelivered notifications" error
167-
// by temporarily unobserving the textarea element while manipulating the height
168-
// and reobserving one frame later
169-
resizeObserver.unobserve(textarea);
170-
cancelAnimationFrame(frameRef.current);
171-
syncHeight();
172-
frameRef.current = requestAnimationFrame(() => {
173-
resizeObserver.observe(textarea);
174-
});
175-
});
176-
resizeObserver.observe(textarea);
166+
resizeObserver = new ResizeObserver(
167+
process.env.NODE_ENV === 'test' ? rAFHandleResize : handleResize,
168+
);
169+
resizeObserver.observe(input);
177170
}
178171

179172
return () => {
180173
debounceHandleResize.clear();
181-
cancelAnimationFrame(frameRef.current);
174+
cancelAnimationFrame(rAF);
182175
containerWindow.removeEventListener('resize', debounceHandleResize);
183176
if (resizeObserver) {
184177
resizeObserver.disconnect();
@@ -215,7 +208,7 @@ const TextareaAutosize = React.forwardRef(function TextareaAutosize(
215208
aria-hidden
216209
className={props.className}
217210
readOnly
218-
ref={hiddenTextareaRef}
211+
ref={shadowRef}
219212
tabIndex={-1}
220213
style={{
221214
...styles.shadow,

0 commit comments

Comments
 (0)