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