From 3b1c66bd18e0a52ce7c9ab6333143a63f34e1275 Mon Sep 17 00:00:00 2001 From: Topher Fangio Date: Tue, 15 Sep 2015 12:12:07 -0500 Subject: [PATCH] fix(mdInput): Support multiple ng-messages simultaneously. Previously, multiple `ng-message`s would render on top of each other. Fix by altering CSS position and altering transition to support multiple messages (i.e. potentially varying height). Also some other small fixes to inputs/errors: * Fix number input widths in Firefox * Update errors demo messages to be more dynamic and show multiple errors * Update SCSS to allow `ng-message-exp` and associated `data-` and `x-` attributes * Add demo using `ng-message-exp` to show new SCSS styles being applied Fixes #2648. Fixes #1957. Fixes #1793. Closes #4647. Closes #4472. Closes #4008. > Should also close PRs #4472 and #4008. Thanks to @bopm and @iksose for the initial PRs! --- src/components/input/demoErrors/index.html | 39 +++++++++++++-- src/components/input/input-theme.scss | 10 ++-- src/components/input/input.js | 36 ++++++++++++-- src/components/input/input.scss | 56 +++++++++++++++++----- 4 files changed, 115 insertions(+), 26 deletions(-) diff --git a/src/components/input/demoErrors/index.html b/src/components/input/demoErrors/index.html index 4fc6b56862a..6c30968fde4 100644 --- a/src/components/input/demoErrors/index.html +++ b/src/components/input/demoErrors/index.html @@ -25,13 +25,42 @@

+ + + + +
+
+ Your email must be between 10 and 100 characters long and look like an e-mail address. +
+
+
+ - -
-
You've got to charge something! You can't just give away a Missile Defense System.
-
You should charge at least $800 an hour. This job is a big deal... if you mess up, everyone dies!
-
$5,000 an hour? That's a little ridiculous. I doubt event Bill Clinton could afford that.
+ + +
+
+ You've got to charge something! You can't just give away a Missile Defense + System. +
+ +
+ You should charge at least $800 an hour. This job is a big deal... if you mess up, + everyone dies! +
+ +
+ You should charge exactly $1,234. +
+ +
+ {{projectForm.rate.$viewValue | currency:"$":0}} an hour? That's a little ridiculous. I + doubt event Bill Clinton could afford that. +
diff --git a/src/components/input/input-theme.scss b/src/components/input/input-theme.scss index 0391025a9ca..1671c837da5 100644 --- a/src/components/input/input-theme.scss +++ b/src/components/input/input-theme.scss @@ -16,12 +16,13 @@ md-input-container.md-THEME_NAME-theme { color: '{{foreground-3}}'; } - ng-messages, - [ng-message], [data-ng-message], [x-ng-message] { - color: '{{warn-500}}' + ng-messages, [ng-messages], + ng-message, data-ng-message, x-ng-message, + [ng-message], [data-ng-message], [x-ng-message], + [ng-message-exp], [data-ng-message-exp], [x-ng-message-exp] { + color: '{{warn-500}}'; } - &:not(.md-input-invalid) { &.md-input-has-value { label { @@ -65,6 +66,7 @@ md-input-container.md-THEME_NAME-theme { } ng-message, data-ng-message, x-ng-message, [ng-message], [data-ng-message], [x-ng-message], + [ng-message-exp], [data-ng-message-exp], [x-ng-message-exp], .md-char-counter { color: '{{warn-500}}'; } diff --git a/src/components/input/input.js b/src/components/input/input.js index 8bab01bf501..67fb80b9e75 100644 --- a/src/components/input/input.js +++ b/src/components/input/input.js @@ -11,7 +11,8 @@ angular.module('material.components.input', [ .directive('input', inputTextareaDirective) .directive('textarea', inputTextareaDirective) .directive('mdMaxlength', mdMaxlengthDirective) - .directive('placeholder', placeholderDirective); + .directive('placeholder', placeholderDirective) + .directive('ngMessages', ngMessagesDirective); /** * @ngdoc directive @@ -69,6 +70,9 @@ function mdInputContainerDirective($mdTheming, $parse) { self.setHasValue = function(hasValue) { $element.toggleClass('md-input-has-value', !!hasValue); }; + self.setHasMessages = function(hasMessages) { + $element.toggleClass('md-input-has-messages', !!hasMessages); + }; self.setInvalid = function(isInvalid) { $element.toggleClass('md-input-invalid', !!isInvalid); }; @@ -341,11 +345,12 @@ function mdMaxlengthDirective($animate) { var ngModelCtrl = ctrls[0]; var containerCtrl = ctrls[1]; var charCountEl = angular.element('
'); + var input = angular.element(containerCtrl.element[0].querySelector('[md-maxlength]')); // Stop model from trimming. This makes it so whitespace // over the maxlength still counts as invalid. attr.$set('ngTrim', 'false'); - containerCtrl.element.append(charCountEl); + input.after(charCountEl); ngModelCtrl.$formatters.push(renderCharCount); ngModelCtrl.$viewChangeListeners.push(renderCharCount); @@ -357,8 +362,7 @@ function mdMaxlengthDirective($animate) { maxlength = value; if (angular.isNumber(value) && value > 0) { if (!charCountEl.parent().length) { - $animate.enter(charCountEl, containerCtrl.element, - angular.element(containerCtrl.element[0].lastElementChild)); + $animate.enter(charCountEl, containerCtrl.element, input); } renderCharCount(); } else { @@ -408,3 +412,27 @@ function placeholderDirective($log) { } } + +function ngMessagesDirective() { + return { + restrict: 'EA', + link: postLink, + + // This is optional because we don't want target *all* ngMessage instances, just those inside of + // mdInputContainer. + require: '^^?mdInputContainer' + }; + + function postLink(scope, element, attr, inputContainer) { + // If we are not a child of an input container, don't do anything + if (!inputContainer) return; + + // Tell our parent input container we have messages so we can set the proper classes + inputContainer.setHasMessages(true); + + // When destroyed, inform our input container + scope.$on('$destroy', function() { + inputContainer.setHasMessages(false); + }); + } +} diff --git a/src/components/input/input.scss b/src/components/input/input.scss index 7dc72fb026d..cbd9390eb1d 100644 --- a/src/components/input/input.scss +++ b/src/components/input/input.scss @@ -28,6 +28,12 @@ md-input-container { padding: $input-container-padding; padding-bottom: $input-container-padding + $input-error-height; + // When we have ng-messages, remove the input error height from our bottom padding, since the + // ng-messages wrapper has a min-height of 1 error (so we don't adjust height as often; see below) + &.md-input-has-messages { + padding-bottom: $input-container-padding; + } + > md-icon { position: absolute; top: 5px; @@ -143,6 +149,9 @@ md-input-container { -ms-flex-preferred-size: $input-line-height; //IE fix border-radius: 0; + // Fix number inputs in Firefox to be full-width + width: auto; + &:focus { outline: none; } @@ -156,45 +165,66 @@ md-input-container { } } + .md-char-counter { + position: absolute; + right: 0; + order: 3; + } + ng-messages, data-ng-messages, x-ng-messages, [ng-messages], [data-ng-messages], [x-ng-messages] { - order: 3; position: relative; + order: 4; + min-height: $input-error-height; } + ng-message, data-ng-message, x-ng-message, [ng-message], [data-ng-message], [x-ng-message], + [ng-message-exp], [data-ng-message-exp], [x-ng-message-exp], .md-char-counter { + $input-error-line-height: $input-error-font-size + 2px; //-webkit-font-smoothing: antialiased; - position: absolute; font-size: $input-error-font-size; - line-height: $input-error-height; + line-height: $input-error-line-height; + overflow: hidden; + + // Add some top padding which is equal to half the difference between the expected height + // and the actual height + $error-padding-top: ($input-error-height - $input-error-line-height) / 2; + padding-top: $error-padding-top; &:not(.md-char-counter) { - padding-right: rem(3); + padding-right: rem(5); } &.ng-enter { - transition: $swift-ease-out; - transition-delay: 0.2s; + transition: $swift-ease-in; + + // Delay the enter transition so it happens after the leave + transition-delay: $swift-ease-in-duration / 1.5; + + // Since we're delaying the transition, we speed up the duration a little bit to compensate + transition-duration: $swift-ease-in-duration / 1.5; } &.ng-leave { - transition: $swift-ease-in; + transition: $swift-ease-out; + + // Speed up the duration (see enter comment above) + transition-duration: $swift-ease-out-duration / 1.5; } &.ng-enter, &.ng-leave.ng-leave-active { + // Move the error upwards off the screen and fade it out + margin-top: -$input-error-line-height - $error-padding-top; opacity: 0; - transform: translate3d(0, -20%, 0); } &.ng-leave, &.ng-enter.ng-enter-active { + // Move the error down into position and fade it in + margin-top: 0; opacity: 1; - transform: translate3d(0, 0, 0); } } - .md-char-counter { - bottom: $input-container-padding; - right: $input-container-padding; - } &.md-input-focused, &.md-input-has-value {