Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(select): add md-select-header directive #3211

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/demo-app/select/select-demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -113,5 +113,20 @@
</md-card>
</div>

<md-card *ngIf="showSelect">
<md-select placeholder="Drink" [(ngModel)]="currentDrink" #selectWitHeader="mdSelect">
<md-select-header>
<input type="search" [(ngModel)]="searchTerm" role="combobox" [attr.aria-owns]="selectWitHeader.panelId"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this demo, if I select a value and then try to type into the input then the focus leaves the text input on every keypress

Copy link
Member Author

@crisbeto crisbeto Jun 18, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm you're right. This complicates things a lot, because it means that we'd need two different key handling strategies depending on whether the select has a header (FocusKeyManager by default and ActiveDescendantKeyManager if there's a header), which in turn will complicate the select even more, because it would have to delegate to the header in some cases but not in others. I can see how it can turn into a huge mess pretty quickly since we'd need to sprinkle if (this.header) all over the place and there would be a MdSelect <-> MdSelectHeader dependency. It might be best if we park and rethink whether we want to do this or recommend that people use md-autocomplete instead.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or recommend that people use md-autocomplete instead.

just not this, please, searching is not the sole use case for the select header and even if it was, autocomplete has it's own problems that make it hard to use as a select.. #3334

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you try this out in a screen reader w/ this role/aria-owns? I'm not sure it will work as expected
(what I had been talking about the other day was that to do this property you would need to revamp how the roles on select are set up completely)

(ngModelChange)="filterDrinks()" placeholder="Search for a drink"/>
</md-select-header>

<md-option *ngFor="let drink of filteredDrinks" [value]="drink.value" [disabled]="drink.disabled">
{{ drink.viewValue }}
</md-option>

</md-select>
</md-card>


</div>
<div style="height: 500px">This div is for testing scrolled selects.</div>
9 changes: 9 additions & 0 deletions src/demo-app/select/select-demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export class SelectDemo {
currentDrink: string;
currentPokemon: string[];
currentPokemonFromGroup: string;
searchTerm: string;
latestChangeEvent: MdSelectChange;
floatPlaceholder: string = 'auto';
foodControl = new FormControl('pizza-1');
Expand All @@ -43,6 +44,8 @@ export class SelectDemo {
{value: 'milk-8', viewValue: 'Milk'},
];

filteredDrinks = this.drinks.slice();

pokemon = [
{value: 'bulbasaur-0', viewValue: 'Bulbasaur'},
{value: 'charizard-1', viewValue: 'Charizard'},
Expand Down Expand Up @@ -101,4 +104,10 @@ export class SelectDemo {
setPokemonValue() {
this.currentPokemon = ['eevee-4', 'psyduck-6'];
}

filterDrinks() {
this.filteredDrinks = this.searchTerm ? this.drinks.filter(item => {
return item.viewValue.toLowerCase().indexOf(this.searchTerm.toLowerCase()) > -1;
}) : this.drinks.slice();
}
}
1 change: 1 addition & 0 deletions src/examples/select-header/select-header-example.css
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/** No CSS for this example */
12 changes: 12 additions & 0 deletions src/examples/select-header/select-header-example.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<md-select placeholder="Favorite food" [(ngModel)]="selectedValue" name="food" #select="mdSelect">
<md-select-header>
<input type="search" [(ngModel)]="searchString" role="combobox" [attr.aria-owns]="select.panelId"
(ngModelChange)="filterFoods()" placeholder="Search for food"/>
</md-select-header>

<md-option *ngFor="let food of foods" [value]="food.value">
{{food.viewValue}}
</md-option>
</md-select>

<p> Selected value: {{selectedValue}} </p>
30 changes: 30 additions & 0 deletions src/examples/select-header/select-header-example.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import {Component} from '@angular/core';


@Component({
selector: 'select-form-example',
templateUrl: './select-form-example.html',
})
export class SelectHeaderExample {
selectedValue: string;
searchString: string;

initialFoods = [
{ value: 'steak-0', viewValue: 'Steak' },
{ value: 'pizza-1', viewValue: 'Pizza' },
{ value: 'tacos-2', viewValue: 'Tacos' },
{ value: 'sandwich-3', viewValue: 'Sandwich' },
{ value: 'chips-4', viewValue: 'Chips' },
{ value: 'eggs-5', viewValue: 'Eggs' },
{ value: 'pasta-6', viewValue: 'Pasta' },
{ value: 'sushi-7', viewValue: 'Sushi' },
];

foods = this.initialFoods.slice();

filterFoods() {
this.foods = this.searchString ? this.initialFoods.filter(item => {
return item.viewValue.toLowerCase().indexOf(this.searchString.toLowerCase()) > -1;
}) : this.initialFoods.slice();
}
}
10 changes: 7 additions & 3 deletions src/lib/core/style/_menu-common.scss
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,10 @@ $mat-menu-icon-margin: 16px !default;

@mixin mat-menu-base() {
@include mat-elevation(8);
@include mat-menu-scrollable();

min-width: $mat-menu-overlay-min-width;
max-width: $mat-menu-overlay-max-width;

overflow: auto;
-webkit-overflow-scrolling: touch; // for momentum scroll on mobile
}

@mixin mat-menu-item-base() {
Expand Down Expand Up @@ -91,3 +90,8 @@ $mat-menu-icon-margin: 16px !default;
}
}
}

@mixin mat-menu-scrollable() {
overflow: auto;
-webkit-overflow-scrolling: touch; // for momentum scroll on mobile
}
4 changes: 4 additions & 0 deletions src/lib/select/_select-theme.scss
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@
color: mat-color($foreground, hint-text);
}

.mat-select-header {
color: mat-color($foreground, divider);
}

.mat-select-underline {
background-color: mat-color($foreground, divider);
}
Expand Down
6 changes: 4 additions & 2 deletions src/lib/select/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {MdSelect} from './select';
import {MdSelectHeader} from './select-header';
import {MdCommonModule, OverlayModule, MdOptionModule} from '../core';


Expand All @@ -19,11 +20,12 @@ import {MdCommonModule, OverlayModule, MdOptionModule} from '../core';
MdOptionModule,
MdCommonModule,
],
exports: [MdSelect, MdOptionModule, MdCommonModule],
declarations: [MdSelect],
exports: [MdSelect, MdSelectHeader, MdOptionModule, MdCommonModule],
declarations: [MdSelect, MdSelectHeader],
})
export class MdSelectModule {}


export * from './select';
export * from './select-header';
export {fadeInContent, transformPanel, transformPlaceholder} from './select-animations';
21 changes: 21 additions & 0 deletions src/lib/select/select-header.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import {Directive} from '@angular/core';


/**
* Fixed header that will be rendered above a select's options.
*/
@Directive({
selector: 'md-select-header, mat-select-header',
host: {
'class': 'mat-select-header',
}
})
export class MdSelectHeader { }
7 changes: 6 additions & 1 deletion src/lib/select/select.html
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,12 @@
[style.transformOrigin]="_transformOrigin"
[class.mat-select-panel-done-animating]="_panelDoneAnimating">

<div class="mat-select-content" [@fadeInContent]="'showing'" (@fadeInContent.done)="_onFadeInDone()">
<div [@fadeInContent]="'showing'">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you could replace this div with an ng-container

<ng-content select="md-select-header, mat-select-header"></ng-content>
</div>

<div class="mat-select-content" [attr.id]="panelId" [@fadeInContent]="'showing'"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think just [id] should work here

(@fadeInContent.done)="_onFadeInDone()">
<ng-content></ng-content>
</div>
</div>
Expand Down
27 changes: 23 additions & 4 deletions src/lib/select/select.scss
Original file line number Diff line number Diff line change
Expand Up @@ -117,14 +117,33 @@ $mat-select-panel-max-height: 256px !default;
margin: 0 $mat-select-arrow-margin;
}

.mat-select-panel {
@include mat-menu-base();
padding-top: 0;
padding-bottom: 0;
.mat-select-content {
@include mat-menu-scrollable();
max-height: $mat-select-panel-max-height;
min-width: 100%; // prevents some animation twitching and test inconsistencies in IE11

@include cdk-high-contrast {
outline: solid 1px;
}
}

.mat-select-panel {
@include mat-menu-base();
border: none;
}

.mat-select-header {
@include mat-menu-item-base();
border-bottom: solid 1px;
box-sizing: border-box;

input {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd lean toward omitting this style in the select itself, since the select header is mean to be a generic container that's not necessarily used for this style of filter (even if that is the most common use case)

Copy link
Member Author

@crisbeto crisbeto Jun 15, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue with allowing anything inside the header is that, with the way it is currently set up, the positioning won't work with anything that isn't 48px high. Alternatively, I can hard-code the header height.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather do that for now and then revisit the positioning to make it more flexible in the future.

display: block;
width: 100%;
height: 100%;
border: none;
outline: none;
padding: 0;
background: transparent;
}
}
Loading