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