@@ -250,7 +250,7 @@ function labelDirective() {
250
250
*
251
251
*/
252
252
253
- function inputTextareaDirective ( $mdUtil , $window , $mdAria ) {
253
+ function inputTextareaDirective ( $mdUtil , $window , $mdAria , $timeout ) {
254
254
return {
255
255
restrict : 'E' ,
256
256
require : [ '^?mdInputContainer' , '?ngModel' ] ,
@@ -365,84 +365,81 @@ function inputTextareaDirective($mdUtil, $window, $mdAria) {
365
365
}
366
366
367
367
function setupTextarea ( ) {
368
- if ( angular . isDefined ( element . attr ( 'md-no-autogrow' ) ) ) {
368
+ if ( attr . hasOwnProperty ( 'mdNoAutogrow' ) ) {
369
369
return ;
370
370
}
371
371
372
- var node = element [ 0 ] ;
373
- var container = containerCtrl . element [ 0 ] ;
374
-
375
- var min_rows = NaN ;
376
- var lineHeight = null ;
377
- // can't check if height was or not explicity set,
372
+ // Can't check if height was or not explicity set,
378
373
// so rows attribute will take precedence if present
379
- if ( node . hasAttribute ( 'rows' ) ) {
380
- min_rows = parseInt ( node . getAttribute ( 'rows' ) ) ;
381
- }
382
-
383
- var onChangeTextarea = $mdUtil . debounce ( growTextarea , 1 ) ;
384
-
385
- function pipelineListener ( value ) {
386
- onChangeTextarea ( ) ;
387
- return value ;
388
- }
374
+ var minRows = attr . hasOwnProperty ( 'rows' ) ? parseInt ( attr . rows ) : NaN ;
375
+ var lineHeight = null ;
376
+ var node = element [ 0 ] ;
389
377
390
- if ( ngModelCtrl ) {
391
- ngModelCtrl . $formatters . push ( pipelineListener ) ;
392
- ngModelCtrl . $viewChangeListeners . push ( pipelineListener ) ;
378
+ // This timeout is necessary, because the browser needs a little bit
379
+ // of time to calculate the `clientHeight` and `scrollHeight`.
380
+ $timeout ( function ( ) {
381
+ $mdUtil . nextTick ( growTextarea ) ;
382
+ } , 10 , false ) ;
383
+
384
+ // We can hook into Angular's pipeline, instead of registering a new listener.
385
+ // Note that we should use `$parsers`, as opposed to `$viewChangeListeners` which
386
+ // was used before, because `$viewChangeListeners` don't fire if the input is
387
+ // invalid.
388
+ if ( hasNgModel ) {
389
+ ngModelCtrl . $formatters . unshift ( pipelineListener ) ;
390
+ ngModelCtrl . $parsers . unshift ( pipelineListener ) ;
393
391
} else {
394
- onChangeTextarea ( ) ;
392
+ // Note that it's safe to use the `input` event since we're not supporting IE9 and below.
393
+ element . on ( 'input' , growTextarea ) ;
395
394
}
396
- element . on ( 'keydown input' , onChangeTextarea ) ;
397
-
398
- if ( isNaN ( min_rows ) ) {
399
- element . attr ( 'rows' , '1' ) ;
400
395
401
- element . on ( 'scroll' , onScroll ) ;
396
+ if ( ! minRows ) {
397
+ element
398
+ . attr ( 'rows' , 1 )
399
+ . on ( 'scroll' , onScroll ) ;
402
400
}
403
401
404
- angular . element ( $window ) . on ( 'resize' , onChangeTextarea ) ;
402
+ angular . element ( $window ) . on ( 'resize' , growTextarea ) ;
405
403
406
404
scope . $on ( '$destroy' , function ( ) {
407
- angular . element ( $window ) . off ( 'resize' , onChangeTextarea ) ;
405
+ angular . element ( $window ) . off ( 'resize' , growTextarea ) ;
408
406
} ) ;
409
407
410
408
function growTextarea ( ) {
411
- // sets the md-input-container height to avoid jumping around
412
- container . style . height = container . offsetHeight + 'px' ;
413
-
414
409
// temporarily disables element's flex so its height 'runs free'
415
- element . addClass ( 'md-no-flex' ) ;
416
-
417
- if ( isNaN ( min_rows ) ) {
418
- node . style . height = "auto" ;
419
- node . scrollTop = 0 ;
420
- var height = getHeight ( ) ;
421
- if ( height ) node . style . height = height + 'px' ;
422
- } else {
423
- node . setAttribute ( "rows" , 1 ) ;
410
+ element
411
+ . addClass ( 'md-no-flex' )
412
+ . attr ( 'rows' , 1 ) ;
424
413
414
+ if ( minRows ) {
425
415
if ( ! lineHeight ) {
426
- node . style . minHeight = '0' ;
427
-
416
+ node . style . minHeight = 0 ;
428
417
lineHeight = element . prop ( 'clientHeight' ) ;
429
-
430
418
node . style . minHeight = null ;
431
419
}
432
420
433
- var rows = Math . min ( min_rows , Math . round ( node . scrollHeight / lineHeight ) ) ;
434
- node . setAttribute ( "rows" , rows ) ;
435
- node . style . height = lineHeight * rows + "px" ;
421
+ var newRows = Math . round ( Math . round ( getHeight ( ) / lineHeight ) ) ;
422
+ var rowsToSet = Math . min ( newRows , minRows ) ;
423
+
424
+ element
425
+ . css ( 'height' , lineHeight * rowsToSet + 'px' )
426
+ . attr ( 'rows' , rowsToSet )
427
+ . toggleClass ( '_md-textarea-scrollable' , newRows >= minRows ) ;
428
+
429
+ } else {
430
+ element . css ( 'height' , 'auto' ) ;
431
+ node . scrollTop = 0 ;
432
+ var height = getHeight ( ) ;
433
+ if ( height ) element . css ( 'height' , height + 'px' ) ;
436
434
}
437
435
438
- // reset everything back to normal
439
436
element . removeClass ( 'md-no-flex' ) ;
440
- container . style . height = 'auto' ;
441
437
}
442
438
443
439
function getHeight ( ) {
444
- var line = node . scrollHeight - node . offsetHeight ;
445
- return node . offsetHeight + ( line > 0 ? line : 0 ) ;
440
+ var offsetHeight = node . offsetHeight ;
441
+ var line = node . scrollHeight - offsetHeight ;
442
+ return offsetHeight + ( line > 0 ? line : 0 ) ;
446
443
}
447
444
448
445
function onScroll ( e ) {
@@ -453,8 +450,13 @@ function inputTextareaDirective($mdUtil, $window, $mdAria) {
453
450
node . style . height = height + 'px' ;
454
451
}
455
452
453
+ function pipelineListener ( value ) {
454
+ growTextarea ( ) ;
455
+ return value ;
456
+ }
457
+
456
458
// Attach a watcher to detect when the textarea gets shown.
457
- if ( angular . isDefined ( element . attr ( 'md-detect-hidden' ) ) ) {
459
+ if ( attr . hasOwnProperty ( 'mdDetectHidden' ) ) {
458
460
459
461
var handleHiddenChange = function ( ) {
460
462
var wasHidden = false ;
@@ -616,7 +618,7 @@ function placeholderDirective($log) {
616
618
*
617
619
* </hljs>
618
620
*/
619
- function mdSelectOnFocusDirective ( ) {
621
+ function mdSelectOnFocusDirective ( $timeout ) {
620
622
621
623
return {
622
624
restrict : 'A' ,
@@ -626,15 +628,40 @@ function mdSelectOnFocusDirective() {
626
628
function postLink ( scope , element , attr ) {
627
629
if ( element [ 0 ] . nodeName !== 'INPUT' && element [ 0 ] . nodeName !== "TEXTAREA" ) return ;
628
630
629
- element . on ( 'focus' , onFocus ) ;
631
+ var preventMouseUp = false ;
632
+
633
+ element
634
+ . on ( 'focus' , onFocus )
635
+ . on ( 'mouseup' , onMouseUp ) ;
630
636
631
637
scope . $on ( '$destroy' , function ( ) {
632
- element . off ( 'focus' , onFocus ) ;
638
+ element
639
+ . off ( 'focus' , onFocus )
640
+ . off ( 'mouseup' , onMouseUp ) ;
633
641
} ) ;
634
642
635
643
function onFocus ( ) {
636
- // Use HTMLInputElement#select to fix firefox select issues
637
- element [ 0 ] . select ( ) ;
644
+ preventMouseUp = true ;
645
+
646
+ $timeout ( function ( ) {
647
+ // Use HTMLInputElement#select to fix firefox select issues.
648
+ // The debounce is here for Edge's sake, otherwise the selection doesn't work.
649
+ element [ 0 ] . select ( ) ;
650
+
651
+ // This should be reset from inside the `focus`, because the event might
652
+ // have originated from something different than a click, e.g. a keyboard event.
653
+ preventMouseUp = false ;
654
+ } , 1 , false ) ;
655
+ }
656
+
657
+ // Prevents the default action of the first `mouseup` after a focus.
658
+ // This is necessary, because browsers fire a `mouseup` right after the element
659
+ // has been focused. In some browsers (Firefox in particular) this can clear the
660
+ // selection. There are examples of the problem in issue #7487.
661
+ function onMouseUp ( event ) {
662
+ if ( preventMouseUp ) {
663
+ event . preventDefault ( ) ;
664
+ }
638
665
}
639
666
}
640
667
}
@@ -706,7 +733,7 @@ function mdInputInvalidMessagesAnimation($q, $animateCss) {
706
733
}
707
734
708
735
// NOTE: We do not need the removeClass method, because the message ng-leave animation will fire
709
- }
736
+ } ;
710
737
}
711
738
712
739
function ngMessagesAnimation ( $q , $animateCss ) {
0 commit comments