arnaucube
arnaucube/commonroutesAppPublic Self-hosted repository
commonroutesApp/www/lib/angular-material/modules/js/input/input.js
www/lib/angular-material/modules/js/input/input.jsRaw
   1/*!
   2 * Angular Material Design
   3 * https://github.com/angular/material
   4 * @license MIT
   5 * v1.1.1
   6 */
   7(function( window, angular, undefined ){
   8"use strict";
   9
  10/**
  11 * @ngdoc module
  12 * @name material.components.input
  13 */
  14mdInputContainerDirective.$inject = ["$mdTheming", "$parse"];
  15inputTextareaDirective.$inject = ["$mdUtil", "$window", "$mdAria", "$timeout", "$mdGesture"];
  16mdMaxlengthDirective.$inject = ["$animate", "$mdUtil"];
  17placeholderDirective.$inject = ["$compile"];
  18ngMessageDirective.$inject = ["$mdUtil"];
  19mdSelectOnFocusDirective.$inject = ["$timeout"];
  20mdInputInvalidMessagesAnimation.$inject = ["$$AnimateRunner", "$animateCss", "$mdUtil"];
  21ngMessagesAnimation.$inject = ["$$AnimateRunner", "$animateCss", "$mdUtil"];
  22ngMessageAnimation.$inject = ["$$AnimateRunner", "$animateCss", "$mdUtil"];
  23angular.module('material.components.input', [
  24    'material.core'
  25  ])
  26  .directive('mdInputContainer', mdInputContainerDirective)
  27  .directive('label', labelDirective)
  28  .directive('input', inputTextareaDirective)
  29  .directive('textarea', inputTextareaDirective)
  30  .directive('mdMaxlength', mdMaxlengthDirective)
  31  .directive('placeholder', placeholderDirective)
  32  .directive('ngMessages', ngMessagesDirective)
  33  .directive('ngMessage', ngMessageDirective)
  34  .directive('ngMessageExp', ngMessageDirective)
  35  .directive('mdSelectOnFocus', mdSelectOnFocusDirective)
  36
  37  .animation('.md-input-invalid', mdInputInvalidMessagesAnimation)
  38  .animation('.md-input-messages-animation', ngMessagesAnimation)
  39  .animation('.md-input-message-animation', ngMessageAnimation)
  40
  41  // Register a service for each animation so that we can easily inject them into unit tests
  42  .service('mdInputInvalidAnimation', mdInputInvalidMessagesAnimation)
  43  .service('mdInputMessagesAnimation', ngMessagesAnimation)
  44  .service('mdInputMessageAnimation', ngMessageAnimation);
  45
  46/**
  47 * @ngdoc directive
  48 * @name mdInputContainer
  49 * @module material.components.input
  50 *
  51 * @restrict E
  52 *
  53 * @description
  54 * `<md-input-container>` is the parent of any input or textarea element.
  55 *
  56 * Input and textarea elements will not behave properly unless the md-input-container
  57 * parent is provided.
  58 *
  59 * A single `<md-input-container>` should contain only one `<input>` element, otherwise it will throw an error.
  60 *
  61 * <b>Exception:</b> Hidden inputs (`<input type="hidden" />`) are ignored and will not throw an error, so
  62 * you may combine these with other inputs.
  63 *
  64 * <b>Note:</b> When using `ngMessages` with your input element, make sure the message and container elements
  65 * are *block* elements, otherwise animations applied to the messages will not look as intended. Either use a `div` and
  66 * apply the `ng-message` and `ng-messages` classes respectively, or use the `md-block` class on your element.
  67 *
  68 * @param md-is-error {expression=} When the given expression evaluates to true, the input container
  69 *   will go into error state. Defaults to erroring if the input has been touched and is invalid.
  70 * @param md-no-float {boolean=} When present, `placeholder` attributes on the input will not be converted to floating
  71 *   labels.
  72 *
  73 * @usage
  74 * <hljs lang="html">
  75 *
  76 * <md-input-container>
  77 *   <label>Username</label>
  78 *   <input type="text" ng-model="user.name">
  79 * </md-input-container>
  80 *
  81 * <md-input-container>
  82 *   <label>Description</label>
  83 *   <textarea ng-model="user.description"></textarea>
  84 * </md-input-container>
  85 *
  86 * </hljs>
  87 *
  88 * <h3>When disabling floating labels</h3>
  89 * <hljs lang="html">
  90 *
  91 * <md-input-container md-no-float>
  92 *   <input type="text" placeholder="Non-Floating Label">
  93 * </md-input-container>
  94 *
  95 * </hljs>
  96 */
  97function mdInputContainerDirective($mdTheming, $parse) {
  98
  99  ContainerCtrl.$inject = ["$scope", "$element", "$attrs", "$animate"];
 100  var INPUT_TAGS = ['INPUT', 'TEXTAREA', 'SELECT', 'MD-SELECT'];
 101
 102  var LEFT_SELECTORS = INPUT_TAGS.reduce(function(selectors, isel) {
 103    return selectors.concat(['md-icon ~ ' + isel, '.md-icon ~ ' + isel]);
 104  }, []).join(",");
 105
 106  var RIGHT_SELECTORS = INPUT_TAGS.reduce(function(selectors, isel) {
 107    return selectors.concat([isel + ' ~ md-icon', isel + ' ~ .md-icon']);
 108  }, []).join(",");
 109
 110  return {
 111    restrict: 'E',
 112    compile: compile,
 113    controller: ContainerCtrl
 114  };
 115
 116  function compile(tElement) {
 117    // Check for both a left & right icon
 118    var leftIcon = tElement[0].querySelector(LEFT_SELECTORS);
 119    var rightIcon = tElement[0].querySelector(RIGHT_SELECTORS);
 120
 121    if (leftIcon) { tElement.addClass('md-icon-left'); }
 122    if (rightIcon) { tElement.addClass('md-icon-right'); }
 123
 124    return function postLink(scope, element) {
 125      $mdTheming(element);
 126    };
 127  }
 128
 129  function ContainerCtrl($scope, $element, $attrs, $animate) {
 130    var self = this;
 131
 132    self.isErrorGetter = $attrs.mdIsError && $parse($attrs.mdIsError);
 133
 134    self.delegateClick = function() {
 135      self.input.focus();
 136    };
 137    self.element = $element;
 138    self.setFocused = function(isFocused) {
 139      $element.toggleClass('md-input-focused', !!isFocused);
 140    };
 141    self.setHasValue = function(hasValue) {
 142      $element.toggleClass('md-input-has-value', !!hasValue);
 143    };
 144    self.setHasPlaceholder = function(hasPlaceholder) {
 145      $element.toggleClass('md-input-has-placeholder', !!hasPlaceholder);
 146    };
 147    self.setInvalid = function(isInvalid) {
 148      if (isInvalid) {
 149        $animate.addClass($element, 'md-input-invalid');
 150      } else {
 151        $animate.removeClass($element, 'md-input-invalid');
 152      }
 153    };
 154    $scope.$watch(function() {
 155      return self.label && self.input;
 156    }, function(hasLabelAndInput) {
 157      if (hasLabelAndInput && !self.label.attr('for')) {
 158        self.label.attr('for', self.input.attr('id'));
 159      }
 160    });
 161  }
 162}
 163
 164function labelDirective() {
 165  return {
 166    restrict: 'E',
 167    require: '^?mdInputContainer',
 168    link: function(scope, element, attr, containerCtrl) {
 169      if (!containerCtrl || attr.mdNoFloat || element.hasClass('md-container-ignore')) return;
 170
 171      containerCtrl.label = element;
 172      scope.$on('$destroy', function() {
 173        containerCtrl.label = null;
 174      });
 175    }
 176  };
 177}
 178
 179/**
 180 * @ngdoc directive
 181 * @name mdInput
 182 * @restrict E
 183 * @module material.components.input
 184 *
 185 * @description
 186 * You can use any `<input>` or `<textarea>` element as a child of an `<md-input-container>`. This
 187 * allows you to build complex forms for data entry.
 188 *
 189 * When the input is required and uses a floating label, then the label will automatically contain
 190 * an asterisk (`*`).<br/>
 191 * This behavior can be disabled by using the `md-no-asterisk` attribute.
 192 *
 193 * @param {number=} md-maxlength The maximum number of characters allowed in this input. If this is
 194 *   specified, a character counter will be shown underneath the input.<br/><br/>
 195 *   The purpose of **`md-maxlength`** is exactly to show the max length counter text. If you don't
 196 *   want the counter text and only need "plain" validation, you can use the "simple" `ng-maxlength`
 197 *   or maxlength attributes.<br/><br/>
 198 *   **Note:** Only valid for text/string inputs (not numeric).
 199 *
 200 * @param {string=} aria-label Aria-label is required when no label is present.  A warning message
 201 *   will be logged in the console if not present.
 202 * @param {string=} placeholder An alternative approach to using aria-label when the label is not
 203 *   PRESENT. The placeholder text is copied to the aria-label attribute.
 204 * @param md-no-autogrow {boolean=} When present, textareas will not grow automatically.
 205 * @param md-no-asterisk {boolean=} When present, an asterisk will not be appended to the inputs floating label
 206 * @param md-no-resize {boolean=} Disables the textarea resize handle.
 207 * @param {number=} max-rows The maximum amount of rows for a textarea.
 208 * @param md-detect-hidden {boolean=} When present, textareas will be sized properly when they are
 209 *   revealed after being hidden. This is off by default for performance reasons because it
 210 *   guarantees a reflow every digest cycle.
 211 *
 212 * @usage
 213 * <hljs lang="html">
 214 * <md-input-container>
 215 *   <label>Color</label>
 216 *   <input type="text" ng-model="color" required md-maxlength="10">
 217 * </md-input-container>
 218 * </hljs>
 219 *
 220 * <h3>With Errors</h3>
 221 *
 222 * `md-input-container` also supports errors using the standard `ng-messages` directives and
 223 * animates the messages when they become visible using from the `ngEnter`/`ngLeave` events or
 224 * the `ngShow`/`ngHide` events.
 225 *
 226 * By default, the messages will be hidden until the input is in an error state. This is based off
 227 * of the `md-is-error` expression of the `md-input-container`. This gives the user a chance to
 228 * fill out the form before the errors become visible.
 229 *
 230 * <hljs lang="html">
 231 * <form name="colorForm">
 232 *   <md-input-container>
 233 *     <label>Favorite Color</label>
 234 *     <input name="favoriteColor" ng-model="favoriteColor" required>
 235 *     <div ng-messages="colorForm.favoriteColor.$error">
 236 *       <div ng-message="required">This is required!</div>
 237 *     </div>
 238 *   </md-input-container>
 239 * </form>
 240 * </hljs>
 241 *
 242 * We automatically disable this auto-hiding functionality if you provide any of the following
 243 * visibility directives on the `ng-messages` container:
 244 *
 245 *  - `ng-if`
 246 *  - `ng-show`/`ng-hide`
 247 *  - `ng-switch-when`/`ng-switch-default`
 248 *
 249 * You can also disable this functionality manually by adding the `md-auto-hide="false"` expression
 250 * to the `ng-messages` container. This may be helpful if you always want to see the error messages
 251 * or if you are building your own visibilty directive.
 252 *
 253 * _<b>Note:</b> The `md-auto-hide` attribute is a static string that is  only checked upon
 254 * initialization of the `ng-messages` directive to see if it equals the string `false`._
 255 *
 256 * <hljs lang="html">
 257 * <form name="userForm">
 258 *   <md-input-container>
 259 *     <label>Last Name</label>
 260 *     <input name="lastName" ng-model="lastName" required md-maxlength="10" minlength="4">
 261 *     <div ng-messages="userForm.lastName.$error" ng-show="userForm.lastName.$dirty">
 262 *       <div ng-message="required">This is required!</div>
 263 *       <div ng-message="md-maxlength">That's too long!</div>
 264 *       <div ng-message="minlength">That's too short!</div>
 265 *     </div>
 266 *   </md-input-container>
 267 *   <md-input-container>
 268 *     <label>Biography</label>
 269 *     <textarea name="bio" ng-model="biography" required md-maxlength="150"></textarea>
 270 *     <div ng-messages="userForm.bio.$error" ng-show="userForm.bio.$dirty">
 271 *       <div ng-message="required">This is required!</div>
 272 *       <div ng-message="md-maxlength">That's too long!</div>
 273 *     </div>
 274 *   </md-input-container>
 275 *   <md-input-container>
 276 *     <input aria-label='title' ng-model='title'>
 277 *   </md-input-container>
 278 *   <md-input-container>
 279 *     <input placeholder='title' ng-model='title'>
 280 *   </md-input-container>
 281 * </form>
 282 * </hljs>
 283 *
 284 * <h3>Notes</h3>
 285 *
 286 * - Requires [ngMessages](https://docs.angularjs.org/api/ngMessages).
 287 * - Behaves like the [AngularJS input directive](https://docs.angularjs.org/api/ng/directive/input).
 288 *
 289 * The `md-input` and `md-input-container` directives use very specific positioning to achieve the
 290 * error animation effects. Therefore, it is *not* advised to use the Layout system inside of the
 291 * `<md-input-container>` tags. Instead, use relative or absolute positioning.
 292 *
 293 *
 294 * <h3>Textarea directive</h3>
 295 * The `textarea` element within a `md-input-container` has the following specific behavior:
 296 * - By default the `textarea` grows as the user types. This can be disabled via the `md-no-autogrow`
 297 * attribute.
 298 * - If a `textarea` has the `rows` attribute, it will treat the `rows` as the minimum height and will
 299 * continue growing as the user types. For example a textarea with `rows="3"` will be 3 lines of text
 300 * high initially. If no rows are specified, the directive defaults to 1.
 301 * - The textarea's height gets set on initialization, as well as while the user is typing. In certain situations
 302 * (e.g. while animating) the directive might have been initialized, before the element got it's final height. In
 303 * those cases, you can trigger a resize manually by broadcasting a `md-resize-textarea` event on the scope.
 304 * - If you wan't a `textarea` to stop growing at a certain point, you can specify the `max-rows` attribute.
 305 * - The textarea's bottom border acts as a handle which users can drag, in order to resize the element vertically.
 306 * Once the user has resized a `textarea`, the autogrowing functionality becomes disabled. If you don't want a
 307 * `textarea` to be resizeable by the user, you can add the `md-no-resize` attribute.
 308 */
 309
 310function inputTextareaDirective($mdUtil, $window, $mdAria, $timeout, $mdGesture) {
 311  return {
 312    restrict: 'E',
 313    require: ['^?mdInputContainer', '?ngModel', '?^form'],
 314    link: postLink
 315  };
 316
 317  function postLink(scope, element, attr, ctrls) {
 318
 319    var containerCtrl = ctrls[0];
 320    var hasNgModel = !!ctrls[1];
 321    var ngModelCtrl = ctrls[1] || $mdUtil.fakeNgModel();
 322    var parentForm = ctrls[2];
 323    var isReadonly = angular.isDefined(attr.readonly);
 324    var mdNoAsterisk = $mdUtil.parseAttributeBoolean(attr.mdNoAsterisk);
 325    var tagName = element[0].tagName.toLowerCase();
 326
 327
 328    if (!containerCtrl) return;
 329    if (attr.type === 'hidden') {
 330      element.attr('aria-hidden', 'true');
 331      return;
 332    } else if (containerCtrl.input) {
 333      if (containerCtrl.input[0].contains(element[0])) {
 334        return;
 335      } else {
 336        throw new Error("<md-input-container> can only have *one* <input>, <textarea> or <md-select> child element!");
 337      }
 338    }
 339    containerCtrl.input = element;
 340
 341    setupAttributeWatchers();
 342
 343    // Add an error spacer div after our input to provide space for the char counter and any ng-messages
 344    var errorsSpacer = angular.element('<div class="md-errors-spacer">');
 345    element.after(errorsSpacer);
 346
 347    if (!containerCtrl.label) {
 348      $mdAria.expect(element, 'aria-label', attr.placeholder);
 349    }
 350
 351    element.addClass('md-input');
 352    if (!element.attr('id')) {
 353      element.attr('id', 'input_' + $mdUtil.nextUid());
 354    }
 355
 356    // This works around a Webkit issue where number inputs, placed in a flexbox, that have
 357    // a `min` and `max` will collapse to about 1/3 of their proper width. Please check #7349
 358    // for more info. Also note that we don't override the `step` if the user has specified it,
 359    // in order to prevent some unexpected behaviour.
 360    if (tagName === 'input' && attr.type === 'number' && attr.min && attr.max && !attr.step) {
 361      element.attr('step', 'any');
 362    } else if (tagName === 'textarea') {
 363      setupTextarea();
 364    }
 365
 366    // If the input doesn't have an ngModel, it may have a static value. For that case,
 367    // we have to do one initial check to determine if the container should be in the
 368    // "has a value" state.
 369    if (!hasNgModel) {
 370      inputCheckValue();
 371    }
 372
 373    var isErrorGetter = containerCtrl.isErrorGetter || function() {
 374      return ngModelCtrl.$invalid && (ngModelCtrl.$touched || (parentForm && parentForm.$submitted));
 375    };
 376
 377    scope.$watch(isErrorGetter, containerCtrl.setInvalid);
 378
 379    // When the developer uses the ngValue directive for the input, we have to observe the attribute, because
 380    // Angular's ngValue directive is just setting the `value` attribute.
 381    if (attr.ngValue) {
 382      attr.$observe('value', inputCheckValue);
 383    }
 384
 385    ngModelCtrl.$parsers.push(ngModelPipelineCheckValue);
 386    ngModelCtrl.$formatters.push(ngModelPipelineCheckValue);
 387
 388    element.on('input', inputCheckValue);
 389
 390    if (!isReadonly) {
 391      element
 392        .on('focus', function(ev) {
 393          $mdUtil.nextTick(function() {
 394            containerCtrl.setFocused(true);
 395          });
 396        })
 397        .on('blur', function(ev) {
 398          $mdUtil.nextTick(function() {
 399            containerCtrl.setFocused(false);
 400            inputCheckValue();
 401          });
 402        });
 403    }
 404
 405    scope.$on('$destroy', function() {
 406      containerCtrl.setFocused(false);
 407      containerCtrl.setHasValue(false);
 408      containerCtrl.input = null;
 409    });
 410
 411    /** Gets run through ngModel's pipeline and set the `has-value` class on the container. */
 412    function ngModelPipelineCheckValue(arg) {
 413      containerCtrl.setHasValue(!ngModelCtrl.$isEmpty(arg));
 414      return arg;
 415    }
 416
 417    function setupAttributeWatchers() {
 418      if (containerCtrl.label) {
 419        attr.$observe('required', function (value) {
 420          // We don't need to parse the required value, it's always a boolean because of angular's
 421          // required directive.
 422          containerCtrl.label.toggleClass('md-required', value && !mdNoAsterisk);
 423        });
 424      }
 425    }
 426
 427    function inputCheckValue() {
 428      // An input's value counts if its length > 0,
 429      // or if the input's validity state says it has bad input (eg string in a number input)
 430      containerCtrl.setHasValue(element.val().length > 0 || (element[0].validity || {}).badInput);
 431    }
 432
 433    function setupTextarea() {
 434      var isAutogrowing = !attr.hasOwnProperty('mdNoAutogrow');
 435
 436      attachResizeHandle();
 437
 438      if (!isAutogrowing) return;
 439
 440      // Can't check if height was or not explicity set,
 441      // so rows attribute will take precedence if present
 442      var minRows = attr.hasOwnProperty('rows') ? parseInt(attr.rows) : NaN;
 443      var maxRows = attr.hasOwnProperty('maxRows') ? parseInt(attr.maxRows) : NaN;
 444      var scopeResizeListener = scope.$on('md-resize-textarea', growTextarea);
 445      var lineHeight = null;
 446      var node = element[0];
 447
 448      // This timeout is necessary, because the browser needs a little bit
 449      // of time to calculate the `clientHeight` and `scrollHeight`.
 450      $timeout(function() {
 451        $mdUtil.nextTick(growTextarea);
 452      }, 10, false);
 453
 454      // We could leverage ngModel's $parsers here, however it
 455      // isn't reliable, because Angular trims the input by default,
 456      // which means that growTextarea won't fire when newlines and
 457      // spaces are added.
 458      element.on('input', growTextarea);
 459
 460      // We should still use the $formatters, because they fire when
 461      // the value was changed from outside the textarea.
 462      if (hasNgModel) {
 463        ngModelCtrl.$formatters.push(formattersListener);
 464      }
 465
 466      if (!minRows) {
 467        element.attr('rows', 1);
 468      }
 469
 470      angular.element($window).on('resize', growTextarea);
 471      scope.$on('$destroy', disableAutogrow);
 472
 473      function growTextarea() {
 474        // temporarily disables element's flex so its height 'runs free'
 475        element
 476          .attr('rows', 1)
 477          .css('height', 'auto')
 478          .addClass('md-no-flex');
 479
 480        var height = getHeight();
 481
 482        if (!lineHeight) {
 483          // offsetHeight includes padding which can throw off our value
 484          var originalPadding = element[0].style.padding || '';
 485          lineHeight = element.css('padding', 0).prop('offsetHeight');
 486          element[0].style.padding = originalPadding;
 487        }
 488
 489        if (minRows && lineHeight) {
 490          height = Math.max(height, lineHeight * minRows);
 491        }
 492
 493        if (maxRows && lineHeight) {
 494          var maxHeight = lineHeight * maxRows;
 495
 496          if (maxHeight < height) {
 497            element.attr('md-no-autogrow', '');
 498            height = maxHeight;
 499          } else {
 500            element.removeAttr('md-no-autogrow');
 501          }
 502        }
 503
 504        if (lineHeight) {
 505          element.attr('rows', Math.round(height / lineHeight));
 506        }
 507
 508        element
 509          .css('height', height + 'px')
 510          .removeClass('md-no-flex');
 511      }
 512
 513      function getHeight() {
 514        var offsetHeight = node.offsetHeight;
 515        var line = node.scrollHeight - offsetHeight;
 516        return offsetHeight + Math.max(line, 0);
 517      }
 518
 519      function formattersListener(value) {
 520        $mdUtil.nextTick(growTextarea);
 521        return value;
 522      }
 523
 524      function disableAutogrow() {
 525        if (!isAutogrowing) return;
 526
 527        isAutogrowing = false;
 528        angular.element($window).off('resize', growTextarea);
 529        scopeResizeListener && scopeResizeListener();
 530        element
 531          .attr('md-no-autogrow', '')
 532          .off('input', growTextarea);
 533
 534        if (hasNgModel) {
 535          var listenerIndex = ngModelCtrl.$formatters.indexOf(formattersListener);
 536
 537          if (listenerIndex > -1) {
 538            ngModelCtrl.$formatters.splice(listenerIndex, 1);
 539          }
 540        }
 541      }
 542
 543      function attachResizeHandle() {
 544        if (attr.hasOwnProperty('mdNoResize')) return;
 545
 546        var handle = angular.element('<div class="md-resize-handle"></div>');
 547        var isDragging = false;
 548        var dragStart = null;
 549        var startHeight = 0;
 550        var container = containerCtrl.element;
 551        var dragGestureHandler = $mdGesture.register(handle, 'drag', { horizontal: false });
 552
 553
 554        element.wrap('<div class="md-resize-wrapper">').after(handle);
 555        handle.on('mousedown', onMouseDown);
 556
 557        container
 558          .on('$md.dragstart', onDragStart)
 559          .on('$md.drag', onDrag)
 560          .on('$md.dragend', onDragEnd);
 561
 562        scope.$on('$destroy', function() {
 563          handle
 564            .off('mousedown', onMouseDown)
 565            .remove();
 566
 567          container
 568            .off('$md.dragstart', onDragStart)
 569            .off('$md.drag', onDrag)
 570            .off('$md.dragend', onDragEnd);
 571
 572          dragGestureHandler();
 573          handle = null;
 574          container = null;
 575          dragGestureHandler = null;
 576        });
 577
 578        function onMouseDown(ev) {
 579          ev.preventDefault();
 580          isDragging = true;
 581          dragStart = ev.clientY;
 582          startHeight = parseFloat(element.css('height')) || element.prop('offsetHeight');
 583        }
 584
 585        function onDragStart(ev) {
 586          if (!isDragging) return;
 587          ev.preventDefault();
 588          disableAutogrow();
 589          container.addClass('md-input-resized');
 590        }
 591
 592        function onDrag(ev) {
 593          if (!isDragging) return;
 594          element.css('height', startHeight + (ev.pointer.y - dragStart) - $mdUtil.scrollTop() + 'px');
 595        }
 596
 597        function onDragEnd(ev) {
 598          if (!isDragging) return;
 599          isDragging = false;
 600          container.removeClass('md-input-resized');
 601        }
 602      }
 603
 604      // Attach a watcher to detect when the textarea gets shown.
 605      if (attr.hasOwnProperty('mdDetectHidden')) {
 606
 607        var handleHiddenChange = function() {
 608          var wasHidden = false;
 609
 610          return function() {
 611            var isHidden = node.offsetHeight === 0;
 612
 613            if (isHidden === false && wasHidden === true) {
 614              growTextarea();
 615            }
 616
 617            wasHidden = isHidden;
 618          };
 619        }();
 620
 621        // Check every digest cycle whether the visibility of the textarea has changed.
 622        // Queue up to run after the digest cycle is complete.
 623        scope.$watch(function() {
 624          $mdUtil.nextTick(handleHiddenChange, false);
 625          return true;
 626        });
 627      }
 628    }
 629  }
 630}
 631
 632function mdMaxlengthDirective($animate, $mdUtil) {
 633  return {
 634    restrict: 'A',
 635    require: ['ngModel', '^mdInputContainer'],
 636    link: postLink
 637  };
 638
 639  function postLink(scope, element, attr, ctrls) {
 640    var maxlength;
 641    var ngModelCtrl = ctrls[0];
 642    var containerCtrl = ctrls[1];
 643    var charCountEl, errorsSpacer;
 644
 645    // Wait until the next tick to ensure that the input has setup the errors spacer where we will
 646    // append our counter
 647    $mdUtil.nextTick(function() {
 648      errorsSpacer = angular.element(containerCtrl.element[0].querySelector('.md-errors-spacer'));
 649      charCountEl = angular.element('<div class="md-char-counter">');
 650
 651      // Append our character counter inside the errors spacer
 652      errorsSpacer.append(charCountEl);
 653
 654      // Stop model from trimming. This makes it so whitespace
 655      // over the maxlength still counts as invalid.
 656      attr.$set('ngTrim', 'false');
 657
 658      ngModelCtrl.$formatters.push(renderCharCount);
 659      ngModelCtrl.$viewChangeListeners.push(renderCharCount);
 660      element.on('input keydown keyup', function() {
 661        renderCharCount(); //make sure it's called with no args
 662      });
 663
 664      scope.$watch(attr.mdMaxlength, function(value) {
 665        maxlength = value;
 666        if (angular.isNumber(value) && value > 0) {
 667          if (!charCountEl.parent().length) {
 668            $animate.enter(charCountEl, errorsSpacer);
 669          }
 670          renderCharCount();
 671        } else {
 672          $animate.leave(charCountEl);
 673        }
 674      });
 675
 676      ngModelCtrl.$validators['md-maxlength'] = function(modelValue, viewValue) {
 677        if (!angular.isNumber(maxlength) || maxlength < 0) {
 678          return true;
 679        }
 680        return ( modelValue || element.val() || viewValue || '' ).length <= maxlength;
 681      };
 682    });
 683
 684    function renderCharCount(value) {
 685      // If we have not been appended to the body yet; do not render
 686      if (!charCountEl.parent) {
 687        return value;
 688      }
 689
 690      // Force the value into a string since it may be a number,
 691      // which does not have a length property.
 692      charCountEl.text(String(element.val() || value || '').length + ' / ' + maxlength);
 693      return value;
 694    }
 695  }
 696}
 697
 698function placeholderDirective($compile) {
 699  return {
 700    restrict: 'A',
 701    require: '^^?mdInputContainer',
 702    priority: 200,
 703    link: {
 704      // Note that we need to do this in the pre-link, as opposed to the post link, if we want to
 705      // support data bindings in the placeholder. This is necessary, because we have a case where
 706      // we transfer the placeholder value to the `<label>` and we remove it from the original `<input>`.
 707      // If we did this in the post-link, Angular would have set up the observers already and would be
 708      // re-adding the attribute, even though we removed it from the element.
 709      pre: preLink
 710    }
 711  };
 712
 713  function preLink(scope, element, attr, inputContainer) {
 714    // If there is no input container, just return
 715    if (!inputContainer) return;
 716
 717    var label = inputContainer.element.find('label');
 718    var noFloat = inputContainer.element.attr('md-no-float');
 719
 720    // If we have a label, or they specify the md-no-float attribute, just return
 721    if ((label && label.length) || noFloat === '' || scope.$eval(noFloat)) {
 722      // Add a placeholder class so we can target it in the CSS
 723      inputContainer.setHasPlaceholder(true);
 724      return;
 725    }
 726
 727    // md-select handles placeholders on it's own
 728    if (element[0].nodeName != 'MD-SELECT') {
 729      // Move the placeholder expression to the label
 730      var newLabel = angular.element('<label ng-click="delegateClick()" tabindex="-1">' + attr.placeholder + '</label>');
 731
 732      // Note that we unset it via `attr`, in order to get Angular
 733      // to remove any observers that it might have set up. Otherwise
 734      // the attribute will be added on the next digest.
 735      attr.$set('placeholder', null);
 736
 737      // We need to compile the label manually in case it has any bindings.
 738      // A gotcha here is that we first add the element to the DOM and we compile
 739      // it later. This is necessary, because if we compile the element beforehand,
 740      // it won't be able to find the `mdInputContainer` controller.
 741      inputContainer.element
 742        .addClass('md-icon-float')
 743        .prepend(newLabel);
 744
 745      $compile(newLabel)(scope);
 746    }
 747  }
 748}
 749
 750/**
 751 * @ngdoc directive
 752 * @name mdSelectOnFocus
 753 * @module material.components.input
 754 *
 755 * @restrict A
 756 *
 757 * @description
 758 * The `md-select-on-focus` directive allows you to automatically select the element's input text on focus.
 759 *
 760 * <h3>Notes</h3>
 761 * - The use of `md-select-on-focus` is restricted to `<input>` and `<textarea>` elements.
 762 *
 763 * @usage
 764 * <h3>Using with an Input</h3>
 765 * <hljs lang="html">
 766 *
 767 * <md-input-container>
 768 *   <label>Auto Select</label>
 769 *   <input type="text" md-select-on-focus>
 770 * </md-input-container>
 771 * </hljs>
 772 *
 773 * <h3>Using with a Textarea</h3>
 774 * <hljs lang="html">
 775 *
 776 * <md-input-container>
 777 *   <label>Auto Select</label>
 778 *   <textarea md-select-on-focus>This text will be selected on focus.</textarea>
 779 * </md-input-container>
 780 *
 781 * </hljs>
 782 */
 783function mdSelectOnFocusDirective($timeout) {
 784
 785  return {
 786    restrict: 'A',
 787    link: postLink
 788  };
 789
 790  function postLink(scope, element, attr) {
 791    if (element[0].nodeName !== 'INPUT' && element[0].nodeName !== "TEXTAREA") return;
 792
 793    var preventMouseUp = false;
 794
 795    element
 796      .on('focus', onFocus)
 797      .on('mouseup', onMouseUp);
 798
 799    scope.$on('$destroy', function() {
 800      element
 801        .off('focus', onFocus)
 802        .off('mouseup', onMouseUp);
 803    });
 804
 805    function onFocus() {
 806      preventMouseUp = true;
 807
 808      $timeout(function() {
 809        // Use HTMLInputElement#select to fix firefox select issues.
 810        // The debounce is here for Edge's sake, otherwise the selection doesn't work.
 811        element[0].select();
 812
 813        // This should be reset from inside the `focus`, because the event might
 814        // have originated from something different than a click, e.g. a keyboard event.
 815        preventMouseUp = false;
 816      }, 1, false);
 817    }
 818
 819    // Prevents the default action of the first `mouseup` after a focus.
 820    // This is necessary, because browsers fire a `mouseup` right after the element
 821    // has been focused. In some browsers (Firefox in particular) this can clear the
 822    // selection. There are examples of the problem in issue #7487.
 823    function onMouseUp(event) {
 824      if (preventMouseUp) {
 825        event.preventDefault();
 826      }
 827    }
 828  }
 829}
 830
 831var visibilityDirectives = ['ngIf', 'ngShow', 'ngHide', 'ngSwitchWhen', 'ngSwitchDefault'];
 832function ngMessagesDirective() {
 833  return {
 834    restrict: 'EA',
 835    link: postLink,
 836
 837    // This is optional because we don't want target *all* ngMessage instances, just those inside of
 838    // mdInputContainer.
 839    require: '^^?mdInputContainer'
 840  };
 841
 842  function postLink(scope, element, attrs, inputContainer) {
 843    // If we are not a child of an input container, don't do anything
 844    if (!inputContainer) return;
 845
 846    // Add our animation class
 847    element.toggleClass('md-input-messages-animation', true);
 848
 849    // Add our md-auto-hide class to automatically hide/show messages when container is invalid
 850    element.toggleClass('md-auto-hide', true);
 851
 852    // If we see some known visibility directives, remove the md-auto-hide class
 853    if (attrs.mdAutoHide == 'false' || hasVisibiltyDirective(attrs)) {
 854      element.toggleClass('md-auto-hide', false);
 855    }
 856  }
 857
 858  function hasVisibiltyDirective(attrs) {
 859    return visibilityDirectives.some(function(attr) {
 860      return attrs[attr];
 861    });
 862  }
 863}
 864
 865function ngMessageDirective($mdUtil) {
 866  return {
 867    restrict: 'EA',
 868    compile: compile,
 869    priority: 100
 870  };
 871
 872  function compile(tElement) {
 873    if (!isInsideInputContainer(tElement)) {
 874
 875      // When the current element is inside of a document fragment, then we need to check for an input-container
 876      // in the postLink, because the element will be later added to the DOM and is currently just in a temporary
 877      // fragment, which causes the input-container check to fail.
 878      if (isInsideFragment()) {
 879        return function (scope, element) {
 880          if (isInsideInputContainer(element)) {
 881            // Inside of the postLink function, a ngMessage directive will be a comment element, because it's
 882            // currently hidden. To access the shown element, we need to use the element from the compile function.
 883            initMessageElement(tElement);
 884          }
 885        };
 886      }
 887    } else {
 888      initMessageElement(tElement);
 889    }
 890
 891    function isInsideFragment() {
 892      var nextNode = tElement[0];
 893      while (nextNode = nextNode.parentNode) {
 894        if (nextNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
 895          return true;
 896        }
 897      }
 898      return false;
 899    }
 900
 901    function isInsideInputContainer(element) {
 902      return !!$mdUtil.getClosest(element, "md-input-container");
 903    }
 904
 905    function initMessageElement(element) {
 906      // Add our animation class
 907      element.toggleClass('md-input-message-animation', true);
 908    }
 909  }
 910}
 911
 912var $$AnimateRunner, $animateCss, $mdUtil;
 913
 914function mdInputInvalidMessagesAnimation($$AnimateRunner, $animateCss, $mdUtil) {
 915  saveSharedServices($$AnimateRunner, $animateCss, $mdUtil);
 916
 917  return {
 918    addClass: function(element, className, done) {
 919      showInputMessages(element, done);
 920    }
 921
 922    // NOTE: We do not need the removeClass method, because the message ng-leave animation will fire
 923  };
 924}
 925
 926function ngMessagesAnimation($$AnimateRunner, $animateCss, $mdUtil) {
 927  saveSharedServices($$AnimateRunner, $animateCss, $mdUtil);
 928
 929  return {
 930    enter: function(element, done) {
 931      showInputMessages(element, done);
 932    },
 933
 934    leave: function(element, done) {
 935      hideInputMessages(element, done);
 936    },
 937
 938    addClass: function(element, className, done) {
 939      if (className == "ng-hide") {
 940        hideInputMessages(element, done);
 941      } else {
 942        done();
 943      }
 944    },
 945
 946    removeClass: function(element, className, done) {
 947      if (className == "ng-hide") {
 948        showInputMessages(element, done);
 949      } else {
 950        done();
 951      }
 952    }
 953  }
 954}
 955
 956function ngMessageAnimation($$AnimateRunner, $animateCss, $mdUtil) {
 957  saveSharedServices($$AnimateRunner, $animateCss, $mdUtil);
 958
 959  return {
 960    enter: function(element, done) {
 961      var animator = showMessage(element);
 962
 963      animator.start().done(done);
 964    },
 965
 966    leave: function(element, done) {
 967      var animator = hideMessage(element);
 968
 969      animator.start().done(done);
 970    }
 971  }
 972}
 973
 974function showInputMessages(element, done) {
 975  var animators = [], animator;
 976  var messages = getMessagesElement(element);
 977
 978  angular.forEach(messages.children(), function(child) {
 979    animator = showMessage(angular.element(child));
 980
 981    animators.push(animator.start());
 982  });
 983
 984  $$AnimateRunner.all(animators, done);
 985}
 986
 987function hideInputMessages(element, done) {
 988  var animators = [], animator;
 989  var messages = getMessagesElement(element);
 990
 991  angular.forEach(messages.children(), function(child) {
 992    animator = hideMessage(angular.element(child));
 993
 994    animators.push(animator.start());
 995  });
 996
 997  $$AnimateRunner.all(animators, done);
 998}
 999
1000function showMessage(element) {
1001  var height = parseInt(window.getComputedStyle(element[0]).height);
1002  var topMargin = parseInt(window.getComputedStyle(element[0]).marginTop);
1003
1004  var messages = getMessagesElement(element);
1005  var container = getInputElement(element);
1006
1007  // Check to see if the message is already visible so we can skip
1008  var alreadyVisible = (topMargin > -height);
1009
1010  // If we have the md-auto-hide class, the md-input-invalid animation will fire, so we can skip
1011  if (alreadyVisible || (messages.hasClass('md-auto-hide') && !container.hasClass('md-input-invalid'))) {
1012    return $animateCss(element, {});
1013  }
1014
1015  return $animateCss(element, {
1016    event: 'enter',
1017    structural: true,
1018    from: {"opacity": 0, "margin-top": -height + "px"},
1019    to: {"opacity": 1, "margin-top": "0"},
1020    duration: 0.3
1021  });
1022}
1023
1024function hideMessage(element) {
1025  var height = element[0].offsetHeight;
1026  var styles = window.getComputedStyle(element[0]);
1027
1028  // If we are already hidden, just return an empty animation
1029  if (styles.opacity == 0) {
1030    return $animateCss(element, {});
1031  }
1032
1033  // Otherwise, animate
1034  return $animateCss(element, {
1035    event: 'leave',
1036    structural: true,
1037    from: {"opacity": 1, "margin-top": 0},
1038    to: {"opacity": 0, "margin-top": -height + "px"},
1039    duration: 0.3
1040  });
1041}
1042
1043function getInputElement(element) {
1044  var inputContainer = element.controller('mdInputContainer');
1045
1046  return inputContainer.element;
1047}
1048
1049function getMessagesElement(element) {
1050  // If we are a ng-message element, we need to traverse up the DOM tree
1051  if (element.hasClass('md-input-message-animation')) {
1052    return angular.element($mdUtil.getClosest(element, function(node) {
1053      return node.classList.contains('md-input-messages-animation');
1054    }));
1055  }
1056
1057  // Otherwise, we can traverse down
1058  return angular.element(element[0].querySelector('.md-input-messages-animation'));
1059}
1060
1061function saveSharedServices(_$$AnimateRunner_, _$animateCss_, _$mdUtil_) {
1062  $$AnimateRunner = _$$AnimateRunner_;
1063  $animateCss = _$animateCss_;
1064  $mdUtil = _$mdUtil_;
1065}
1066
1067})(window, window.angular);