@@ -36,16 +36,13 @@ type TextareaStyles = {
36
36
overflowing : boolean ;
37
37
} ;
38
38
39
- function isObjectEmpty ( object : TextareaStyles ) {
40
- // eslint-disable-next-line
41
- for ( const _ in object ) {
42
- return false ;
43
- }
44
- return true ;
45
- }
46
-
47
39
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
+ ) ;
49
46
}
50
47
51
48
/**
@@ -65,21 +62,16 @@ const TextareaAutosize = React.forwardRef(function TextareaAutosize(
65
62
const { onChange, maxRows, minRows = 1 , style, value, ...other } = props ;
66
63
67
64
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 ) ;
70
67
const heightRef = React . useRef < number > ( null ) ;
71
- const hiddenTextareaRef = React . useRef < HTMLTextAreaElement > ( null ) ;
68
+ const shadowRef = React . useRef < HTMLTextAreaElement > ( null ) ;
72
69
73
70
const calculateTextareaStyles = React . useCallback ( ( ) => {
74
- const textarea = textareaRef . current ;
75
- const hiddenTextarea = hiddenTextareaRef . current ;
71
+ const input = inputRef . current ! ;
76
72
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 ) ;
83
75
84
76
// If input's width is shrunk and it's not visible, don't sync height.
85
77
if ( computedStyle . width === '0px' ) {
@@ -89,13 +81,15 @@ const TextareaAutosize = React.forwardRef(function TextareaAutosize(
89
81
} ;
90
82
}
91
83
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' ) {
95
89
// Certain fonts which overflow the line height will cause the textarea
96
90
// to report a different scrollHeight depending on whether the last line
97
91
// is empty. Make it non-empty to avoid this issue.
98
- hiddenTextarea . value += ' ' ;
92
+ inputShallow . value += ' ' ;
99
93
}
100
94
101
95
const boxSizing = computedStyle . boxSizing ;
@@ -105,11 +99,11 @@ const TextareaAutosize = React.forwardRef(function TextareaAutosize(
105
99
getStyleValue ( computedStyle . borderBottomWidth ) + getStyleValue ( computedStyle . borderTopWidth ) ;
106
100
107
101
// The height of the inner content
108
- const innerHeight = hiddenTextarea . scrollHeight ;
102
+ const innerHeight = inputShallow . scrollHeight ;
109
103
110
104
// 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 ;
113
107
114
108
// The height of the outer content
115
109
let outerHeight = innerHeight ;
@@ -130,55 +124,54 @@ const TextareaAutosize = React.forwardRef(function TextareaAutosize(
130
124
} , [ maxRows , minRows , props . placeholder ] ) ;
131
125
132
126
const syncHeight = React . useCallback ( ( ) => {
133
- const textarea = textareaRef . current ;
134
127
const textareaStyles = calculateTextareaStyles ( ) ;
135
128
136
- if ( ! textarea || ! textareaStyles || isEmpty ( textareaStyles ) ) {
129
+ if ( isEmpty ( textareaStyles ) ) {
137
130
return ;
138
131
}
139
132
140
133
const outerHeightStyle = textareaStyles . outerHeightStyle ;
134
+ const input = inputRef . current ! ;
141
135
if ( heightRef . current !== outerHeightStyle ) {
142
136
heightRef . current = outerHeightStyle ;
143
- textarea . style . height = `${ outerHeightStyle } px` ;
137
+ input . style . height = `${ outerHeightStyle } px` ;
144
138
}
145
- textarea . style . overflow = textareaStyles . overflowing ? 'hidden' : '' ;
139
+ input . style . overflow = textareaStyles . overflowing ? 'hidden' : '' ;
146
140
} , [ calculateTextareaStyles ] ) ;
147
141
148
- const frameRef = React . useRef ( - 1 ) ;
149
-
150
142
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 ) ;
159
160
160
161
containerWindow . addEventListener ( 'resize' , debounceHandleResize ) ;
161
162
162
163
let resizeObserver : ResizeObserver ;
163
164
164
165
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 ) ;
177
170
}
178
171
179
172
return ( ) => {
180
173
debounceHandleResize . clear ( ) ;
181
- cancelAnimationFrame ( frameRef . current ) ;
174
+ cancelAnimationFrame ( rAF ) ;
182
175
containerWindow . removeEventListener ( 'resize' , debounceHandleResize ) ;
183
176
if ( resizeObserver ) {
184
177
resizeObserver . disconnect ( ) ;
@@ -215,7 +208,7 @@ const TextareaAutosize = React.forwardRef(function TextareaAutosize(
215
208
aria-hidden
216
209
className = { props . className }
217
210
readOnly
218
- ref = { hiddenTextareaRef }
211
+ ref = { shadowRef }
219
212
tabIndex = { - 1 }
220
213
style = { {
221
214
...styles . shadow ,
0 commit comments