diff --git a/src/demo-app/autocomplete/autocomplete-demo.html b/src/demo-app/autocomplete/autocomplete-demo.html index 0745c4ac4ea0..3ab4a1eaabd5 100644 --- a/src/demo-app/autocomplete/autocomplete-demo.html +++ b/src/demo-app/autocomplete/autocomplete-demo.html @@ -1,3 +1,9 @@
- + + + + + + {{ state.name }} +
diff --git a/src/demo-app/autocomplete/autocomplete-demo.ts b/src/demo-app/autocomplete/autocomplete-demo.ts index b89197833174..c06a099fd343 100644 --- a/src/demo-app/autocomplete/autocomplete-demo.ts +++ b/src/demo-app/autocomplete/autocomplete-demo.ts @@ -6,4 +6,33 @@ import {Component} from '@angular/core'; templateUrl: 'autocomplete-demo.html', styleUrls: ['autocomplete-demo.css'], }) -export class AutocompleteDemo {} +export class AutocompleteDemo { + states = [ + {code: 'AL', name: 'Alabama'}, + {code: 'AZ', name: 'Arizona'}, + {code: 'CA', name: 'California'}, + {code: 'CO', name: 'Colorado'}, + {code: 'CT', name: 'Connecticut'}, + {code: 'FL', name: 'Florida'}, + {code: 'GA', name: 'Georgia'}, + {code: 'ID', name: 'Idaho'}, + {code: 'KS', name: 'Kansas'}, + {code: 'LA', name: 'Louisiana'}, + {code: 'MA', name: 'Massachusetts'}, + {code: 'MN', name: 'Minnesota'}, + {code: 'MI', name: 'Mississippi'}, + {code: 'NY', name: 'New York'}, + {code: 'NC', name: 'North Carolina'}, + {code: 'OK', name: 'Oklahoma'}, + {code: 'OH', name: 'Ohio'}, + {code: 'OR', name: 'Oregon'}, + {code: 'PA', name: 'Pennsylvania'}, + {code: 'SC', name: 'South Carolina'}, + {code: 'TN', name: 'Tennessee'}, + {code: 'TX', name: 'Texas'}, + {code: 'VA', name: 'Virginia'}, + {code: 'WA', name: 'Washington'}, + {code: 'WI', name: 'Wisconsin'}, + {code: 'WY', name: 'Wyoming'}, + ]; +} diff --git a/src/lib/autocomplete/_autocomplete-theme.scss b/src/lib/autocomplete/_autocomplete-theme.scss index b77fff253e0c..5d0493df039c 100644 --- a/src/lib/autocomplete/_autocomplete-theme.scss +++ b/src/lib/autocomplete/_autocomplete-theme.scss @@ -1,5 +1,16 @@ @import '../core/theming/theming'; @mixin md-autocomplete-theme($theme) { + $foreground: map-get($theme, foreground); + $background: map-get($theme, background); + md-option { + background: md-color($background, card); + color: md-color($foreground, text); + + &.md-selected { + background: md-color($background, card); + color: md-color($foreground, text); + } + } } \ No newline at end of file diff --git a/src/lib/autocomplete/autocomplete-trigger.ts b/src/lib/autocomplete/autocomplete-trigger.ts new file mode 100644 index 000000000000..c037b403a096 --- /dev/null +++ b/src/lib/autocomplete/autocomplete-trigger.ts @@ -0,0 +1,116 @@ +import {Directive, ElementRef, Input, ViewContainerRef, OnDestroy} from '@angular/core'; +import {Overlay, OverlayRef, OverlayState, TemplatePortal} from '../core'; +import {MdAutocomplete} from './autocomplete'; +import {PositionStrategy} from '../core/overlay/position/position-strategy'; +import {Observable} from 'rxjs/Observable'; +import {Subscription} from 'rxjs/Subscription'; +import 'rxjs/add/observable/merge'; + +/** The panel needs a slight y-offset to ensure the input underline displays. */ +export const MD_AUTOCOMPLETE_PANEL_OFFSET = 6; + +@Directive({ + selector: 'input[mdAutocomplete], input[matAutocomplete]', + host: { + '(focus)': 'openPanel()' + } +}) +export class MdAutocompleteTrigger implements OnDestroy { + private _overlayRef: OverlayRef; + private _portal: TemplatePortal; + private _panelOpen: boolean = false; + private _closeWatcher: Subscription; + + /* The autocomplete panel to be attached to this trigger. */ + @Input('mdAutocomplete') autocomplete: MdAutocomplete; + + constructor(private _element: ElementRef, private _overlay: Overlay, + private _vcr: ViewContainerRef) {} + + ngOnDestroy() { this.destroyPanel(); } + + /* Whether or not the autocomplete panel is open. */ + get panelOpen(): boolean { + return this._panelOpen; + } + + /** Opens the autocomplete suggestion panel. */ + openPanel(): void { + if (!this._overlayRef) { + this._createOverlay(); + } + + if (!this._overlayRef.hasAttached()) { + this._overlayRef.attach(this._portal); + this._watchForClose(); + } + + this._panelOpen = true; + } + + /** Closes the autocomplete suggestion panel. */ + closePanel(): void { + if (this._overlayRef && this._overlayRef.hasAttached()) { + this._overlayRef.detach(); + } + + this._closeWatcher.unsubscribe(); + this._panelOpen = false; + } + + /** Destroys the autocomplete suggestion panel. */ + destroyPanel(): void { + if (this._overlayRef) { + this.closePanel(); + this._overlayRef.dispose(); + this._overlayRef = null; + } + } + + /** + * This method will close the panel if it receives a selection event from any of the options + * or a click on the backdrop. + */ + private _watchForClose() { + // TODO(kara): add tab event watcher when adding keyboard events + this._closeWatcher = Observable.merge(...this._getOptionObs(), this._overlayRef.backdropClick()) + .subscribe(() => this.closePanel()); + } + + /** + * This method maps all the autocomplete's child options into a flattened list + * of their selection events. This map will be used to merge the option events and + * the backdrop click into one observable. + */ + private _getOptionObs(): Observable[] { + return this.autocomplete.options.map((option) => option.onSelect); + } + + private _createOverlay(): void { + this._portal = new TemplatePortal(this.autocomplete.template, this._vcr); + this._overlayRef = this._overlay.create(this._getOverlayConfig()); + } + + private _getOverlayConfig(): OverlayState { + const overlayState = new OverlayState(); + overlayState.positionStrategy = this._getOverlayPosition(); + overlayState.width = this._getHostWidth(); + overlayState.hasBackdrop = true; + overlayState.backdropClass = 'md-overlay-transparent-backdrop'; + return overlayState; + } + + private _getOverlayPosition(): PositionStrategy { + return this._overlay.position().connectedTo( + this._element, + {originX: 'start', originY: 'bottom'}, {overlayX: 'start', overlayY: 'top'}) + .withOffsetY(MD_AUTOCOMPLETE_PANEL_OFFSET); + } + + /** Returns the width of the input element, so the panel width can match it. */ + private _getHostWidth(): number { + return this._element.nativeElement.getBoundingClientRect().width; + } + +} + diff --git a/src/lib/autocomplete/autocomplete.html b/src/lib/autocomplete/autocomplete.html index f0d1cbc032a0..97727158af0c 100644 --- a/src/lib/autocomplete/autocomplete.html +++ b/src/lib/autocomplete/autocomplete.html @@ -1 +1,5 @@ -I'm an autocomplete! \ No newline at end of file + \ No newline at end of file diff --git a/src/lib/autocomplete/autocomplete.scss b/src/lib/autocomplete/autocomplete.scss index e69de29bb2d1..d6c9b0162d35 100644 --- a/src/lib/autocomplete/autocomplete.scss +++ b/src/lib/autocomplete/autocomplete.scss @@ -0,0 +1,5 @@ +@import '../core/style/menu-common'; + +.md-autocomplete-panel { + @include md-menu-base(); +} \ No newline at end of file diff --git a/src/lib/autocomplete/autocomplete.spec.ts b/src/lib/autocomplete/autocomplete.spec.ts index 158a6cf263d6..8af275692b3a 100644 --- a/src/lib/autocomplete/autocomplete.spec.ts +++ b/src/lib/autocomplete/autocomplete.spec.ts @@ -1,29 +1,188 @@ -import {TestBed, async} from '@angular/core/testing'; -import {Component} from '@angular/core'; -import {MdAutocompleteModule} from './index'; +import {TestBed, async, ComponentFixture} from '@angular/core/testing'; +import {Component, ViewChild} from '@angular/core'; +import {By} from '@angular/platform-browser'; +import {MdAutocompleteModule, MdAutocompleteTrigger} from './index'; +import {OverlayContainer} from '../core/overlay/overlay-container'; +import {MdInputModule} from '../input/index'; describe('MdAutocomplete', () => { + let overlayContainerElement: HTMLElement; beforeEach(async(() => { TestBed.configureTestingModule({ - imports: [MdAutocompleteModule.forRoot()], + imports: [MdAutocompleteModule.forRoot(), MdInputModule.forRoot()], declarations: [SimpleAutocomplete], - providers: [] + providers: [ + {provide: OverlayContainer, useFactory: () => { + overlayContainerElement = document.createElement('div'); + + // add fixed positioning to match real overlay container styles + overlayContainerElement.style.position = 'fixed'; + overlayContainerElement.style.top = '0'; + overlayContainerElement.style.left = '0'; + document.body.appendChild(overlayContainerElement); + + // remove body padding to keep consistent cross-browser + document.body.style.padding = '0'; + document.body.style.margin = '0'; + + return {getContainerElement: () => overlayContainerElement}; + }}, + ] }); TestBed.compileComponents(); })); - it('should have a test', () => { - expect(true).toBe(true); + describe('panel toggling', () => { + let fixture: ComponentFixture; + let trigger: HTMLElement; + + beforeEach(() => { + fixture = TestBed.createComponent(SimpleAutocomplete); + fixture.detectChanges(); + + trigger = fixture.debugElement.query(By.css('input')).nativeElement; + }); + + it('should open the panel when the input is focused', () => { + expect(fixture.componentInstance.trigger.panelOpen).toBe(false); + dispatchEvent('focus', trigger); + fixture.detectChanges(); + + expect(fixture.componentInstance.trigger.panelOpen) + .toBe(true, `Expected panel state to read open when input is focused.`); + expect(overlayContainerElement.textContent) + .toContain('Alabama', `Expected panel to display when input is focused.`); + expect(overlayContainerElement.textContent) + .toContain('California', `Expected panel to display when input is focused.`); + }); + + it('should open the panel programmatically', () => { + expect(fixture.componentInstance.trigger.panelOpen).toBe(false); + fixture.componentInstance.trigger.openPanel(); + fixture.detectChanges(); + + expect(fixture.componentInstance.trigger.panelOpen) + .toBe(true, `Expected panel state to read open when opened programmatically.`); + expect(overlayContainerElement.textContent) + .toContain('Alabama', `Expected panel to display when opened programmatically.`); + expect(overlayContainerElement.textContent) + .toContain('California', `Expected panel to display when opened programmatically.`); + }); + + it('should close the panel when a click occurs outside it', async(() => { + dispatchEvent('focus', trigger); + fixture.detectChanges(); + + const backdrop = overlayContainerElement.querySelector('.cdk-overlay-backdrop'); + backdrop.click(); + fixture.detectChanges(); + + fixture.whenStable().then(() => { + expect(fixture.componentInstance.trigger.panelOpen) + .toBe(false, `Expected clicking outside the panel to set its state to closed.`); + expect(overlayContainerElement.textContent) + .toEqual('', `Expected clicking outside the panel to close the panel.`); + }); + })); + + it('should close the panel when an option is clicked', async(() => { + dispatchEvent('focus', trigger); + fixture.detectChanges(); + + const option = overlayContainerElement.querySelector('md-option'); + option.click(); + fixture.detectChanges(); + + fixture.whenStable().then(() => { + expect(fixture.componentInstance.trigger.panelOpen) + .toBe(false, `Expected clicking an option to set the panel state to closed.`); + expect(overlayContainerElement.textContent) + .toEqual('', `Expected clicking an option to close the panel.`); + }); + })); + + it('should close the panel when a newly created option is clicked', async(() => { + fixture.componentInstance.states.unshift({code: 'TEST', name: 'test'}); + fixture.detectChanges(); + + dispatchEvent('focus', trigger); + fixture.detectChanges(); + + const option = overlayContainerElement.querySelector('md-option'); + option.click(); + fixture.detectChanges(); + + fixture.whenStable().then(() => { + expect(fixture.componentInstance.trigger.panelOpen) + .toBe(false, `Expected clicking a new option to set the panel state to closed.`); + expect(overlayContainerElement.textContent) + .toEqual('', `Expected clicking a new option to close the panel.`); + }); + })); + + it('should close the panel programmatically', async(() => { + fixture.componentInstance.trigger.openPanel(); + fixture.detectChanges(); + + fixture.componentInstance.trigger.closePanel(); + fixture.detectChanges(); + + fixture.whenStable().then(() => { + expect(fixture.componentInstance.trigger.panelOpen) + .toBe(false, `Expected closing programmatically to set the panel state to closed.`); + expect(overlayContainerElement.textContent) + .toEqual('', `Expected closing programmatically to close the panel.`); + }); + })); + }); }); @Component({ template: ` - + + + + + + {{ state.name }} + ` }) -class SimpleAutocomplete {} +class SimpleAutocomplete { + @ViewChild(MdAutocompleteTrigger) trigger: MdAutocompleteTrigger; + + states = [ + {code: 'AL', name: 'Alabama'}, + {code: 'CA', name: 'California'}, + {code: 'FL', name: 'Florida'}, + {code: 'KS', name: 'Kansas'}, + {code: 'MA', name: 'Massachusetts'}, + {code: 'NY', name: 'New York'}, + {code: 'OR', name: 'Oregon'}, + {code: 'PA', name: 'Pennsylvania'}, + {code: 'TN', name: 'Tennessee'}, + {code: 'VA', name: 'Virginia'}, + {code: 'WY', name: 'Wyoming'}, + ]; +} + + +/** + * TODO: Move this to core testing utility until Angular has event faking + * support. + * + * Dispatches an event from an element. + * @param eventName Name of the event + * @param element The element from which the event will be dispatched. + */ +function dispatchEvent(eventName: string, element: HTMLElement): void { + let event = document.createEvent('Event'); + event.initEvent(eventName, true, true); + element.dispatchEvent(event); +} + diff --git a/src/lib/autocomplete/autocomplete.ts b/src/lib/autocomplete/autocomplete.ts index 18545cb2c6e2..bb2abbca20b4 100644 --- a/src/lib/autocomplete/autocomplete.ts +++ b/src/lib/autocomplete/autocomplete.ts @@ -1,4 +1,12 @@ -import {Component, ViewEncapsulation} from '@angular/core'; +import { + Component, + ContentChildren, + QueryList, + TemplateRef, + ViewChild, + ViewEncapsulation +} from '@angular/core'; +import {MdOption} from '../core'; @Component({ moduleId: module.id, @@ -6,6 +14,11 @@ import {Component, ViewEncapsulation} from '@angular/core'; templateUrl: 'autocomplete.html', styleUrls: ['autocomplete.css'], encapsulation: ViewEncapsulation.None, + exportAs: 'mdAutocomplete' }) -export class MdAutocomplete {} +export class MdAutocomplete { + + @ViewChild(TemplateRef) template: TemplateRef; + @ContentChildren(MdOption) options: QueryList; +} diff --git a/src/lib/autocomplete/index.ts b/src/lib/autocomplete/index.ts index 92bb41a39a65..a9c4eec768fc 100644 --- a/src/lib/autocomplete/index.ts +++ b/src/lib/autocomplete/index.ts @@ -1,18 +1,24 @@ import {ModuleWithProviders, NgModule} from '@angular/core'; -import {DefaultStyleCompatibilityModeModule} from '../core'; +import { + MdOptionModule, OverlayModule, OVERLAY_PROVIDERS, DefaultStyleCompatibilityModeModule +} from '../core'; import {MdAutocomplete} from './autocomplete'; +import {MdAutocompleteTrigger} from './autocomplete-trigger'; export * from './autocomplete'; +export * from './autocomplete-trigger'; @NgModule({ - imports: [DefaultStyleCompatibilityModeModule], - exports: [MdAutocomplete, DefaultStyleCompatibilityModeModule], - declarations: [MdAutocomplete], + imports: [MdOptionModule, OverlayModule, DefaultStyleCompatibilityModeModule], + exports: [ + MdAutocomplete, MdOptionModule, MdAutocompleteTrigger, DefaultStyleCompatibilityModeModule + ], + declarations: [MdAutocomplete, MdAutocompleteTrigger], }) export class MdAutocompleteModule { static forRoot(): ModuleWithProviders { return { ngModule: MdAutocompleteModule, - providers: [] + providers: [OVERLAY_PROVIDERS] }; } } diff --git a/src/lib/core/_core.scss b/src/lib/core/_core.scss index c573e0f44438..0df852437e9e 100644 --- a/src/lib/core/_core.scss +++ b/src/lib/core/_core.scss @@ -3,7 +3,8 @@ @import 'style/elevation'; @import 'overlay/overlay'; @import 'ripple/ripple'; - +@import 'option/option'; +@import 'option/option-theme'; // Mixin that renders all of the core styles that are not theme-dependent. @mixin md-core() { @@ -17,6 +18,7 @@ } @include md-ripple(); + @include md-option(); @include cdk-a11y(); @include cdk-overlay(); } @@ -24,4 +26,5 @@ // Mixin that renders all of the core styles that depend on the theme. @mixin md-core-theme($theme) { @include md-ripple-theme($theme); + @include md-option-theme($theme); } diff --git a/src/lib/core/core.ts b/src/lib/core/core.ts index 8943114da921..062c430e90fb 100644 --- a/src/lib/core/core.ts +++ b/src/lib/core/core.ts @@ -2,6 +2,7 @@ import {NgModule, ModuleWithProviders} from '@angular/core'; import {MdLineModule} from './line/line'; import {RtlModule} from './rtl/dir'; import {ObserveContentModule} from './observe-content/observe-content'; +import {MdOptionModule} from './option/option'; import {MdRippleModule} from './ripple/ripple'; import {PortalModule} from './portal/portal-directives'; import {OverlayModule} from './overlay/overlay-directives'; @@ -15,6 +16,8 @@ export {Dir, LayoutDirection, RtlModule} from './rtl/dir'; // Mutation Observer export {ObserveContentModule, ObserveContent} from './observe-content/observe-content'; +export {MdOptionModule, MdOption} from './option/option'; + // Portals export { Portal, @@ -125,6 +128,7 @@ export {NoConflictStyleCompatibilityMode} from './compatibility/no-conflict-mode PortalModule, OverlayModule, A11yModule, + MdOptionModule ], exports: [ MdLineModule, @@ -134,6 +138,7 @@ export {NoConflictStyleCompatibilityMode} from './compatibility/no-conflict-mode PortalModule, OverlayModule, A11yModule, + MdOptionModule ], }) export class MdCoreModule { diff --git a/src/lib/core/option/_option-theme.scss b/src/lib/core/option/_option-theme.scss new file mode 100644 index 000000000000..019f1eb46847 --- /dev/null +++ b/src/lib/core/option/_option-theme.scss @@ -0,0 +1,23 @@ +@import '../theming/palette'; +@import '../theming/theming'; + +@mixin md-option-theme($theme) { + $foreground: map-get($theme, foreground); + $background: map-get($theme, background); + + md-option { + &:hover:not(.md-option-disabled), &:focus:not(.md-option-disabled) { + background: md-color($background, hover); + } + + &.md-selected { + background: md-color($background, hover); + color: md-color($primary); + } + + &.md-option-disabled { + color: md-color($foreground, hint-text); + } + + } +} \ No newline at end of file diff --git a/src/lib/select/option.html b/src/lib/core/option/option.html similarity index 100% rename from src/lib/select/option.html rename to src/lib/core/option/option.html diff --git a/src/lib/core/option/option.scss b/src/lib/core/option/option.scss new file mode 100644 index 000000000000..3409bf886c3a --- /dev/null +++ b/src/lib/core/option/option.scss @@ -0,0 +1,30 @@ +@import '../style/menu-common'; +@import '../a11y/a11y'; + +@mixin md-option() { + md-option { + @include md-menu-item-base(); + position: relative; + cursor: pointer; + outline: none; + + &[aria-disabled='true'] { + cursor: default; + user-select: none; + } + } + + .md-option-ripple { + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + + // In high contrast mode this completely covers the text. + @include cdk-high-contrast { + opacity: 0.5; + } + } +} + diff --git a/src/lib/select/option.ts b/src/lib/core/option/option.ts similarity index 83% rename from src/lib/select/option.ts rename to src/lib/core/option/option.ts index ef56987a39b6..5eaae88e4ced 100644 --- a/src/lib/select/option.ts +++ b/src/lib/core/option/option.ts @@ -4,11 +4,15 @@ import { EventEmitter, Input, Output, + NgModule, + ModuleWithProviders, Renderer, ViewEncapsulation } from '@angular/core'; -import {ENTER, SPACE} from '../core/keyboard/keycodes'; -import {coerceBooleanProperty} from '../core/coercion/boolean-property'; +import {CommonModule} from '@angular/common'; +import {ENTER, SPACE} from '../keyboard/keycodes'; +import {coerceBooleanProperty} from '../coercion/boolean-property'; +import {MdRippleModule} from '../ripple/ripple'; /** * Option IDs need to be unique across components, so this counter exists outside of @@ -34,7 +38,7 @@ let _uniqueIdCounter = 0; '(keydown)': '_handleKeydown($event)' }, templateUrl: 'option.html', - styleUrls: ['select.css'], + styleUrls: ['option.css'], encapsulation: ViewEncapsulation.None }) export class MdOption { @@ -43,7 +47,7 @@ export class MdOption { /** Whether the option is disabled. */ private _disabled: boolean = false; - private _id: string = `md-select-option-${_uniqueIdCounter++}`; + private _id: string = `md-option-${_uniqueIdCounter++}`; /** The unique ID of the option. */ get id() { return this._id; } @@ -98,7 +102,6 @@ export class MdOption { } } - /** * Selects the option while indicating the selection came from the user. Used to * determine if the select's view -> model callback should be invoked. @@ -120,3 +123,17 @@ export class MdOption { } } + +@NgModule({ + imports: [MdRippleModule, CommonModule], + exports: [MdOption], + declarations: [MdOption] +}) +export class MdOptionModule { + static forRoot(): ModuleWithProviders { + return { + ngModule: MdOptionModule, + providers: [] + }; + } +} diff --git a/src/lib/select/_select-theme.scss b/src/lib/select/_select-theme.scss index 43a046737cca..847a0728e474 100644 --- a/src/lib/select/_select-theme.scss +++ b/src/lib/select/_select-theme.scss @@ -45,20 +45,4 @@ color: md-color($foreground, hint-text); } } - - md-option { - &:hover:not(.md-option-disabled), &:focus:not(.md-option-disabled) { - background: md-color($background, hover); - } - - &.md-selected { - background: md-color($background, hover); - color: md-color($primary); - } - - &.md-option-disabled { - color: md-color($foreground, hint-text); - } - - } } diff --git a/src/lib/select/index.ts b/src/lib/select/index.ts index a79c2a571bd7..a709f6792ead 100644 --- a/src/lib/select/index.ts +++ b/src/lib/select/index.ts @@ -1,22 +1,20 @@ import {NgModule, ModuleWithProviders} from '@angular/core'; import {CommonModule} from '@angular/common'; import {MdSelect} from './select'; -import {MdOption} from './option'; +import {MdOptionModule} from '../core/option/option'; import { DefaultStyleCompatibilityModeModule, OVERLAY_PROVIDERS, - MdRippleModule, OverlayModule, } from '../core'; export * from './select'; -export {MdOption} from './option'; export {fadeInContent, transformPanel, transformPlaceholder} from './select-animations'; @NgModule({ - imports: [CommonModule, OverlayModule, MdRippleModule, DefaultStyleCompatibilityModeModule], - exports: [MdSelect, MdOption, DefaultStyleCompatibilityModeModule], - declarations: [MdSelect, MdOption], + imports: [CommonModule, OverlayModule, MdOptionModule, DefaultStyleCompatibilityModeModule], + exports: [MdSelect, MdOptionModule, DefaultStyleCompatibilityModeModule], + declarations: [MdSelect], }) export class MdSelectModule { static forRoot(): ModuleWithProviders { diff --git a/src/lib/select/select.scss b/src/lib/select/select.scss index 04ca67077ebd..5c05072044a8 100644 --- a/src/lib/select/select.scss +++ b/src/lib/select/select.scss @@ -105,27 +105,3 @@ md-select { } } -md-option { - @include md-menu-item-base(); - position: relative; - cursor: pointer; - outline: none; - - &[aria-disabled='true'] { - cursor: default; - user-select: none; - } -} - -.md-option-ripple { - position: absolute; - top: 0; - left: 0; - bottom: 0; - right: 0; - - // In high contrast mode this completely covers the text. - @include cdk-high-contrast { - opacity: 0.5; - } -} diff --git a/src/lib/select/select.spec.ts b/src/lib/select/select.spec.ts index f5e3f6b9f40b..c2e3e6a12d53 100644 --- a/src/lib/select/select.spec.ts +++ b/src/lib/select/select.spec.ts @@ -4,7 +4,7 @@ import {Component, DebugElement, QueryList, ViewChild, ViewChildren} from '@angu import {MdSelectModule} from './index'; import {OverlayContainer} from '../core/overlay/overlay-container'; import {MdSelect} from './select'; -import {MdOption} from './option'; +import {MdOption} from '../core/option/option'; import {Dir} from '../core/rtl/dir'; import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms'; import {ViewportRuler} from '../core/overlay/position/viewport-ruler'; @@ -587,6 +587,7 @@ describe('MdSelect', () => { select.style.marginRight = '20px'; }); + it('should align the first option with the trigger text if no option is selected', () => { trigger.click(); fixture.detectChanges(); @@ -1081,7 +1082,7 @@ describe('MdSelect', () => { let firstOptionID = options[0].id; expect(options[0].id) - .toContain('md-select-option', `Expected option ID to have the correct prefix.`); + .toContain('md-option', `Expected option ID to have the correct prefix.`); expect(options[0].id).not.toEqual(options[1].id, `Expected option IDs to be unique.`); const backdrop = @@ -1096,7 +1097,7 @@ describe('MdSelect', () => { options = overlayContainerElement.querySelectorAll('md-option') as NodeListOf; expect(options[0].id) - .toContain('md-select-option', `Expected option ID to have the correct prefix.`); + .toContain('md-option', `Expected option ID to have the correct prefix.`); expect(options[0].id).not.toEqual(firstOptionID, `Expected option IDs to be unique.`); expect(options[0].id).not.toEqual(options[1].id, `Expected option IDs to be unique.`); }); @@ -1219,7 +1220,7 @@ class ManySelects {} - ` + `, }) class NgIfSelect { diff --git a/src/lib/select/select.ts b/src/lib/select/select.ts index e2de6c548829..8f408c1f15fa 100644 --- a/src/lib/select/select.ts +++ b/src/lib/select/select.ts @@ -13,7 +13,7 @@ import { ViewEncapsulation, ViewChild, } from '@angular/core'; -import {MdOption} from './option'; +import {MdOption} from '../core/option/option'; import {ENTER, SPACE} from '../core/keyboard/keycodes'; import {ListKeyManager} from '../core/a11y/list-key-manager'; import {Dir} from '../core/rtl/dir'; diff --git a/tools/gulp/tasks/components.ts b/tools/gulp/tasks/components.ts index 8e71f9d921a1..64102458d0d1 100644 --- a/tools/gulp/tasks/components.ts +++ b/tools/gulp/tasks/components.ts @@ -75,6 +75,7 @@ task(':build:components:rollup', () => { 'rxjs/add/observable/fromEvent': 'Rx.Observable', 'rxjs/add/observable/forkJoin': 'Rx.Observable', 'rxjs/add/observable/of': 'Rx.Observable', + 'rxjs/add/observable/merge': 'Rx.Observable', 'rxjs/add/observable/throw': 'Rx.Observable', 'rxjs/add/operator/toPromise': 'Rx.Observable.prototype', 'rxjs/add/operator/map': 'Rx.Observable.prototype',