@@ -105,8 +105,7 @@ export class CdkTextareaAutosize implements AfterViewInit, DoCheck, OnDestroy {
105
105
/** Used to reference correct document/window */
106
106
protected _document ?: Document ;
107
107
108
- /** Class that should be applied to the textarea while it's being measured. */
109
- private _measuringClass : string ;
108
+ private _hasFocus : boolean ;
110
109
111
110
private _isViewInited = false ;
112
111
@@ -118,9 +117,6 @@ export class CdkTextareaAutosize implements AfterViewInit, DoCheck, OnDestroy {
118
117
this . _document = document ;
119
118
120
119
this . _textareaElement = this . _elementRef . nativeElement as HTMLTextAreaElement ;
121
- this . _measuringClass = _platform . FIREFOX ?
122
- 'cdk-textarea-autosize-measuring-firefox' :
123
- 'cdk-textarea-autosize-measuring' ;
124
120
}
125
121
126
122
/** Sets the minimum height of the textarea as determined by minRows. */
@@ -147,7 +143,6 @@ export class CdkTextareaAutosize implements AfterViewInit, DoCheck, OnDestroy {
147
143
if ( this . _platform . isBrowser ) {
148
144
// Remember the height which we started with in case autosizing is disabled
149
145
this . _initialHeight = this . _textareaElement . style . height ;
150
-
151
146
this . resizeToFitContent ( ) ;
152
147
153
148
this . _ngZone . runOutsideAngular ( ( ) => {
@@ -156,6 +151,9 @@ export class CdkTextareaAutosize implements AfterViewInit, DoCheck, OnDestroy {
156
151
fromEvent ( window , 'resize' )
157
152
. pipe ( auditTime ( 16 ) , takeUntil ( this . _destroyed ) )
158
153
. subscribe ( ( ) => this . resizeToFitContent ( true ) ) ;
154
+
155
+ this . _textareaElement . addEventListener ( 'focus' , this . _handleFocusEvent ) ;
156
+ this . _textareaElement . addEventListener ( 'blur' , this . _handleFocusEvent ) ;
159
157
} ) ;
160
158
161
159
this . _isViewInited = true ;
@@ -164,6 +162,8 @@ export class CdkTextareaAutosize implements AfterViewInit, DoCheck, OnDestroy {
164
162
}
165
163
166
164
ngOnDestroy ( ) {
165
+ this . _textareaElement . removeEventListener ( 'focus' , this . _handleFocusEvent ) ;
166
+ this . _textareaElement . removeEventListener ( 'blur' , this . _handleFocusEvent ) ;
167
167
this . _destroyed . next ( ) ;
168
168
this . _destroyed . complete ( ) ;
169
169
}
@@ -212,13 +212,32 @@ export class CdkTextareaAutosize implements AfterViewInit, DoCheck, OnDestroy {
212
212
}
213
213
214
214
private _measureScrollHeight ( ) : number {
215
+ const element = this . _textareaElement ;
216
+ const previousMargin = element . style . marginBottom || '' ;
217
+ const isFirefox = this . _platform . FIREFOX ;
218
+ const needsMarginFiller = isFirefox && this . _hasFocus ;
219
+ const measuringClass = isFirefox ?
220
+ 'cdk-textarea-autosize-measuring-firefox' :
221
+ 'cdk-textarea-autosize-measuring' ;
222
+
223
+ // In some cases the page might move around while we're measuring the `textarea` on Firefox. We
224
+ // work around it by assigning a temporary margin with the same height as the `textarea` so that
225
+ // it occupies the same amount of space. See #23233.
226
+ if ( needsMarginFiller ) {
227
+ element . style . marginBottom = `${ element . clientHeight } px` ;
228
+ }
229
+
215
230
// Reset the textarea height to auto in order to shrink back to its default size.
216
231
// Also temporarily force overflow:hidden, so scroll bars do not interfere with calculations.
217
- this . _textareaElement . classList . add ( this . _measuringClass ) ;
232
+ element . classList . add ( measuringClass ) ;
218
233
// The measuring class includes a 2px padding to workaround an issue with Chrome,
219
234
// so we account for that extra space here by subtracting 4 (2px top + 2px bottom).
220
- const scrollHeight = this . _textareaElement . scrollHeight - 4 ;
221
- this . _textareaElement . classList . remove ( this . _measuringClass ) ;
235
+ const scrollHeight = element . scrollHeight - 4 ;
236
+ element . classList . remove ( measuringClass ) ;
237
+
238
+ if ( needsMarginFiller ) {
239
+ element . style . marginBottom = previousMargin ;
240
+ }
222
241
223
242
return scrollHeight ;
224
243
}
@@ -239,6 +258,11 @@ export class CdkTextareaAutosize implements AfterViewInit, DoCheck, OnDestroy {
239
258
this . _textareaElement . value = value ;
240
259
}
241
260
261
+ /** Handles `focus` and `blur` events. */
262
+ private _handleFocusEvent = ( event : FocusEvent ) => {
263
+ this . _hasFocus = event . type === 'focus' ;
264
+ }
265
+
242
266
ngDoCheck ( ) {
243
267
if ( this . _platform . isBrowser ) {
244
268
this . resizeToFitContent ( ) ;
@@ -329,15 +353,14 @@ export class CdkTextareaAutosize implements AfterViewInit, DoCheck, OnDestroy {
329
353
*/
330
354
private _scrollToCaretPosition ( textarea : HTMLTextAreaElement ) {
331
355
const { selectionStart, selectionEnd} = textarea ;
332
- const document = this . _getDocument ( ) ;
333
356
334
357
// IE will throw an "Unspecified error" if we try to set the selection range after the
335
358
// element has been removed from the DOM. Assert that the directive hasn't been destroyed
336
359
// between the time we requested the animation frame and when it was executed.
337
360
// Also note that we have to assert that the textarea is focused before we set the
338
361
// selection range. Setting the selection range on a non-focused textarea will cause
339
362
// it to receive focus on IE and Edge.
340
- if ( ! this . _destroyed . isStopped && document . activeElement === textarea ) {
363
+ if ( ! this . _destroyed . isStopped && this . _hasFocus ) {
341
364
textarea . setSelectionRange ( selectionStart , selectionEnd ) ;
342
365
}
343
366
}
0 commit comments