-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Commit
…precate md-text-float Closes #993. Closes #372. Closes #476. Closes #523. Closes #543. Closes #547. Closes #553. Closes #562. Closes #575. Closes #605. Closes #606. Closes #612. Closes #654. Closes #687. Closes #744. Closes #839. Closes #847. Closes #931. Closes #993. md-textfloat has been deprecated due to flaws (explanation in [#547](#547)). Now, to create an input you use the native `<input>` and `<textarea>` elements, with a `<md-input-container>` parent around each `<input>` or `<textarea>`. Change your code from this: ```html <md-textfloat label="First Name" ng-model="firstName"></md-textfloat> ``` To this: ```html <md-input-container> <label>First Name</label> <input ng-model="firstName"> </md-input-container> ````
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
<div ng-app="inputBasicDemo" ng-controller="DemoCtrl" layout="column"> | ||
|
||
<md-content md-theme="docs-dark" class="md-padding" layout="row" layout-sm="column"> | ||
<md-input-container> | ||
<label>Title</label> | ||
<input ng-model="user.title"> | ||
</md-input-container> | ||
<md-input-container> | ||
<label>Email</label> | ||
<input ng-model="user.email" type="email"> | ||
</md-input-container> | ||
</md-content> | ||
|
||
<md-content class="md-padding"> | ||
|
||
<md-input-container flex> | ||
<label>Company (Disabled)</label> | ||
<input ng-model="user.company" disabled> | ||
</md-input-container> | ||
|
||
<div layout layout-padding layout-sm="column"> | ||
<md-input-container flex> | ||
<label>First Name</label> | ||
<input ng-model="user.firstName"> | ||
</md-input-container> | ||
<md-input-container flex> | ||
<label>Last Name</label> | ||
<input ng-model="user.lastName"> | ||
</md-input-container> | ||
</div> | ||
|
||
<md-input-container flex> | ||
<label>Address</label> | ||
<input ng-model="user.address"> | ||
</md-input-container> | ||
|
||
<div layout layout-padding layout-sm="column"> | ||
<md-input-container flex> | ||
<label>City</label> | ||
<input ng-model="user.city"> | ||
</md-input-container> | ||
<md-input-container flex> | ||
<label>State</label> | ||
<input ng-model="user.state"> | ||
</md-input-container> | ||
<md-input-container flex> | ||
<label>Postal Code</label> | ||
<input ng-model="user.postalCode"> | ||
</md-input-container> | ||
</div> | ||
|
||
<md-input-container flex> | ||
<label>Country</label> | ||
<input ng-model="user.country"> | ||
</md-input-container> | ||
|
||
</md-content> | ||
</div> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
angular.module('inputBasicDemo', ['ngMaterial']) | ||
|
||
.controller('DemoCtrl', function($scope) { | ||
$scope.user = { | ||
title: 'Developer', | ||
email: '[email protected]', | ||
firstName: '', | ||
lastName: '' , | ||
company: 'Google' , | ||
address: '1600 Amphitheatre Pkwy' , | ||
city: 'Mountain View' , | ||
state: 'CA' , | ||
country: 'USA' , | ||
postalCode : '94043' | ||
}; | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
md-input-container.md-THEME_NAME-theme { | ||
.md-input { | ||
@include input-placeholder-color('{{foreground-3}}'); | ||
color: '{{foreground-1}}'; | ||
border-color: '{{foreground-4}}'; | ||
text-shadow: '{{foreground-shadow}}'; | ||
} | ||
|
||
label { | ||
text-shadow: '{{foreground-shadow}}'; | ||
color: '{{foreground-3}}'; | ||
} | ||
|
||
&.md-input-focused { | ||
.md-input { | ||
border-color: '{{primary-500}}'; | ||
} | ||
label { | ||
color: '{{primary-500}}'; | ||
} | ||
&.md-accent { | ||
.md-input { | ||
border-color: '{{accent-500}}'; | ||
} | ||
label { | ||
color: '{{accent-500}}'; | ||
} | ||
} | ||
} | ||
|
||
&.md-input-has-value:not(.md-input-focused) { | ||
label { | ||
color: '{{foreground-2}}'; | ||
} | ||
} | ||
|
||
.md-input[disabled] { | ||
border-bottom-color: transparent; | ||
color: '{{foreground-3}}'; | ||
background-image: linear-gradient(to right, '{{foreground-4}}' 0%, '{{foreground-4}}' 33%, transparent 0%); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
(function() { | ||
|
||
/** | ||
* @ngdoc module | ||
* @name material.components.input | ||
*/ | ||
|
||
angular.module('material.components.input', [ | ||
'material.core' | ||
]) | ||
.directive('mdInputContainer', mdInputContainerDirective) | ||
.directive('label', labelDirective) | ||
.directive('input', inputDirective) | ||
.directive('textarea', inputDirective); | ||
|
||
/** | ||
* @ngdoc directive | ||
* @name mdInputContainer | ||
* @module material.components.input | ||
* | ||
* @restrict E | ||
* | ||
* @description | ||
* `<md-input-container>` is the parent of any input or textarea element. | ||
* | ||
* Input and textarea elements will not behave properly unless the md-input-container | ||
* parent is provided. | ||
* | ||
* @usage | ||
* <hljs lang="html"> | ||
* | ||
* <md-input-container> | ||
* <label>Username</label> | ||
* <input type="text" ng-model="user.name"> | ||
* </md-input-container> | ||
* | ||
* <md-input-container> | ||
* <label>Description</label> | ||
* <textarea ng-model="user.description"></textarea> | ||
* </md-input-container> | ||
* | ||
* </hljs> | ||
*/ | ||
function mdInputContainerDirective($mdTheming) { | ||
return { | ||
restrict: 'E', | ||
link: postLink, | ||
controller: ContainerCtrl | ||
}; | ||
|
||
function postLink(scope, element, attr) { | ||
$mdTheming(element); | ||
} | ||
function ContainerCtrl($scope, $element, $mdUtil) { | ||
var self = this; | ||
|
||
self.setFocused = function(isFocused) { | ||
$element.toggleClass('md-input-focused', !!isFocused); | ||
}; | ||
self.setHasValue = function(hasValue) { | ||
$element.toggleClass('md-input-has-value', !!hasValue); | ||
}; | ||
|
||
$scope.$watch(function() { | ||
return self.label && self.input; | ||
}, function(hasLabelAndInput) { | ||
if (hasLabelAndInput && !self.label.attr('for')) { | ||
self.label.attr('for', self.input.attr('id')); | ||
} | ||
}); | ||
} | ||
} | ||
|
||
function labelDirective() { | ||
return { | ||
restrict: 'E', | ||
require: '^?mdInputContainer', | ||
link: function(scope, element, attr, containerCtrl) { | ||
if (!containerCtrl) return; | ||
|
||
containerCtrl.label = element; | ||
scope.$on('$destroy', function() { | ||
containerCtrl.label = null; | ||
}); | ||
} | ||
}; | ||
} | ||
|
||
function inputDirective($mdUtil) { | ||
return { | ||
restrict: 'E', | ||
require: ['^?mdInputContainer', '?ngModel'], | ||
compile: compile, | ||
This comment has been minimized.
Sorry, something went wrong. |
||
}; | ||
|
||
function compile(element) { | ||
element.addClass('md-input'); | ||
return postLink; | ||
} | ||
function postLink(scope, element, attr, ctrls) { | ||
|
||
var containerCtrl = ctrls[0]; | ||
var ngModelCtrl = ctrls[1]; | ||
|
||
if ( !containerCtrl ) return; | ||
|
||
if (containerCtrl.input) { | ||
throw new Error("<md-input-container> can only have *one* <input> or <textarea> child element!"); | ||
} | ||
if (!element.attr('id')) { | ||
element.attr('id', 'input_' + $mdUtil.nextUid()); | ||
} | ||
|
||
containerCtrl.input = element; | ||
|
||
// When the input value changes, check if it "has" a value, and | ||
// set the appropriate class on the input group | ||
if (ngModelCtrl) { | ||
ngModelCtrl.$formatters.push(checkHasValue); | ||
ngModelCtrl.$parsers.push(checkHasValue); | ||
} else { | ||
element.on('input', function() { | ||
containerCtrl.setHasValue( (""+element.val()).length > 0 ); | ||
}); | ||
containerCtrl.setHasValue( (""+element.val()).length > 0 ); | ||
} | ||
function checkHasValue(value) { | ||
containerCtrl.setHasValue(!ngModelCtrl.$isEmpty(value)); | ||
return value; | ||
} | ||
|
||
element | ||
.on('focus', function(e) { | ||
containerCtrl.setFocused(true); | ||
}) | ||
.on('blur', function(e) { | ||
containerCtrl.setFocused(false); | ||
}); | ||
|
||
scope.$on('$destroy', function() { | ||
containerCtrl.setFocused(false); | ||
containerCtrl.setHasValue(false); | ||
containerCtrl.input = null; | ||
}); | ||
|
||
function isNotEmpty(value) { | ||
This comment has been minimized.
Sorry, something went wrong. |
||
value = angular.isUndefined(value) ? element.val() : value; | ||
return (angular.isDefined(value) && (value!==null) && | ||
(value.toString().trim() !== "")); | ||
} | ||
} | ||
} | ||
|
||
})(); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
$input-container-padding: 2px !default; | ||
|
||
$input-label-default-offset: 24px !default; | ||
$input-label-default-scale: 1.0 !default; | ||
$input-label-float-offset: 4px !default; | ||
$input-label-float-scale: 0.75 !default; | ||
|
||
$input-border-width-default: 1px !default; | ||
$input-border-width-focused: 2px !default; | ||
$input-line-height: 26px !default; | ||
$input-padding-top: 2px !default; | ||
|
||
md-input-container { | ||
display: flex; | ||
position: relative; | ||
flex-direction: column; | ||
padding: $input-container-padding; | ||
|
||
textarea, | ||
input[type="text"], | ||
input[type="password"], | ||
input[type="datetime"], | ||
input[type="datetime-local"], | ||
input[type="date"], | ||
input[type="month"], | ||
input[type="time"], | ||
input[type="week"], | ||
input[type="number"], | ||
input[type="email"], | ||
input[type="url"], | ||
input[type="search"], | ||
input[type="tel"], | ||
input[type="color"] { | ||
/* remove default appearance from all input/textarea */ | ||
-moz-appearance: none; | ||
-webkit-appearance: none; | ||
} | ||
|
||
label { | ||
order: 1; | ||
pointer-events: none; | ||
-webkit-font-smoothing: antialiased; | ||
z-index: 1; | ||
transform: translate3d(0, $input-label-default-offset, 0) scale($input-label-default-scale); | ||
transform-origin: left top; | ||
transition: all $swift-ease-out-timing-function 0.2s; | ||
} | ||
|
||
/* | ||
* The .md-input class is added to the input/textarea | ||
*/ | ||
.md-input { | ||
flex: 1; | ||
order: 2; | ||
display: block; | ||
|
||
background: none; | ||
padding-top: $input-padding-top; | ||
padding-bottom: $input-border-width-focused - $input-border-width-default; | ||
border-width: 0 0 $input-border-width-default 0; | ||
line-height: $input-line-height; | ||
|
||
&:focus { | ||
outline: none; | ||
} | ||
} | ||
|
||
&.md-input-focused, | ||
&.md-input-has-value { | ||
label { | ||
transform: translate3d(0,$input-label-float-offset,0) scale($input-label-float-scale); | ||
} | ||
} | ||
&.md-input-focused { | ||
.md-input { | ||
padding-bottom: 0px; // Increase border width by 1px, decrease padding by 1 | ||
border-width: 0 0 $input-border-width-focused 0; | ||
} | ||
} | ||
|
||
.md-input[disabled] { | ||
background-position: 0 bottom; | ||
// This background-size is coordinated with a linear-gradient set in input-theme.scss | ||
// to create a dotted line under the input. | ||
background-size: 3px 1px; | ||
background-repeat: repeat-x; | ||
} | ||
} |
11 comments
on commit 60fcd6f
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wow. This commit solves so much issues that I just can't wait to see it landing in a final release 💯
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👏
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ajoslin Great job! This should make forms a lot nicer again.
How will this affect the plans for handling errors/non floating labels will we just be adding classes to the md-input-container
now? I think that'd be a lot nicer than having separate elements.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Errors will be handled with ng-messages, the normal angular way. We'll just add some css for the ng-message elements.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work! Awesome.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Works great so far, thanks! 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @yarl.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ajoslin: This looks great !
I have left a couple of inline comments (mostly cleanup stuff).
It would be nice to have better test-coverage (considering this is probably going to be one of the most heavily utilized components). E.g. tests for non-text inputs etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Placeholder mixin won't work as expected in Chrome.
@mixin input-placeholder-color($color) {
&::-webkit-input-placeholder,
&::-moz-placeholder, /* Firefox 19+ */
&:-moz-placeholder, /* Firefox 18- */
&:-ms-input-placeholder {
color: $color;
}
}
This is because Chrome (at this point in time) fails to parse its part. These have to be separated out like so:
@mixin input-placeholder-color($color) {
&::-webkit-input-placeholder { color: $color; }
&::-moz-placeholder { color: $color; } /* Firefox 19+ */
&:-moz-placeholder { color: $color; } /* Firefox 18- */
&:-ms-input-placeholder { color: $color; }
}
BTW, found this out the hard way.
Is there any benefit in using a compile function ?