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