@@ -105,8 +105,7 @@ export class CdkTextareaAutosize implements AfterViewInit, DoCheck, OnDestroy {
105105 /** Used to reference correct document/window */
106106 protected _document ?: Document ;
107107
108- /** Class that should be applied to the textarea while it's being measured. */
109- private _measuringClass : string ;
108+ private _hasFocus : boolean ;
110109
111110 private _isViewInited = false ;
112111
@@ -118,9 +117,6 @@ export class CdkTextareaAutosize implements AfterViewInit, DoCheck, OnDestroy {
118117 this . _document = document ;
119118
120119 this . _textareaElement = this . _elementRef . nativeElement as HTMLTextAreaElement ;
121- this . _measuringClass = _platform . FIREFOX ?
122- 'cdk-textarea-autosize-measuring-firefox' :
123- 'cdk-textarea-autosize-measuring' ;
124120 }
125121
126122 /** Sets the minimum height of the textarea as determined by minRows. */
@@ -147,7 +143,6 @@ export class CdkTextareaAutosize implements AfterViewInit, DoCheck, OnDestroy {
147143 if ( this . _platform . isBrowser ) {
148144 // Remember the height which we started with in case autosizing is disabled
149145 this . _initialHeight = this . _textareaElement . style . height ;
150-
151146 this . resizeToFitContent ( ) ;
152147
153148 this . _ngZone . runOutsideAngular ( ( ) => {
@@ -156,6 +151,9 @@ export class CdkTextareaAutosize implements AfterViewInit, DoCheck, OnDestroy {
156151 fromEvent ( window , 'resize' )
157152 . pipe ( auditTime ( 16 ) , takeUntil ( this . _destroyed ) )
158153 . subscribe ( ( ) => this . resizeToFitContent ( true ) ) ;
154+
155+ this . _textareaElement . addEventListener ( 'focus' , this . _handleFocusEvent ) ;
156+ this . _textareaElement . addEventListener ( 'blur' , this . _handleFocusEvent ) ;
159157 } ) ;
160158
161159 this . _isViewInited = true ;
@@ -164,6 +162,8 @@ export class CdkTextareaAutosize implements AfterViewInit, DoCheck, OnDestroy {
164162 }
165163
166164 ngOnDestroy ( ) {
165+ this . _textareaElement . removeEventListener ( 'focus' , this . _handleFocusEvent ) ;
166+ this . _textareaElement . removeEventListener ( 'blur' , this . _handleFocusEvent ) ;
167167 this . _destroyed . next ( ) ;
168168 this . _destroyed . complete ( ) ;
169169 }
@@ -212,13 +212,32 @@ export class CdkTextareaAutosize implements AfterViewInit, DoCheck, OnDestroy {
212212 }
213213
214214 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+
215230 // Reset the textarea height to auto in order to shrink back to its default size.
216231 // 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 ) ;
218233 // The measuring class includes a 2px padding to workaround an issue with Chrome,
219234 // 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+ }
222241
223242 return scrollHeight ;
224243 }
@@ -239,6 +258,11 @@ export class CdkTextareaAutosize implements AfterViewInit, DoCheck, OnDestroy {
239258 this . _textareaElement . value = value ;
240259 }
241260
261+ /** Handles `focus` and `blur` events. */
262+ private _handleFocusEvent = ( event : FocusEvent ) => {
263+ this . _hasFocus = event . type === 'focus' ;
264+ }
265+
242266 ngDoCheck ( ) {
243267 if ( this . _platform . isBrowser ) {
244268 this . resizeToFitContent ( ) ;
@@ -329,15 +353,14 @@ export class CdkTextareaAutosize implements AfterViewInit, DoCheck, OnDestroy {
329353 */
330354 private _scrollToCaretPosition ( textarea : HTMLTextAreaElement ) {
331355 const { selectionStart, selectionEnd} = textarea ;
332- const document = this . _getDocument ( ) ;
333356
334357 // IE will throw an "Unspecified error" if we try to set the selection range after the
335358 // element has been removed from the DOM. Assert that the directive hasn't been destroyed
336359 // between the time we requested the animation frame and when it was executed.
337360 // Also note that we have to assert that the textarea is focused before we set the
338361 // selection range. Setting the selection range on a non-focused textarea will cause
339362 // it to receive focus on IE and Edge.
340- if ( ! this . _destroyed . isStopped && document . activeElement === textarea ) {
363+ if ( ! this . _destroyed . isStopped && this . _hasFocus ) {
341364 textarea . setSelectionRange ( selectionStart , selectionEnd ) ;
342365 }
343366 }
0 commit comments