Skip to content

Commit

Permalink
Fix select pristine/dirty error and many styles.
Browse files Browse the repository at this point in the history
_NOT READY FOR MERGE - Need to share with others to see improved
UI functionality/styles and get sign-off._

Fix styles and code to follow pristine/dirty styling of other
input elements and provide CSS class for stand-alone usage.

 - Select now behaves like a normal input, appearing as invalid
   if the user focuses/blurs the element, or submits the form,
   without selecting an option.
 - Fix issues with floating labels not working on focus.
 - Add new `md-no-underline` CSS class to allow for stand-alone
   usage (non-form).
 - Update demos to show new stand-alone usage.

Fixes angular#8529. Fixes angular#7988.
  • Loading branch information
topherfangio committed Jun 4, 2016
1 parent 51f8a2a commit 93de4d8
Show file tree
Hide file tree
Showing 10 changed files with 254 additions and 55 deletions.
28 changes: 21 additions & 7 deletions src/components/input/demoErrors/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,23 @@
</div>
</md-input-container>

<md-input-container class="md-block">
<label>Client Name</label>
<input required name="clientName" ng-model="project.clientName">
<div ng-messages="projectForm.clientName.$error">
<div ng-message="required">This is required.</div>
</div>
</md-input-container>
<div layout="row">
<md-input-container flex="50">
<label>Client Name</label>
<input required name="clientName" ng-model="project.clientName">
<div ng-messages="projectForm.clientName.$error">
<div ng-message="required">This is required.</div>
</div>
</md-input-container>

<md-input-container flex="50">
<label>Project Type</label>
<md-select name="type" ng-model="project.type" required>
<md-option value="app">Application</md-option>
<md-option value="web">Website</md-option>
</md-select>
</md-input-container>
</div>

<md-input-container class="md-block">
<label>Client Email</label>
Expand Down Expand Up @@ -59,6 +69,10 @@
</div>
</md-input-container>

<div>
<md-button type="submit">Submit</md-button>
</div>

<p style="font-size:.8em; width: 100%; text-align: center;">
Make sure to include <a href="https://docs.angularjs.org/api/ngMessages" target="_blank">ngMessages</a> module when using ng-message markup.
</p>
Expand Down
6 changes: 4 additions & 2 deletions src/components/input/input-theme.scss
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ md-input-container.md-THEME_NAME-theme {
color: '{{foreground-3}}';
}

label.md-required:after {
color: '{{warn-A700}}'
&.md-input-invalid {
label.md-required:after {
color: '{{warn-A700}}'
}
}

&:not(.md-input-focused):not(.md-input-invalid) label.md-required:after {
Expand Down
9 changes: 1 addition & 8 deletions src/components/input/input.js
Original file line number Diff line number Diff line change
Expand Up @@ -334,14 +334,7 @@ function inputTextareaDirective($mdUtil, $window, $mdAria, $timeout, $mdGesture)
}

var isErrorGetter = containerCtrl.isErrorGetter || function() {
return ngModelCtrl.$invalid && (ngModelCtrl.$touched || isParentFormSubmitted());
};

var isParentFormSubmitted = function () {
var parent = $mdUtil.getClosest(element, 'form');
var form = parent ? angular.element(parent).controller('form') : null;

return form ? form.$submitted : false;
return ngModelCtrl.$invalid && (ngModelCtrl.$touched || $mdUtil.isParentFormSubmitted(element));
};

scope.$watch(isErrorGetter, containerCtrl.setInvalid);
Expand Down
105 changes: 81 additions & 24 deletions src/components/select/demoBasicUsage/index.html
Original file line number Diff line number Diff line change
@@ -1,26 +1,83 @@
<div ng-controller="AppCtrl as ctrl" class="md-padding" ng-cloak>
<div>
<h1 class="md-title">Enter an address</h1>
<div layout="row">

<md-input-container>
<label>Street Name</label>
<input>
</md-input-container>

<md-input-container>
<label>City</label>
<input>
</md-input-container>

<md-input-container>
<label>State</label>
<md-select ng-model="ctrl.userState">
<md-option ng-repeat="state in ctrl.states" value="{{state.abbrev}}" ng-disabled="$index === 1">
{{state.abbrev}}
</md-option>
<div ng-controller="AppCtrl as ctrl" class="md-padding" ng-cloak layout="column">

<p>
The <code>&lt;md-select&gt;</code> component can be used within a
<code>&lt;md-input-container&gt;</code> or as a stand alone component by using the
<code>md-no-underline</code> class.
</p>

<md-card>
<md-card-title>
<md-card-title-text>
<span class="md-headline">Account Preferences</span>
<span class="md-subhead">Tell us a little about you.</span>
</md-card-title-text>
</md-card-title>

<md-card-content>
<div layout="row">
<md-input-container>
<label>Street Name</label>
<input>
</md-input-container>

<md-input-container>
<label>City</label>
<input>
</md-input-container>

<md-input-container>
<label>State</label>
<md-select ng-model="ctrl.userState">
<md-option ng-repeat="state in ctrl.states" value="{{state.abbrev}}" ng-disabled="$index === 1">
{{state.abbrev}}
</md-option>
</md-select>
</md-input-container>
</div>
</md-card-content>
</md-card>

<md-card>
<md-card-title>
<md-card-title-text>
<span class="md-headline">Battle Preferences</span>
<span class="md-subhead">Choose wisely if you want to win.</span>
</md-card-title-text>
</md-card-title>

<md-card-content>
<div layout="row" layout-align="space-between center">
<span>What is your favorite weapon?</span>
<md-select ng-model="weapon" placeholder="Choose..." class="md-no-underline">
<md-option value="axe">Axe</md-option>
<md-option value="sword">Sword</md-option>
<md-option value="wand">Wand</md-option>
<md-option value="pen">Pen?</md-option>
</md-select>
</div>

<div layout="row" layout-align="space-between center">
<span>What armor do you wear?</span>
<md-select ng-model="armor" placeholder="Choose..." class="md-no-underline">
<md-option value="cloth">Cloth</md-option>
<md-option value="leather">Leather</md-option>
<md-option value="chain">Chainmail</md-option>
<md-option value="plate">Plate</md-option>
</md-select>
</md-input-container>
</div>
</div>
</div>

<div layout="row" layout-align="space-between center">
<span>How do you refresh your magic?</span>
<md-select ng-model="drink" placeholder="Choose..." class="md-no-underline">
<md-option value="water">Water</md-option>
<md-option value="juice">Juice</md-option>
<md-option value="milk">Milk</md-option>
<md-option value="wine">Wine</md-option>
<md-option value="mead">Mead</md-option>
</md-select>
</div>
</md-card-content>
</md-card>

</div>
26 changes: 25 additions & 1 deletion src/components/select/select-theme.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
md-input-container {
&.md-input-focused {
&:not(.md-input-has-value) {
md-select.md-THEME_NAME-theme {
._md-select-value {
color: '{{primary-color}}';
&._md-select-placeholder {
color: '{{primary-color}}';
}
}
}
}
}
}

md-select.md-THEME_NAME-theme {
&[disabled] ._md-select-value {
border-bottom-color: transparent;
Expand All @@ -10,11 +25,17 @@ md-select.md-THEME_NAME-theme {
}
border-bottom-color: '{{foreground-4}}';
}
&.ng-invalid.ng-dirty {
&.md-no-underline ._md-select-value {
border-bottom-color: transparent !important;
}
&.ng-invalid.ng-touched {
._md-select-value {
color: '{{warn-A700}}' !important;
border-bottom-color: '{{warn-A700}}' !important;
}
&.md-no-underline ._md-select-value {
border-bottom-color: transparent !important;
}
}
&:not([disabled]):focus {
._md-select-value {
Expand All @@ -24,6 +45,9 @@ md-select.md-THEME_NAME-theme {
color: '{{ foreground-1 }}';
}
}
&.md-no-underline ._md-select-value {
border-bottom-color: transparent !important;
}
&.md-accent ._md-select-value {
border-bottom-color: '{{accent-color}}';
}
Expand Down
13 changes: 6 additions & 7 deletions src/components/select/select.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,10 @@ angular.module('material.components.select', [
* @description Displays a select box, bound to an ng-model.
*
* When the select is required and uses a floating label, then the label will automatically contain
* an asterisk (`*`).<br/>
* This behavior can be disabled by using the `md-no-asterisk` attribute.
* an asterisk (`*`). This behavior can be disabled by using the `md-no-asterisk` attribute.
*
* By default, the select will display with an underline to match other form elements. This can be
* disabled by applying the `md-no-underline` CSS class.
*
* @param {expression} ng-model The model!
* @param {boolean=} multiple Whether it's multiple.
Expand Down Expand Up @@ -331,11 +333,8 @@ function SelectDirective($mdSelect, $mdUtil, $mdTheming, $mdAria, $compile, $par
if (!isReadonly) {
element
.on('focus', function(ev) {
// only set focus on if we don't currently have a selected value. This avoids the "bounce"
// on the label transition because the focus will immediately switch to the open menu.
if (containerCtrl && containerCtrl.element.hasClass('md-input-has-value')) {
containerCtrl.setFocused(true);
}
// Always focus the container so floating labels and other styles are applied properly
containerCtrl.setFocused(true);
});

// Attach before ngModel's blur listener to stop propagation of blur event
Expand Down
58 changes: 53 additions & 5 deletions src/components/select/select.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,23 @@ $select-container-transition-duration: 350ms !default;

$select-max-visible-options: 5 !default;

// Fixes the animations with the floating label when select is inside an input container
md-input-container {
&:not([md-no-float]) {
._md-select-placeholder span:first-child {
transition: transform $swift-ease-out-duration $swift-ease-out-timing-function;
@include rtl(transform-origin, left top, right top);
}
}
&.md-input-focused {
&:not([md-no-float]) {
._md-select-placeholder span:first-child {
transform: translateY(-22px) translateX(-2px) scale(0.75);
}
}
}
}

._md-select-menu-container {
position: fixed;
left: 0;
Expand All @@ -16,6 +33,9 @@ $select-max-visible-options: 5 !default;
opacity: 0;
display: none;

// Fix 1px alignment issue to line up with text inputs (and spec)
transform: translate3d(0, -1px, 0);

// Don't let the user select a new choice while it's animating
&:not(._md-clickable) {
pointer-events: none;
Expand Down Expand Up @@ -56,9 +76,25 @@ md-input-container > md-select {
order: 2;
}


// Show the asterisk on the placeholder if the element is required
//
// NOTE: When the input has a value and uses a floating label, the floating label will show the
// asterisk denoting that it is required
md-input-container:not(.md-input-has-value) {
md-select[required], md-select.ng-required {
._md-select-value span:first-child:after {
content: ' *';
font-size: 13px;
vertical-align: top;
}
}
}

md-select {
display: flex;
margin: 2.5*$baseline-grid 0 3*$baseline-grid + 2 0;

&[disabled] ._md-select-value {
background-position: 0 bottom;
// This background-size is coordinated with a linear-gradient set in select-theme.scss
Expand All @@ -67,6 +103,7 @@ md-select {
background-repeat: repeat-x;
margin-bottom: -1px; // Shift downward so dotted line is positioned the same as other bottom borders
}

&:focus {
outline: none;
}
Expand All @@ -77,10 +114,10 @@ md-select {
&:hover {
cursor: pointer
}
&.ng-invalid.ng-dirty {
&.ng-invalid.ng-touched {
._md-select-value {
border-bottom: 2px solid;
padding-bottom: 0;
border-bottom-style: solid;
padding-bottom: 1px;
}
}
&:focus {
Expand All @@ -89,10 +126,21 @@ md-select {
border-bottom-style: solid;
padding-bottom: 0;
}
&.ng-invalid.ng-touched {
._md-select-value {
padding-bottom: 0;
}
}
}
}
}

// Fix value by 1px to align with standard text inputs (and spec)
md-input-container.md-input-has-value ._md-select-value {
> span:not(._md-select-icon) {
transform: translate3d(0, 1px, 0);
}
}

._md-select-value {
display: flex;
Expand All @@ -111,7 +159,6 @@ md-select {
> span:not(._md-select-icon) {
max-width: 100%;
flex: 1 1 auto;
transform: translate3d(0, 2px, 0);
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
Expand All @@ -127,7 +174,8 @@ md-select {
text-align: end;
width: 3 * $baseline-grid;
margin: 0 .5 * $baseline-grid;
transform: translate3d(0, 1px, 0);
transform: translate3d(0, -2px, 0);
font-size: 1.2rem;
}

._md-select-icon:after {
Expand Down
11 changes: 11 additions & 0 deletions src/components/select/select.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,17 @@ describe('<md-select>', function() {
expect($rootScope.myForm.select.$touched).toBe(true);
}));

it('applies the md-input-focused class to the container when focused with the keyboard', function() {
var element = setupSelect('ng-model="val"');
var select = element.find('md-select');

select.triggerHandler('focus');
expect(element.hasClass('md-input-focused')).toBe(true);

select.triggerHandler('blur');
expect(element.hasClass('md-input-focused')).toBe(false);
});

it('restores focus to select when the menu is closed', inject(function($document) {
var select = setupSelect('ng-model="val"').find('md-select');
openSelect(select);
Expand Down
Loading

0 comments on commit 93de4d8

Please sign in to comment.