Skip to content

Commit 85913d9

Browse files
committed
feat(select): basic selection and animations
1 parent 04e2201 commit 85913d9

File tree

15 files changed

+871
-20
lines changed

15 files changed

+871
-20
lines changed

src/demo-app/select/select-demo.html

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
1-
<md-select></md-select>
1+
<div class="demo-select">
2+
<md-select placeholder="Food">
3+
<md-option *ngFor="let food of foods"> {{ food.viewValue }} </md-option>
4+
</md-select>
5+
</div>

src/demo-app/select/select-demo.scss

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.demo-select {
2+
}

src/demo-app/select/select-demo.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,9 @@ import {Component} from '@angular/core';
88
styleUrls: ['select-demo.css'],
99
})
1010
export class SelectDemo {
11-
11+
foods = [
12+
{value: 'steak', viewValue: 'Steak'},
13+
{value: 'pizza', viewValue: 'Pizza'},
14+
{value: 'tacos', viewValue: 'Tacos'}
15+
];
1216
}

src/lib/core/a11y/list-key-manager.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ export class ListKeyManager {
2525

2626
constructor(private _items: QueryList<MdFocusable>) {}
2727

28+
get focusedItemIndex(): number {
29+
return this._focusedItemIndex;
30+
}
31+
2832
set focusedItemIndex(value: number) {
2933
this._focusedItemIndex = value;
3034
}

src/lib/core/compatibility/style-compatibility.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export const ELEMENTS_SELECTOR = `
2626
md-list-item,
2727
md-menu,
2828
md-nav-list,
29+
md-option,
2930
md-placeholder,
3031
md-progress-bar,
3132
md-progress-circle,

src/lib/select/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
## Work in progress!
2+
3+
The select is still a work in progress, so most features have not been implemented. Not ready for use!

src/lib/select/_select-theme.scss

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,50 @@
22
@import '../core/theming/theming';
33

44
@mixin md-select-theme($theme) {
5+
$foreground: map-get($theme, foreground);
6+
$background: map-get($theme, background);
7+
$primary: map-get($theme, primary);
58

9+
.md-select-trigger {
10+
color: md-color($foreground, hint-text);
11+
border-bottom: 1px solid md-color($foreground, divider);
12+
13+
md-select:focus & {
14+
border-bottom: 1px solid md-color($primary);
15+
}
16+
}
17+
18+
.md-select-placeholder {
19+
md-select:focus & {
20+
color: md-color($primary);
21+
}
22+
}
23+
24+
.md-select-arrow {
25+
color: md-color($foreground, hint-text);
26+
27+
md-select:focus & {
28+
color: md-color($primary);
29+
}
30+
}
31+
32+
.md-select-content {
33+
background: md-color($background, card);
34+
}
35+
36+
.md-select-value {
37+
color: md-color($foreground, text);
38+
}
39+
40+
md-option {
41+
&:hover, &:focus {
42+
background: md-color($background, hover);
43+
}
44+
45+
&.md-selected {
46+
background: md-color($background, hover);
47+
color: md-color($primary);
48+
}
49+
50+
}
651
}

src/lib/select/index.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
11
import {NgModule, ModuleWithProviders} from '@angular/core';
2+
import {CommonModule} from '@angular/common';
23
import {MdSelect} from './select';
34
import {MdOption} from './option';
5+
import {OverlayModule} from '../core/overlay/overlay-directives';
6+
import {MdRippleModule} from '../core/ripple/ripple';
7+
import {OVERLAY_PROVIDERS} from '../core/overlay/overlay';
48
export * from './select';
59

610
@NgModule({
7-
imports: [],
11+
imports: [CommonModule, OverlayModule, MdRippleModule],
812
exports: [MdSelect, MdOption],
913
declarations: [MdSelect, MdOption],
1014
})
1115
export class MdSelectModule {
1216
static forRoot(): ModuleWithProviders {
1317
return {
1418
ngModule: MdSelectModule,
15-
providers: []
19+
providers: [OVERLAY_PROVIDERS]
1620
};
1721
}
1822
}

src/lib/select/option.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<ng-content></ng-content>
2+
<div class="md-option-ripple" md-ripple md-ripple-background-color="rgba(0,0,0,0)"
3+
[md-ripple-trigger]="_getHostElement()"></div>

src/lib/select/option.ts

Lines changed: 68 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,75 @@
1-
import {Component, ViewEncapsulation} from '@angular/core';
1+
import {
2+
Component,
3+
ElementRef,
4+
EventEmitter,
5+
Output,
6+
Renderer,
7+
ViewEncapsulation
8+
} from '@angular/core';
9+
import {ENTER, SPACE} from '../core/keyboard/keycodes';
210

311
@Component({
412
moduleId: module.id,
513
selector: 'md-option',
6-
template: ``,
14+
host: {
15+
'role': 'option',
16+
'tabindex': '0',
17+
'[class.md-selected]': 'selected',
18+
'[attr.aria-selected]': 'selected.toString()',
19+
'(click)': 'select()',
20+
'(keydown)': '_handleKeydown($event)'
21+
},
22+
templateUrl: 'option.html',
723
styleUrls: ['select.css'],
824
encapsulation: ViewEncapsulation.None
925
})
10-
export class MdOption {}
26+
export class MdOption {
27+
private _selected = false;
28+
29+
/** Event emitted when the option is selected. */
30+
@Output() onSelect = new EventEmitter();
31+
32+
constructor(private _element: ElementRef, private _renderer: Renderer) {}
33+
34+
/** Whether or not the option is currently selected. */
35+
get selected(): boolean {
36+
return this._selected;
37+
}
38+
39+
/**
40+
* The displayed value of the option. It is necessary to show the selected option in the
41+
* select's trigger.
42+
* TODO(kara): Add input property alternative for node envs.
43+
*/
44+
get viewValue(): string {
45+
return this._getHostElement().textContent.trim();
46+
}
47+
48+
/** Selects the option. */
49+
select(): void {
50+
this._selected = true;
51+
this.onSelect.emit();
52+
}
53+
54+
/** Deselects the option. */
55+
deselect(): void {
56+
this._selected = false;
57+
}
58+
59+
/** Sets focus onto this option. */
60+
focus(): void {
61+
this._renderer.invokeElementMethod(this._getHostElement(), 'focus');
62+
}
63+
64+
/** Ensures the option is selected when activated from the keyboard. */
65+
_handleKeydown(event: KeyboardEvent): void {
66+
if (event.keyCode === ENTER || event.keyCode === SPACE) {
67+
this.select();
68+
}
69+
}
70+
71+
_getHostElement(): HTMLElement {
72+
return this._element.nativeElement;
73+
}
74+
75+
}

src/lib/select/select-animations.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import {
2+
animate,
3+
AnimationEntryMetadata,
4+
state,
5+
style,
6+
transition,
7+
trigger,
8+
} from '@angular/core';
9+
10+
/**
11+
* The following are all the animations for the md-select component, with each
12+
* const containing the metadata for one animation.
13+
*
14+
* The values below match the implementation of the Material 1 md-select animation.
15+
*/
16+
17+
/**
18+
* This animation shrinks the placeholder text to 75% of its normal size and translates
19+
* it to either the top left corner (ltr) or top right corner (rtl) of the trigger,
20+
* depending on the text direction of the application.
21+
*/
22+
export const transformPlaceholder: AnimationEntryMetadata = trigger('transformPlaceholder', [
23+
state('normal', style({
24+
transform: `translate3d(0, 0, 0) scale(1)`
25+
})),
26+
state('floating-ltr', style({
27+
transform: `translate3d(-2px, -22px, 0) scale(0.75)`
28+
})),
29+
state('floating-rtl', style({
30+
transform: `translate3d(2px, -22px, 0) scale(0.75)`
31+
})),
32+
transition('* => *', animate(`400ms cubic-bezier(0.25, 0.8, 0.25, 1)`))
33+
]);
34+
35+
/**
36+
* This animation transforms the select's overlay panel on and off the page.
37+
*
38+
* When the panel is attached to the DOM, it expands its width 32px, scales it up to
39+
* 100% on the Y axis, fades in its border, and translates slightly up and to the
40+
* side to ensure the option text correctly overlaps the trigger text.
41+
*
42+
* When the panel is removed from the DOM, it simply fades out linearly.
43+
*/
44+
export const transformPanel: AnimationEntryMetadata = trigger('transformPanel', [
45+
state('showing-ltr', style({
46+
opacity: 1,
47+
width: 'calc(100% + 32px)',
48+
transform: `translate3d(-16px, -9px, 0) scaleY(1)`
49+
})),
50+
state('showing-rtl', style({
51+
opacity: 1,
52+
width: 'calc(100% + 32px)',
53+
transform: `translate3d(16px, -9px, 0) scaleY(1)`
54+
})),
55+
transition('void => *', [
56+
style({
57+
opacity: 0,
58+
width: '100%',
59+
transform: `translate3d(0, 0, 0) scaleY(0)`
60+
}),
61+
animate(`150ms cubic-bezier(0.55, 0, 0.55, 0.2)`)
62+
]),
63+
transition('* => void', [
64+
animate('250ms linear', style({opacity: 0}))
65+
])
66+
]);
67+
68+
/**
69+
* This animation fades in the background color and text content of the
70+
* select's options. It is time delayed to occur 100ms after the overlay
71+
* panel has transformed in.
72+
*/
73+
export const fadeInContent: AnimationEntryMetadata = trigger('fadeInContent', [
74+
state('showing', style({opacity: 1})),
75+
transition('void => showing', [
76+
style({opacity: 0}),
77+
animate(`150ms 100ms cubic-bezier(0.55, 0, 0.55, 0.2)`)
78+
])
79+
]);

src/lib/select/select.html

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,15 @@
1-
I'm a select!
1+
<div class="md-select-trigger" overlay-origin (click)="toggle()" #origin="overlayOrigin" #trigger>
2+
<span class="md-select-placeholder" [@transformPlaceholder]="_getPlaceholderState()"> {{ placeholder }} </span>
3+
<span class="md-select-value" *ngIf="selected"> {{ selected?.viewValue }} </span>
4+
<span class="md-select-arrow"></span>
5+
</div>
6+
7+
<template connected-overlay [origin]="origin" [open]="panelOpen" hasBackdrop (backdropClick)="close()"
8+
backdropClass="md-overlay-transparent-backdrop" [positions]="_positions" [width]="_getWidth()">
9+
<div class="md-select-panel" [@transformPanel]="_getPanelState()" (@transformPanel.done)="_onPanelDone()"
10+
(keydown)="_keyManager.onKeydown($event)">
11+
<div class="md-select-content" [@fadeInContent]="'showing'">
12+
<ng-content></ng-content>
13+
</div>
14+
</div>
15+
</template>

src/lib/select/select.scss

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
@import '../core/style/menu-common';
2+
3+
$md-select-trigger-height: 30px !default;
4+
$md-select-trigger-min-width: 112px !default;
5+
$md-select-arrow-size: 5px !default;
6+
7+
md-select {
8+
display: inline-block;
9+
outline: none;
10+
}
11+
12+
.md-select-trigger {
13+
display: flex;
14+
justify-content: space-between;
15+
align-items: center;
16+
height: $md-select-trigger-height;
17+
min-width: $md-select-trigger-min-width;
18+
cursor: pointer;
19+
}
20+
21+
.md-select-placeholder {
22+
padding: 0 2px;
23+
transform-origin: left top;
24+
25+
[dir='rtl'] & {
26+
transform-origin: right top;
27+
}
28+
}
29+
30+
.md-select-value {
31+
position: absolute;
32+
}
33+
34+
.md-select-arrow {
35+
width: 0;
36+
height: 0;
37+
border-left: $md-select-arrow-size solid transparent;
38+
border-right: $md-select-arrow-size solid transparent;
39+
border-top: $md-select-arrow-size solid;
40+
}
41+
42+
.md-select-panel {
43+
@include md-menu-base();
44+
padding-top: 0;
45+
padding-bottom: 0;
46+
transform-origin: top;
47+
}
48+
49+
md-option {
50+
@include md-menu-item-base();
51+
position: relative;
52+
cursor: pointer;
53+
outline: none;
54+
}
55+
56+
.md-option-ripple {
57+
position: absolute;
58+
top: 0;
59+
left: 0;
60+
bottom: 0;
61+
right: 0;
62+
}

0 commit comments

Comments
 (0)