-
Notifications
You must be signed in to change notification settings - Fork 6.8k
feat(select): basic selection and animations #1647
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,5 @@ | ||
<md-select></md-select> | ||
<div class="demo-select"> | ||
<md-select placeholder="Food"> | ||
<md-option *ngFor="let food of foods"> {{ food.viewValue }} </md-option> | ||
</md-select> | ||
</div> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
.demo-select { | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
## Work in progress! | ||
|
||
The select is still a work in progress, so most features have not been implemented. Not ready for use! |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,22 @@ | ||
import {NgModule, ModuleWithProviders} from '@angular/core'; | ||
import {CommonModule} from '@angular/common'; | ||
import {MdSelect} from './select'; | ||
import {MdOption} from './option'; | ||
import {OverlayModule} from '../core/overlay/overlay-directives'; | ||
import {MdRippleModule} from '../core/ripple/ripple'; | ||
import {OVERLAY_PROVIDERS} from '../core/overlay/overlay'; | ||
export * from './select'; | ||
|
||
@NgModule({ | ||
imports: [], | ||
imports: [CommonModule, OverlayModule, MdRippleModule], | ||
exports: [MdSelect, MdOption], | ||
declarations: [MdSelect, MdOption], | ||
}) | ||
export class MdSelectModule { | ||
static forRoot(): ModuleWithProviders { | ||
return { | ||
ngModule: MdSelectModule, | ||
providers: [] | ||
providers: [OVERLAY_PROVIDERS] | ||
}; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
<ng-content></ng-content> | ||
<div class="md-option-ripple" md-ripple md-ripple-background-color="rgba(0,0,0,0)" | ||
[md-ripple-trigger]="_getHostElement()"></div> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,75 @@ | ||
import {Component, ViewEncapsulation} from '@angular/core'; | ||
import { | ||
Component, | ||
ElementRef, | ||
EventEmitter, | ||
Output, | ||
Renderer, | ||
ViewEncapsulation | ||
} from '@angular/core'; | ||
import {ENTER, SPACE} from '../core/keyboard/keycodes'; | ||
|
||
@Component({ | ||
moduleId: module.id, | ||
selector: 'md-option', | ||
template: ``, | ||
host: { | ||
'role': 'option', | ||
'tabindex': '0', | ||
'[class.md-selected]': 'selected', | ||
'[attr.aria-selected]': 'selected.toString()', | ||
'(click)': 'select()', | ||
'(keydown)': '_handleKeydown($event)' | ||
}, | ||
templateUrl: 'option.html', | ||
styleUrls: ['select.css'], | ||
encapsulation: ViewEncapsulation.None | ||
}) | ||
export class MdOption {} | ||
export class MdOption { | ||
private _selected = false; | ||
|
||
/** Event emitted when the option is selected. */ | ||
@Output() onSelect = new EventEmitter(); | ||
|
||
constructor(private _element: ElementRef, private _renderer: Renderer) {} | ||
|
||
/** Whether or not the option is currently selected. */ | ||
get selected(): boolean { | ||
return this._selected; | ||
} | ||
|
||
/** | ||
* The displayed value of the option. It is necessary to show the selected option in the | ||
* select's trigger. | ||
* TODO(kara): Add input property alternative for node envs. | ||
*/ | ||
get viewValue(): string { | ||
return this._getHostElement().textContent.trim(); | ||
} | ||
|
||
/** Selects the option. */ | ||
select(): void { | ||
this._selected = true; | ||
this.onSelect.emit(); | ||
} | ||
|
||
/** Deselects the option. */ | ||
deselect(): void { | ||
this._selected = false; | ||
} | ||
|
||
/** Sets focus onto this option. */ | ||
focus(): void { | ||
this._renderer.invokeElementMethod(this._getHostElement(), 'focus'); | ||
} | ||
|
||
/** Ensures the option is selected when activated from the keyboard. */ | ||
_handleKeydown(event: KeyboardEvent): void { | ||
if (event.keyCode === ENTER || event.keyCode === SPACE) { | ||
this.select(); | ||
} | ||
} | ||
|
||
_getHostElement(): HTMLElement { | ||
return this._element.nativeElement; | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
import { | ||
animate, | ||
AnimationEntryMetadata, | ||
state, | ||
style, | ||
transition, | ||
trigger, | ||
} from '@angular/core'; | ||
|
||
/** | ||
* The following are all the animations for the md-select component, with each | ||
* const containing the metadata for one animation. | ||
* | ||
* The values below match the implementation of the Material 1 md-select animation. | ||
*/ | ||
|
||
/** | ||
* This animation shrinks the placeholder text to 75% of its normal size and translates | ||
* it to either the top left corner (ltr) or top right corner (rtl) of the trigger, | ||
* depending on the text direction of the application. | ||
*/ | ||
export const transformPlaceholder: AnimationEntryMetadata = trigger('transformPlaceholder', [ | ||
state('normal', style({ | ||
transform: `translate3d(0, 0, 0) scale(1)` | ||
})), | ||
state('floating-ltr', style({ | ||
transform: `translate3d(-2px, -22px, 0) scale(0.75)` | ||
})), | ||
state('floating-rtl', style({ | ||
transform: `translate3d(2px, -22px, 0) scale(0.75)` | ||
})), | ||
transition('* => *', animate(`400ms cubic-bezier(0.25, 0.8, 0.25, 1)`)) | ||
]); | ||
|
||
/** | ||
* This animation transforms the select's overlay panel on and off the page. | ||
* | ||
* When the panel is attached to the DOM, it expands its width 32px, scales it up to | ||
* 100% on the Y axis, fades in its border, and translates slightly up and to the | ||
* side to ensure the option text correctly overlaps the trigger text. | ||
* | ||
* When the panel is removed from the DOM, it simply fades out linearly. | ||
*/ | ||
export const transformPanel: AnimationEntryMetadata = trigger('transformPanel', [ | ||
state('showing-ltr', style({ | ||
opacity: 1, | ||
width: 'calc(100% + 32px)', | ||
transform: `translate3d(-16px, -9px, 0) scaleY(1)` | ||
})), | ||
state('showing-rtl', style({ | ||
opacity: 1, | ||
width: 'calc(100% + 32px)', | ||
transform: `translate3d(16px, -9px, 0) scaleY(1)` | ||
})), | ||
transition('void => *', [ | ||
style({ | ||
opacity: 0, | ||
width: '100%', | ||
transform: `translate3d(0, 0, 0) scaleY(0)` | ||
}), | ||
animate(`150ms cubic-bezier(0.55, 0, 0.55, 0.2)`) | ||
]), | ||
transition('* => void', [ | ||
animate('250ms linear', style({opacity: 0})) | ||
]) | ||
]); | ||
|
||
/** | ||
* This animation fades in the background color and text content of the | ||
* select's options. It is time delayed to occur 100ms after the overlay | ||
* panel has transformed in. | ||
*/ | ||
export const fadeInContent: AnimationEntryMetadata = trigger('fadeInContent', [ | ||
state('showing', style({opacity: 1})), | ||
transition('void => showing', [ | ||
style({opacity: 0}), | ||
animate(`150ms 100ms cubic-bezier(0.55, 0, 0.55, 0.2)`) | ||
]) | ||
]); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,15 @@ | ||
I'm a select! | ||
<div class="md-select-trigger" overlay-origin (click)="toggle()" #origin="overlayOrigin" #trigger> | ||
<span class="md-select-placeholder" [@transformPlaceholder]="_getPlaceholderState()"> {{ placeholder }} </span> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are you reusing any of the placeholder css from There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, not directly. Since the input using CSS transitions and not the Angular animations API, it's hard to share. There wasn't much left over to copy after that. In the next PR (for forms), I can reuse more of the input CSS (for things like the underline, etc). |
||
<span class="md-select-value" *ngIf="selected"> {{ selected?.viewValue }} </span> | ||
<span class="md-select-arrow"></span> | ||
</div> | ||
|
||
<template connected-overlay [origin]="origin" [open]="panelOpen" hasBackdrop (backdropClick)="close()" | ||
backdropClass="md-overlay-transparent-backdrop" [positions]="_positions" [width]="_getWidth()"> | ||
<div class="md-select-panel" [@transformPanel]="_getPanelState()" (@transformPanel.done)="_onPanelDone()" | ||
(keydown)="_keyManager.onKeydown($event)"> | ||
<div class="md-select-content" [@fadeInContent]="'showing'"> | ||
<ng-content></ng-content> | ||
</div> | ||
</div> | ||
</template> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
@import '../core/style/menu-common'; | ||
|
||
$md-select-trigger-height: 30px !default; | ||
$md-select-trigger-min-width: 112px !default; | ||
$md-select-arrow-size: 5px !default; | ||
|
||
md-select { | ||
display: inline-block; | ||
outline: none; | ||
} | ||
|
||
.md-select-trigger { | ||
display: flex; | ||
justify-content: space-between; | ||
align-items: center; | ||
height: $md-select-trigger-height; | ||
min-width: $md-select-trigger-min-width; | ||
cursor: pointer; | ||
} | ||
|
||
.md-select-placeholder { | ||
padding: 0 2px; | ||
transform-origin: left top; | ||
|
||
[dir='rtl'] & { | ||
transform-origin: right top; | ||
} | ||
} | ||
|
||
.md-select-value { | ||
position: absolute; | ||
} | ||
|
||
.md-select-arrow { | ||
width: 0; | ||
height: 0; | ||
border-left: $md-select-arrow-size solid transparent; | ||
border-right: $md-select-arrow-size solid transparent; | ||
border-top: $md-select-arrow-size solid; | ||
} | ||
|
||
.md-select-panel { | ||
@include md-menu-base(); | ||
padding-top: 0; | ||
padding-bottom: 0; | ||
transform-origin: top; | ||
} | ||
|
||
md-option { | ||
@include md-menu-item-base(); | ||
position: relative; | ||
cursor: pointer; | ||
outline: none; | ||
} | ||
|
||
.md-option-ripple { | ||
position: absolute; | ||
top: 0; | ||
left: 0; | ||
bottom: 0; | ||
right: 0; | ||
} |
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.
Add
md-option
tostyle-compatibility.ts