From 114cbd9996f2ae4bb021aa5f13b7a9d533c87226 Mon Sep 17 00:00:00 2001 From: Lin Sun <2808411862@qq.com> Date: Fri, 16 Jun 2017 11:12:09 -0700 Subject: [PATCH 01/13] # This is a combination of 4 commits. # This is the 1st commit message: add lib files for sticky-header add chose parent add support to 'optional 'cdkStickyRegion' input ' add app-demo for sticky-header fix bugs and deleted unused tag id in HTML files modify fix some code according to PR review comments change some format to pass TSlint check add '_' before private elements delete @Injectable for StickyHeaderDirective. Because we do not need @Injectable refine code encapsulate 'set style for element' change @Input() Delete 'Observable.fromEvent(this.upperScrollableContainer, 'scroll')' add const STICK_START_CLASS and STICK_END_CLASS Add doc for [cdkStickyRegion] and 'unstuckElement()'. Delete 'detach()' function, add its content into 'ngOnDestroy()'. change 'MdStickyHeaderModule' to 'CdkStickyHeaderModule'; encapsulate reset css style operation for sticky header. delete unnecessary gloable variables sticky-header-rebased add one test && fixed bug 'sticky-header not working on iPhone', and bug 'reshape too obviously when being sticked'. && made some change according to the design doc('md-stick --> cdkSticky') make the sticky element being scrolled up smoothly when being unsticked Add code to support optional parentRegion input add add unit test for sticky-header add e2e/sticky-header files e2e test clear code master sticky-header-rebased Add code to support optional parentRegion input add e2e/sticky-header files e2e test clear code fix encapsulate reset css style operation for sticky header. sticky-header-rebased Add code to support optional parentRegion input add e2e/sticky-header files e2e test clear code master sticky-header-rebased Add code to support optional parentRegion input add e2e/sticky-header files e2e test clear code fix delete unused files encapsulate reset css style operation for sticky header. sticky-header-rebased Add code to support optional parentRegion input add unit test for sticky-header add e2e/sticky-header files e2e test clear code master sticky-header-rebased Add code to support optional parentRegion input add e2e/sticky-header files e2e test clear code fix encapsulate reset css style operation for sticky header. sticky-header-rebased Add code to support optional parentRegion input add e2e/sticky-header files e2e test clear code master sticky-header-rebased Add code to support optional parentRegion input add e2e/sticky-header files e2e test clear code fix clean unused files delete sticky-header delete styicky-heade demo delete sticky-header lib revert public_api delete sticky-header e2e files delete.firebasesrc revert revert revert revert add css styles for selection-list add selection-list structures in list.ts initial version of selection-list all src/lib code fix tslint error fix tslint error applied '[class.mat-list-item-content-reverse]="checkboxPosition == 'after'"', deleted comments in code Deleted 'MdSelectionListCssMatStyler' class Added doc for 'md-selection-option' Added binding expression for 'attr.aria-selected' and 'attr.aria-disabled' Added a value and a disabled @Input() property Changed 'isSelected' to 'selected'. And added '@Input(selected)' for it.(Using 'coerceBooleanProperty') changed '_slist' to 'selectionList' and deleted 'MdSelectionListCheckboxer' class Deleted navList in selection-list-option changed 'onchange()' to toggle(). And added doc for it checkedItems -> selectedOptions deleted 'pCheckbox' and store 'this' instead of HTMLelement in 'optionlist' optimized toggle() with 'this._selected = !this._selected' Added 'Tab' moves focus on selection-list, 'UP_ARROW' and 'DOWN_ARROW' moves focus within selection-list. And use 'SPACE' to select checkbox Added another empty line at the end of each file remove comment line in CSS file Deleted 'webkit-justify-content' in CSS file Deleted 'mat-list-item-content-reverse' to these existing styles; the 'mat-list-item-content-reverse' class should only have the extra styles you need to reverse the display Deleted 'mat-list-item-content' and 'mat-list-item-content-reverse' and '' styles in '.mat-selection-list' Deleted unused 'mat-checkbox' class add ".mat-list-option" class in css file added 'mat-list-option' class fix toggle() disabled Deleted empty class Deleted unused import Removed 'if (this.selectable)' check moved styles in '.mat-selection-list .mat-list-item' to '.mat-list-option'. Deleted position, box-sizing, and height in '.mat-list-item-content-reverse' in list.scss file put md-selection-list and md-list-option in separate files(selection-list.ts and list-option.ts) # This is the commit message #2: nit # This is the commit message #3: correct imports in index.ts # This is the commit message #4: use ' @ContentChildren(MdListOption) options;' instead of QueryList --- package-lock.json | 148 ++++++++++++++++++++- package.json | 2 +- src/lib/list/_list-theme.scss | 32 ++++- src/lib/list/index.ts | 14 +- src/lib/list/list-option.html | 10 ++ src/lib/list/list-option.ts | 154 ++++++++++++++++++++++ src/lib/list/list.scss | 35 ++++- src/lib/list/list.ts | 1 + src/lib/list/selection-list.ts | 229 +++++++++++++++++++++++++++++++++ 9 files changed, 609 insertions(+), 16 deletions(-) create mode 100644 src/lib/list/list-option.html create mode 100644 src/lib/list/list-option.ts create mode 100644 src/lib/list/selection-list.ts diff --git a/package-lock.json b/package-lock.json index 304ff5aebfff..5c090d588a6a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -531,6 +531,26 @@ "through2": "2.0.3" } }, + "@gulp-sourcemaps/identity-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/identity-map/-/identity-map-1.0.1.tgz", + "integrity": "sha1-z6I7xYQPkQTOMqZedNt+epdLvuE=", + "dev": true, + "dependencies": { + "acorn": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.0.3.tgz", + "integrity": "sha1-xGDfCEkUY/AozLguqzcwvwEIez0=", + "dev": true + } + } + }, + "@gulp-sourcemaps/map-sources": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/map-sources/-/map-sources-1.0.0.tgz", + "integrity": "sha1-iQrnxdjId/bThIYCFazp1+yUW9o=", + "dev": true + }, "@types/chalk": { "version": "0.4.31", "resolved": "https://registry.npmjs.org/@types/chalk/-/chalk-0.4.31.tgz", @@ -747,6 +767,11 @@ "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", "dev": true }, + "angular": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/angular/-/angular-1.6.4.tgz", + "integrity": "sha1-A7exXAGggC1+LPWTJA5gQFTcd/s=" + }, "ansi-align": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-1.1.0.tgz", @@ -1096,6 +1121,12 @@ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", "dev": true }, + "atob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/atob/-/atob-1.1.3.tgz", + "integrity": "sha1-lfE2KbEsOlGl0hWr3OKqnzL4B3M=", + "dev": true + }, "autoprefixer": { "version": "6.7.7", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-6.7.7.tgz", @@ -2251,6 +2282,12 @@ "trim-off-newlines": "1.0.1" } }, + "convert-source-map": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.0.tgz", + "integrity": "sha1-ms1whRxtXf3ZPZKC5e35SgP/RrU=", + "dev": true + }, "cookie": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", @@ -2437,6 +2474,20 @@ "uid-safe": "2.1.4" } }, + "css": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css/-/css-2.2.1.tgz", + "integrity": "sha1-c6TIHehdtmTU7mdPfUcIXjstVdw=", + "dev": true, + "dependencies": { + "source-map": { + "version": "0.1.43", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", + "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", + "dev": true + } + } + }, "css-color-names": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.3.tgz", @@ -2711,6 +2762,20 @@ "ms": "2.0.0" } }, + "debug-fabulous": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/debug-fabulous/-/debug-fabulous-0.1.0.tgz", + "integrity": "sha1-rQ6gel1RkyT7VYQqjzTuWcf4/2w=", + "dev": true, + "dependencies": { + "object-assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz", + "integrity": "sha1-ejs9DpgGPUP0wD8uiubNUahog6A=", + "dev": true + } + } + }, "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", @@ -2858,6 +2923,12 @@ "fs-exists-sync": "0.1.0" } }, + "detect-newline": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", + "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", + "dev": true + }, "detective-amd": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/detective-amd/-/detective-amd-2.4.0.tgz", @@ -8910,6 +8981,20 @@ "vinyl-sourcemaps-apply": "0.2.1" } }, + "gulp-sourcemaps": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/gulp-sourcemaps/-/gulp-sourcemaps-2.6.0.tgz", + "integrity": "sha1-fMzomaijv8oVk6M0jQ+/Qd0/UeU=", + "dev": true, + "dependencies": { + "acorn": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", + "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=", + "dev": true + } + } + }, "gulp-transform": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/gulp-transform/-/gulp-transform-2.0.0.tgz", @@ -9603,12 +9688,6 @@ "lower-case": "1.1.4" } }, - "is-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", - "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=", - "dev": true - }, "is-my-json-valid": { "version": "2.16.0", "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.16.0.tgz", @@ -13327,6 +13406,18 @@ "uuid": "3.1.0" } }, + "request-promise": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.1.tgz", + "integrity": "sha1-fuxWyJMXqCLL/qmbA5zlQ8LhX2c=", + "dev": true + }, + "request-promise-core": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.1.tgz", + "integrity": "sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY=", + "dev": true + }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -13452,6 +13543,12 @@ "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", "dev": true }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true + }, "response-time": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/response-time/-/response-time-2.3.2.tgz", @@ -14359,6 +14456,12 @@ "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=", "dev": true }, + "source-map-resolve": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.3.1.tgz", + "integrity": "sha1-YQ9hIqRFuN1RU1oqcbeD38Ekh2E=", + "dev": true + }, "source-map-support": { "version": "0.4.15", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.15.tgz", @@ -14368,6 +14471,12 @@ "source-map": "0.5.6" } }, + "source-map-url": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.3.0.tgz", + "integrity": "sha1-fsrxO1e80J2opAxdJp2zN5nUqvk=", + "dev": true + }, "sourcemap-codec": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.3.1.tgz", @@ -14495,6 +14604,12 @@ "readable-stream": "2.3.3" } }, + "stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", + "dev": true + }, "stream-combiner": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", @@ -14638,6 +14753,12 @@ "is-utf8": "0.2.1" } }, + "strip-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha1-5SEekiQ2n7uB1jOi8ABE3IztrZI=", + "dev": true + }, "strip-eof": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", @@ -15484,6 +15605,12 @@ "integrity": "sha1-YdvC1Ttp/2CRoSoWj9fUMxB+QPE=", "dev": true }, + "travis-after-modes": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/travis-after-modes/-/travis-after-modes-0.0.7.tgz", + "integrity": "sha1-o/PAWNPoaqp79Buwh8dduRRacYw=", + "dev": true + }, "trim-newlines": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", @@ -15607,7 +15734,8 @@ "tslib": { "version": "1.7.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.7.1.tgz", - "integrity": "sha1-vIAEFkaRkjp5/oN4u+s9ogF1OOw=" + "integrity": "sha1-vIAEFkaRkjp5/oN4u+s9ogF1OOw=", + "dev": true }, "tslint": { "version": "5.5.0", @@ -16027,6 +16155,12 @@ "upper-case": "1.1.3" } }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true + }, "url-join": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/url-join/-/url-join-0.0.1.tgz", diff --git a/package.json b/package.json index 77a9c2e020d1..cebdc5e813ff 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,7 @@ "highlight.js": "^9.11.0", "http-rewrite-middleware": "^0.1.6", "image-diff": "^1.6.3", - "jasmine-core": "^2.6.2", + "jasmine-core": "^2.6.3", "jsonwebtoken": "^7.4.1", "karma": "^1.7.0", "karma-browserstack-launcher": "^1.2.0", diff --git a/src/lib/list/_list-theme.scss b/src/lib/list/_list-theme.scss index 2355a23d5c80..e582e82eb8ba 100644 --- a/src/lib/list/_list-theme.scss +++ b/src/lib/list/_list-theme.scss @@ -8,11 +8,15 @@ $background: map-get($theme, background); $foreground: map-get($theme, foreground); - .mat-list, .mat-nav-list { + .mat-list, .mat-nav-list, .mat-selection-list { .mat-list-item { color: mat-color($foreground, text); } + .mat-list-option { + color: mat-color($foreground, text); + } + .mat-subheader { color: mat-color($foreground, secondary-text); } @@ -29,6 +33,14 @@ background: mat-color($background, 'hover'); } } + + .mat-list-option { + outline: none; + + &:hover, &.mat-list-item-focus { + background: mat-color($background, 'hover'); + } + } } @mixin mat-list-typography($config) { @@ -38,13 +50,22 @@ font-family: $font-family; } + .mat-list-option { + font-family: $font-family; + } + // Default list - .mat-list, .mat-nav-list { + .mat-list, .mat-nav-list, .mat-selection-list { .mat-list-item { font-size: mat-font-size($config, subheading-2); @include mat-line-base(mat-font-size($config, body-1)); } + .mat-list-option { + font-size: mat-font-size($config, subheading-2); + @include mat-line-base(mat-font-size($config, body-1)); + } + .mat-subheader { font-family: mat-font-family($config, body-2); font-size: mat-font-size($config, body-2); @@ -53,12 +74,17 @@ } // Dense list - .mat-list[dense], .mat-nav-list[dense] { + .mat-list[dense], .mat-nav-list[dense], .mat-selection-list[dense] { .mat-list-item { font-size: mat-font-size($config, caption); @include mat-line-base(mat-font-size($config, caption)); } + .mat-list-option { + font-size: mat-font-size($config, caption); + @include mat-line-base(mat-font-size($config, caption)); + } + .mat-subheader { font-family: $font-family; font-size: mat-font-size($config, caption); diff --git a/src/lib/list/index.ts b/src/lib/list/index.ts index 986b20a1f23a..6ac9c1fee208 100644 --- a/src/lib/list/index.ts +++ b/src/lib/list/index.ts @@ -7,7 +7,8 @@ */ import {NgModule} from '@angular/core'; -import {MdLineModule, MdRippleModule, MdCommonModule} from '../core'; +import {MdLineModule, MdRippleModule, MdCommonModule, MdSelectionModule} from '../core'; +import {CommonModule} from '@angular/common'; import { MdList, MdListItem, @@ -17,12 +18,14 @@ import { MdListCssMatStyler, MdNavListCssMatStyler, MdDividerCssMatStyler, - MdListSubheaderCssMatStyler, + MdListSubheaderCssMatStyler } from './list'; +import {MdSelectionList} from './selection-list'; +import {MdListOption} from './list-option'; @NgModule({ - imports: [MdLineModule, MdRippleModule, MdCommonModule], + imports: [MdLineModule, MdRippleModule, MdCommonModule, MdSelectionModule, CommonModule], exports: [ MdList, MdListItem, @@ -35,6 +38,9 @@ import { MdNavListCssMatStyler, MdDividerCssMatStyler, MdListSubheaderCssMatStyler, + MdSelectionModule, + MdSelectionList, + MdListOption ], declarations: [ MdList, @@ -46,6 +52,8 @@ import { MdNavListCssMatStyler, MdDividerCssMatStyler, MdListSubheaderCssMatStyler, + MdSelectionList, + MdListOption ], }) export class MdListModule {} diff --git a/src/lib/list/list-option.html b/src/lib/list/list-option.html new file mode 100644 index 000000000000..0489fa056d4b --- /dev/null +++ b/src/lib/list/list-option.html @@ -0,0 +1,10 @@ +
+
+
+ + +
+ +
diff --git a/src/lib/list/list-option.ts b/src/lib/list/list-option.ts new file mode 100644 index 000000000000..6ee0d4259632 --- /dev/null +++ b/src/lib/list/list-option.ts @@ -0,0 +1,154 @@ +/** + * @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 { + AfterContentInit, + Component, + ContentChild, + ContentChildren, + Directive, + ElementRef, + Input, + Optional, + QueryList, + Renderer2, + ViewEncapsulation, + ChangeDetectionStrategy, + OnDestroy, + EventEmitter, + Output, + ChangeDetectorRef +} from '@angular/core'; +import {coerceBooleanProperty, MdLine, MdLineSetter} from '../core'; +import {Focusable} from '../core/a11y/focus-key-manager'; +import {MdSelectionList} from './selection-list'; + +export interface MdSelectionListOptionEvent { + option: MdListOption; +} + +const FOCUSED_STYLE: string = 'mat-list-item-focus'; + +/** + * Component for list-options of selection-list. Each list-option can automatically + * generate a checkbox and can put current item into the selectionModel of selection-list + * if the current item is checked. + */ +@Component({ + moduleId: module.id, + selector: 'md-list-option, mat-list-option', + host: { + 'role': 'option', + 'class': 'mat-list-item, mat-list-option', + '(focus)': '_handleFocus()', + '(blur)': '_handleBlur()', + '(click)': 'toggle()', + 'tabindex': '-1', + '[attr.aria-selected]': 'selected.toString()', + '[attr.aria-disabled]': 'disabled.toString()', + }, + templateUrl: 'list-option.html', + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class MdListOption implements AfterContentInit, OnDestroy, Focusable { + private _lineSetter: MdLineSetter; + private _disableRipple: boolean = false; + private _selected: boolean = false; + /** Whether the checkbox is disabled. */ + private _disabled: boolean = false; + private _value: any; + + /** Whether the option has focus. */ + _hasFocus: boolean = false; + + /** + * Whether the ripple effect on click should be disabled. This applies only to list items that are + * part of a nav list. The value of `disableRipple` on the `md-nav-list` overrides this flag. + */ + @Input() + get disableRipple() { return this._disableRipple; } + set disableRipple(value: boolean) { this._disableRipple = coerceBooleanProperty(value); } + + @ContentChildren(MdLine) _lines: QueryList; + + /** Whether the label should appear after or before the checkbox. Defaults to 'after' */ + @Input() checkboxPosition: 'before' | 'after' = 'after'; + + /** Whether the option is disabled. */ + @Input() + get disabled() { return (this.selectionList && this.selectionList.disabled) || this._disabled; } + set disabled(value: any) { this._disabled = coerceBooleanProperty(value); } + + @Input() + get value() { return this._value; } + set value( val: any) { this._value = coerceBooleanProperty(val); } + + @Input() + get selected() { return this._selected; } + set selected( val: boolean) { this._selected = coerceBooleanProperty(val); } + + /** Emitted when the option is focused. */ + onFocus = new EventEmitter(); + + /** Emitted when the option is selected. */ + @Output() select = new EventEmitter(); + + /** Emitted when the option is deselected. */ + @Output() deselect = new EventEmitter(); + + /** Emitted when the option is destroyed. */ + @Output() destroy = new EventEmitter(); + + constructor(private _renderer: Renderer2, + private _element: ElementRef, + private _changeDetector: ChangeDetectorRef, + @Optional() public selectionList: MdSelectionList,) { } + + + ngAfterContentInit() { + this._lineSetter = new MdLineSetter(this._lines, this._renderer, this._element); + } + + ngOnDestroy(): void { + this.destroy.emit({option: this}); + } + + toggle(): void { + if(this._disabled == false) { + this.selected = !this.selected; + this.selectionList.selectedOptions.toggle(this); + this._changeDetector.markForCheck(); + } + } + + /** Allows for programmatic focusing of the option. */ + focus(): void { + this._element.nativeElement.focus(); + this.onFocus.emit({option: this}); + } + + /** Whether this list item should show a ripple effect when clicked. */ + isRippleEnabled() { + return !this.disableRipple && !this.selectionList.disableRipple; + } + + _handleFocus() { + this._hasFocus = true; + this._renderer.addClass(this._element.nativeElement, FOCUSED_STYLE); + } + + _handleBlur() { + this._renderer.removeClass(this._element.nativeElement, FOCUSED_STYLE); + } + + /** Retrieves the DOM element of the component host. */ + _getHostElement(): HTMLElement { + return this._element.nativeElement; + } +} diff --git a/src/lib/list/list.scss b/src/lib/list/list.scss index 7f91e6596895..3136db6e4f30 100644 --- a/src/lib/list/list.scss +++ b/src/lib/list/list.scss @@ -43,6 +43,14 @@ $mat-dense-list-icon-size: 20px; position: relative; } + .mat-list-item-content-reverse { + display: flex; + align-items: center; + padding: 0 $mat-list-side-padding; + flex-direction: row-reverse; + justify-content: space-around; + } + .mat-list-item-ripple { position: absolute; left: 0; @@ -128,7 +136,7 @@ $mat-dense-list-icon-size: 20px; } } -.mat-list, .mat-nav-list { +.mat-list, .mat-nav-list, .mat-selection-list { padding-top: $mat-list-top-padding; display: block; @@ -145,10 +153,20 @@ $mat-dense-list-icon-size: 20px; $mat-list-icon-size ); } + + .mat-list-option { + @include mat-line-base( + $mat-list-base-height, + $mat-list-avatar-height, + $mat-list-two-line-height, + $mat-list-three-line-height, + $mat-list-icon-size + ) + } } -.mat-list[dense], .mat-nav-list[dense] { +.mat-list[dense], .mat-nav-list[dense], .mat-selection-list[dense] { padding-top: $mat-dense-top-padding; display: block; @@ -165,6 +183,17 @@ $mat-dense-list-icon-size: 20px; $mat-dense-list-icon-size ); } + + .mat-list-option { + @include mat-list-item-base( + $mat-dense-base-height, + $mat-dense-avatar-height, + $mat-dense-two-line-height, + $mat-dense-three-line-height, + $mat-dense-list-icon-size + ); + } + } .mat-divider { @@ -188,3 +217,5 @@ $mat-dense-list-icon-size: 20px; } } } + + diff --git a/src/lib/list/list.ts b/src/lib/list/list.ts index 95ddd64788a8..a78156d71e34 100644 --- a/src/lib/list/list.ts +++ b/src/lib/list/list.ts @@ -32,6 +32,7 @@ export const _MdListMixinBase = mixinDisableRipple(MdListBase); export class MdListItemBase {} export const _MdListItemMixinBase = mixinDisableRipple(MdListItemBase); + @Directive({ selector: 'md-divider, mat-divider', host: { diff --git a/src/lib/list/selection-list.ts b/src/lib/list/selection-list.ts new file mode 100644 index 000000000000..b0ec663c03cd --- /dev/null +++ b/src/lib/list/selection-list.ts @@ -0,0 +1,229 @@ +/** + * @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 { + AfterContentInit, + Component, + ContentChild, + ContentChildren, + Directive, + ElementRef, + Input, + Optional, + QueryList, + Renderer2, + ViewEncapsulation, + ChangeDetectionStrategy, + OnDestroy, + EventEmitter, + Output, + ChangeDetectorRef +} from '@angular/core'; +import {coerceBooleanProperty, MdLine, MdLineSetter, SelectionModel} from '../core'; +import {FocusKeyManager} from '../core/a11y/focus-key-manager'; +import {Subscription} from 'rxjs/Subscription'; +import {SPACE} from '../core/keyboard/keycodes'; +import {Focusable} from '../core/a11y/focus-key-manager'; +import {MdListOption} from './list-option'; + +@Component({ + moduleId: module.id, + selector: 'md-selection-list, mat-selection-list', + host: { + 'role': 'listbox', + '[attr.tabindex]': '_tabIndex', + 'class': 'mat-selection-list', + '(focus)': 'focus()', + '(keydown)': 'keydown($event)'}, + template: '', + styleUrls: ['list.css'], + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class MdSelectionList implements AfterContentInit, OnDestroy { + private _disableRipple: boolean = false; + + private _disabled: boolean = false; + + /** Tab index for the selection-list. */ + _tabIndex = 0; + + /** Track which options we're listening to for focus/destruction. */ + private _subscribed: WeakMap = new WeakMap(); + + /** Subscription to tabbing out from the selection-list. */ + private _tabOutSubscription: Subscription; + + /** Subscription to option changes from the selection-list. */ + private _optionSubscription: Subscription; + + /** Whether or not the option is selectable. */ + protected _selectable: boolean = true; + + /** The FocusKeyManager which handles focus. */ + _keyManager: FocusKeyManager; + + /** The option components contained within this selection-list. */ + @ContentChildren(MdListOption) options; + + /** options which are selected. */ + selectedOptions: SelectionModel = new SelectionModel(true); + + /** + * Whether the ripple effect should be disabled on the list-items or not. + * This flag only has an effect for `mat-selection-list` components. + */ + @Input() + get disableRipple() { return this._disableRipple; } + set disableRipple(value: boolean) { this._disableRipple = coerceBooleanProperty(value); } + + /** Whether the selection-list is disabled */ + @Input() + get disabled() { return this._disabled; } + set disabled(value: any) { this._disabled = coerceBooleanProperty(value); } + + constructor(private _element: ElementRef) { } + + ngAfterContentInit(): void { + this._keyManager = new FocusKeyManager(this.options).withWrap(); + + // Prevents the selection-list from capturing focus and redirecting + // it back to the first option when the user tabs out. + this._tabOutSubscription = this._keyManager.tabOut.subscribe(() => { + this._tabIndex = -1; + setTimeout(() => this._tabIndex = 0); + }); + + // Go ahead and subscribe all of the initial options + this._subscribeOptions(this.options); + + // When the list changes, re-subscribe + this._optionSubscription = + this.options.changes.subscribe((options: QueryList) => { + this._subscribeOptions(options); + }); + } + + ngOnDestroy(): void { + if (this._tabOutSubscription) { + this._tabOutSubscription.unsubscribe(); + } + + if (this._optionSubscription) { + this._optionSubscription.unsubscribe(); + } + } + + /** + * Whether or not this option is selectable. When a option is not selectable, + * it's selected state is always ignored. + */ + @Input() + get selectable(): boolean { return this._selectable; } + set selectable(value: boolean) { + this._selectable = coerceBooleanProperty(value); + } + + focus() { + this._element.nativeElement.focus(); + } + + /** Passes relevant key presses to our key manager. */ + keydown(event: KeyboardEvent) { + switch (event.keyCode) { + case SPACE: + this._toggleSelectOnFocusedOption(); + // Always prevent space from scrolling the page since the list has focus + event.preventDefault(); + break; + default: + this._keyManager.onKeydown(event); + } + } + + /** Toggles the selected state of the currently focused option. */ + protected _toggleSelectOnFocusedOption(): void { + if (!this.selectable) { + return; + } + + let focusedIndex = this._keyManager.activeItemIndex; + + if (typeof focusedIndex === 'number' && this._isValidIndex(focusedIndex)) { + let focusedOption: MdListOption = this.options.toArray()[focusedIndex]; + + if (focusedOption) { + focusedOption.toggle(); + } + } + } + + + /** + * Iterate through the list of options and add them to our list of + * subscribed options. + * + * @param options The list of options to be subscribed. + */ + protected _subscribeOptions(options: QueryList): void { + options.forEach(option => this._addOption(option)); + } + + /** + * Add a specific option to our subscribed list. If the option has + * already been subscribed, this ensures it is only subscribed + * once. + * + * @param option The option to be subscribed (or checked for existing + * subscription). + */ + protected _addOption(option: MdListOption) { + // If we've already been subscribed to a parent, do nothing + if (this._subscribed.has(option)) { + return; + } + + // Watch for focus events outside of the keyboard navigation + option.onFocus.subscribe(() => { + let optionIndex: number = this.options.toArray().indexOf(option); + + if (this._isValidIndex(optionIndex)) { + this._keyManager.updateActiveItemIndex(optionIndex); + } + }); + + // On destroy, remove the item from our list, and check focus + option.destroy.subscribe(() => { + let optionIndex: number = this.options.toArray().indexOf(option); + + if (this._isValidIndex(optionIndex) && option._hasFocus) { + // Check whether the option is the last item + if (optionIndex < this.options.length - 1) { + this._keyManager.setActiveItem(optionIndex); + } else if (optionIndex - 1 >= 0) { + this._keyManager.setActiveItem(optionIndex - 1); + } + } + + this._subscribed.delete(option); + option.destroy.unsubscribe(); + }); + + this._subscribed.set(option, true); + } + + /** + * Utility to ensure all indexes are valid. + * + * @param index The index to be checked. + * @returns True if the index is valid for our list of options. + */ + private _isValidIndex(index: number): boolean { + return index >= 0 && index < this.options.length; + } +} From 341e9dad0b35afa1b8f1796b4afcf4fa0a58b7eb Mon Sep 17 00:00:00 2001 From: Lin Sun <2808411862@qq.com> Date: Tue, 18 Jul 2017 17:32:00 -0700 Subject: [PATCH 02/13] add lib files for sticky-header add chose parent add support to 'optional 'cdkStickyRegion' input ' add app-demo for sticky-header fix bugs and deleted unused tag id in HTML files modify fix some code according to PR review comments change some format to pass TSlint check add '_' before private elements delete @Injectable for StickyHeaderDirective. Because we do not need @Injectable refine code encapsulate 'set style for element' change @Input() Delete 'Observable.fromEvent(this.upperScrollableContainer, 'scroll')' add const STICK_START_CLASS and STICK_END_CLASS Add doc for [cdkStickyRegion] and 'unstuckElement()'. Delete 'detach()' function, add its content into 'ngOnDestroy()'. change 'MdStickyHeaderModule' to 'CdkStickyHeaderModule'; encapsulate reset css style operation for sticky header. delete unnecessary gloable variables sticky-header-rebased add one test && fixed bug 'sticky-header not working on iPhone', and bug 'reshape too obviously when being sticked'. && made some change according to the design doc('md-stick --> cdkSticky') make the sticky element being scrolled up smoothly when being unsticked Add code to support optional parentRegion input add add unit test for sticky-header add e2e/sticky-header files e2e test clear code master sticky-header-rebased Add code to support optional parentRegion input add e2e/sticky-header files e2e test clear code fix encapsulate reset css style operation for sticky header. sticky-header-rebased Add code to support optional parentRegion input add e2e/sticky-header files e2e test clear code master sticky-header-rebased Add code to support optional parentRegion input add e2e/sticky-header files e2e test clear code fix delete unused files encapsulate reset css style operation for sticky header. sticky-header-rebased Add code to support optional parentRegion input add unit test for sticky-header add e2e/sticky-header files e2e test clear code master sticky-header-rebased Add code to support optional parentRegion input add e2e/sticky-header files e2e test clear code fix encapsulate reset css style operation for sticky header. sticky-header-rebased Add code to support optional parentRegion input add e2e/sticky-header files e2e test clear code master sticky-header-rebased Add code to support optional parentRegion input add e2e/sticky-header files e2e test clear code fix clean unused files delete sticky-header delete styicky-heade demo delete sticky-header lib revert public_api delete sticky-header e2e files delete.firebasesrc revert revert revert revert add css styles for selection-list add selection-list structures in list.ts initial version of selection-list all src/lib code fix tslint error fix tslint error applied '[class.mat-list-item-content-reverse]="checkboxPosition == 'after'"', deleted comments in code Deleted 'MdSelectionListCssMatStyler' class Added doc for 'md-selection-option' Added binding expression for 'attr.aria-selected' and 'attr.aria-disabled' Added a value and a disabled @Input() property Changed 'isSelected' to 'selected'. And added '@Input(selected)' for it.(Using 'coerceBooleanProperty') changed '_slist' to 'selectionList' and deleted 'MdSelectionListCheckboxer' class Deleted navList in selection-list-option changed 'onchange()' to toggle(). And added doc for it checkedItems -> selectedOptions deleted 'pCheckbox' and store 'this' instead of HTMLelement in 'optionlist' optimized toggle() with 'this._selected = !this._selected' Added 'Tab' moves focus on selection-list, 'UP_ARROW' and 'DOWN_ARROW' moves focus within selection-list. And use 'SPACE' to select checkbox Added another empty line at the end of each file remove comment line in CSS file Deleted 'webkit-justify-content' in CSS file Deleted 'mat-list-item-content-reverse' to these existing styles; the 'mat-list-item-content-reverse' class should only have the extra styles you need to reverse the display Deleted 'mat-list-item-content' and 'mat-list-item-content-reverse' and '
' styles in '.mat-selection-list' Deleted unused 'mat-checkbox' class add ".mat-list-option" class in css file added 'mat-list-option' class fix toggle() disabled Deleted empty class Deleted unused import Removed 'if (this.selectable)' check moved styles in '.mat-selection-list .mat-list-item' to '.mat-list-option'. Deleted position, box-sizing, and height in '.mat-list-item-content-reverse' in list.scss file put md-selection-list and md-list-option in separate files(selection-list.ts and list-option.ts) nit correct imports in index.ts use ' @ContentChildren(MdListOption) options;' instead of QueryList change to check 'focusedIndex != null'. Set The tabindex of list option as -1 when the selection-list is disabled change ['class': 'mat-list-item, mat-list-option',] to ['class': 'mat-list-item mat-list-option',]; Don't want a comma between class names. fix typo in comments fix Binded 'aria-disabled' for selection-list.ts rename to '_optionsChangeSubscription' typo Instead of defining the disabled getter/setter yourself, you can include it via mixin. Removed ' selectable' property Deleted unnecessary 'this._isValidIndex(optionIndex)' check import switchMap Added SwitchMerge nit revert revert revert2 revert3 r4 r5 r2 r3 r4 r5 revert fix rxjs import Changed to use 'RxChain' instead of directly importing 'observer/add/' delete unusd import Added exports for selectionlist fix tslint check fix tslint check fix tslint check Add tests for selection-list and list-option fix typo in test Added new test on native element's focus & blur Added check expect(selectList.selected.length).toBe(0);before the toggle nit Added test on desecting an option fix tslink check fix typo in doc refine docs fix 'What happens if the selection list is disabled later? Would the list option become disabled?' Changed all the test name to the format of 'should ...' rename Deleted unused '_tabOutSubscription' return RxChain.from(this.options.changes)... fix tsLint check fix tslint fix tslint error test can not pass on travel CI fix travelCI check try to fix nativeElement focus test revert fix test fix travis CI error fixing travis fixed~! update fix fix fix fix2 fix added test check on 'aria-selected' Added test checkou on 'aria-disabled' reformat structure changed to 'if (!fixture.componentInstance.isIE) { ... }' Change 'platform.SAFARI || platform.EDGE || platform.FIREFOX' to '!platform.IS_TRIDENT' fix travis check Added aris-disabled test fix travis test put disabled style in theme.scss use background color for disabled items deselect --> deselected destory -> destoryed optimize changed to "if (!this.disabled) { ... }" restructured test cases change function to private _keydown protected -> private indent change 'select' to 'selectChange' Better to delete if (this.selectionList.disabled) {...} part Use this.disabled to consider the case when parent selectionList is disabled? Deleted emty lines It would be better not to mutate the option disabled state based on the parent state; this can be error prone, as it's easy to miss updating one when the other changes. It's better to rely on the disabled getter for the option to account for the parent state. --- package-lock.json | 148 +---------- package.json | 2 +- src/lib/core/theming/_palette.scss | 1 + src/lib/list/_list-theme.scss | 4 + src/lib/list/index.ts | 2 + src/lib/list/list-option.html | 2 +- src/lib/list/list-option.ts | 41 +-- src/lib/list/list.scss | 8 +- src/lib/list/selection-list.spec.ts | 373 ++++++++++++++++++++++++++++ src/lib/list/selection-list.ts | 181 +++++--------- 10 files changed, 482 insertions(+), 280 deletions(-) create mode 100644 src/lib/list/selection-list.spec.ts diff --git a/package-lock.json b/package-lock.json index 5c090d588a6a..304ff5aebfff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -531,26 +531,6 @@ "through2": "2.0.3" } }, - "@gulp-sourcemaps/identity-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/identity-map/-/identity-map-1.0.1.tgz", - "integrity": "sha1-z6I7xYQPkQTOMqZedNt+epdLvuE=", - "dev": true, - "dependencies": { - "acorn": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.0.3.tgz", - "integrity": "sha1-xGDfCEkUY/AozLguqzcwvwEIez0=", - "dev": true - } - } - }, - "@gulp-sourcemaps/map-sources": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/map-sources/-/map-sources-1.0.0.tgz", - "integrity": "sha1-iQrnxdjId/bThIYCFazp1+yUW9o=", - "dev": true - }, "@types/chalk": { "version": "0.4.31", "resolved": "https://registry.npmjs.org/@types/chalk/-/chalk-0.4.31.tgz", @@ -767,11 +747,6 @@ "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", "dev": true }, - "angular": { - "version": "1.6.4", - "resolved": "https://registry.npmjs.org/angular/-/angular-1.6.4.tgz", - "integrity": "sha1-A7exXAGggC1+LPWTJA5gQFTcd/s=" - }, "ansi-align": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-1.1.0.tgz", @@ -1121,12 +1096,6 @@ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", "dev": true }, - "atob": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/atob/-/atob-1.1.3.tgz", - "integrity": "sha1-lfE2KbEsOlGl0hWr3OKqnzL4B3M=", - "dev": true - }, "autoprefixer": { "version": "6.7.7", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-6.7.7.tgz", @@ -2282,12 +2251,6 @@ "trim-off-newlines": "1.0.1" } }, - "convert-source-map": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.0.tgz", - "integrity": "sha1-ms1whRxtXf3ZPZKC5e35SgP/RrU=", - "dev": true - }, "cookie": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", @@ -2474,20 +2437,6 @@ "uid-safe": "2.1.4" } }, - "css": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/css/-/css-2.2.1.tgz", - "integrity": "sha1-c6TIHehdtmTU7mdPfUcIXjstVdw=", - "dev": true, - "dependencies": { - "source-map": { - "version": "0.1.43", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", - "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", - "dev": true - } - } - }, "css-color-names": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.3.tgz", @@ -2762,20 +2711,6 @@ "ms": "2.0.0" } }, - "debug-fabulous": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/debug-fabulous/-/debug-fabulous-0.1.0.tgz", - "integrity": "sha1-rQ6gel1RkyT7VYQqjzTuWcf4/2w=", - "dev": true, - "dependencies": { - "object-assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz", - "integrity": "sha1-ejs9DpgGPUP0wD8uiubNUahog6A=", - "dev": true - } - } - }, "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", @@ -2923,12 +2858,6 @@ "fs-exists-sync": "0.1.0" } }, - "detect-newline": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", - "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", - "dev": true - }, "detective-amd": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/detective-amd/-/detective-amd-2.4.0.tgz", @@ -8981,20 +8910,6 @@ "vinyl-sourcemaps-apply": "0.2.1" } }, - "gulp-sourcemaps": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/gulp-sourcemaps/-/gulp-sourcemaps-2.6.0.tgz", - "integrity": "sha1-fMzomaijv8oVk6M0jQ+/Qd0/UeU=", - "dev": true, - "dependencies": { - "acorn": { - "version": "4.0.13", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", - "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=", - "dev": true - } - } - }, "gulp-transform": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/gulp-transform/-/gulp-transform-2.0.0.tgz", @@ -9688,6 +9603,12 @@ "lower-case": "1.1.4" } }, + "is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=", + "dev": true + }, "is-my-json-valid": { "version": "2.16.0", "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.16.0.tgz", @@ -13406,18 +13327,6 @@ "uuid": "3.1.0" } }, - "request-promise": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.1.tgz", - "integrity": "sha1-fuxWyJMXqCLL/qmbA5zlQ8LhX2c=", - "dev": true - }, - "request-promise-core": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.1.tgz", - "integrity": "sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY=", - "dev": true - }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -13543,12 +13452,6 @@ "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", "dev": true }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "dev": true - }, "response-time": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/response-time/-/response-time-2.3.2.tgz", @@ -14456,12 +14359,6 @@ "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=", "dev": true }, - "source-map-resolve": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.3.1.tgz", - "integrity": "sha1-YQ9hIqRFuN1RU1oqcbeD38Ekh2E=", - "dev": true - }, "source-map-support": { "version": "0.4.15", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.15.tgz", @@ -14471,12 +14368,6 @@ "source-map": "0.5.6" } }, - "source-map-url": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.3.0.tgz", - "integrity": "sha1-fsrxO1e80J2opAxdJp2zN5nUqvk=", - "dev": true - }, "sourcemap-codec": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.3.1.tgz", @@ -14604,12 +14495,6 @@ "readable-stream": "2.3.3" } }, - "stealthy-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", - "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", - "dev": true - }, "stream-combiner": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", @@ -14753,12 +14638,6 @@ "is-utf8": "0.2.1" } }, - "strip-bom-string": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", - "integrity": "sha1-5SEekiQ2n7uB1jOi8ABE3IztrZI=", - "dev": true - }, "strip-eof": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", @@ -15605,12 +15484,6 @@ "integrity": "sha1-YdvC1Ttp/2CRoSoWj9fUMxB+QPE=", "dev": true }, - "travis-after-modes": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/travis-after-modes/-/travis-after-modes-0.0.7.tgz", - "integrity": "sha1-o/PAWNPoaqp79Buwh8dduRRacYw=", - "dev": true - }, "trim-newlines": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", @@ -15734,8 +15607,7 @@ "tslib": { "version": "1.7.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.7.1.tgz", - "integrity": "sha1-vIAEFkaRkjp5/oN4u+s9ogF1OOw=", - "dev": true + "integrity": "sha1-vIAEFkaRkjp5/oN4u+s9ogF1OOw=" }, "tslint": { "version": "5.5.0", @@ -16155,12 +16027,6 @@ "upper-case": "1.1.3" } }, - "urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "dev": true - }, "url-join": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/url-join/-/url-join-0.0.1.tgz", diff --git a/package.json b/package.json index cebdc5e813ff..77a9c2e020d1 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,7 @@ "highlight.js": "^9.11.0", "http-rewrite-middleware": "^0.1.6", "image-diff": "^1.6.3", - "jasmine-core": "^2.6.3", + "jasmine-core": "^2.6.2", "jsonwebtoken": "^7.4.1", "karma": "^1.7.0", "karma-browserstack-launcher": "^1.2.0", diff --git a/src/lib/core/theming/_palette.scss b/src/lib/core/theming/_palette.scss index 4ac830a085bd..ed79c14b3b52 100644 --- a/src/lib/core/theming/_palette.scss +++ b/src/lib/core/theming/_palette.scss @@ -660,6 +660,7 @@ $mat-light-theme-background: ( selected-disabled-button: map_get($mat-grey, 400), disabled-button-toggle: map_get($mat-grey, 200), unselected-chip: map_get($mat-grey, 300), + disabled-list-option: map_get($mat-grey, 200), ); // Background palette for dark themes. diff --git a/src/lib/list/_list-theme.scss b/src/lib/list/_list-theme.scss index e582e82eb8ba..9ff7714ddc03 100644 --- a/src/lib/list/_list-theme.scss +++ b/src/lib/list/_list-theme.scss @@ -22,6 +22,10 @@ } } + .mat-list-item-disabled { + background-color: mat-color($background, disabled-list-option); + } + .mat-divider { border-top-color: mat-color($foreground, divider); } diff --git a/src/lib/list/index.ts b/src/lib/list/index.ts index 6ac9c1fee208..bb84aef208e4 100644 --- a/src/lib/list/index.ts +++ b/src/lib/list/index.ts @@ -60,3 +60,5 @@ export class MdListModule {} export * from './list'; +export * from './selection-list'; +export * from './list-option'; diff --git a/src/lib/list/list-option.html b/src/lib/list/list-option.html index 0489fa056d4b..638a472d7562 100644 --- a/src/lib/list/list-option.html +++ b/src/lib/list/list-option.html @@ -1,4 +1,4 @@ -
+
diff --git a/src/lib/list/list-option.ts b/src/lib/list/list-option.ts index 6ee0d4259632..569855ebb0e2 100644 --- a/src/lib/list/list-option.ts +++ b/src/lib/list/list-option.ts @@ -9,9 +9,7 @@ import { AfterContentInit, Component, - ContentChild, ContentChildren, - Directive, ElementRef, Input, Optional, @@ -22,6 +20,7 @@ import { OnDestroy, EventEmitter, Output, + HostBinding, ChangeDetectorRef } from '@angular/core'; import {coerceBooleanProperty, MdLine, MdLineSetter} from '../core'; @@ -44,10 +43,10 @@ const FOCUSED_STYLE: string = 'mat-list-item-focus'; selector: 'md-list-option, mat-list-option', host: { 'role': 'option', - 'class': 'mat-list-item, mat-list-option', + 'class': 'mat-list-item mat-list-option', '(focus)': '_handleFocus()', '(blur)': '_handleBlur()', - '(click)': 'toggle()', + '(click)': '_handleClick()', 'tabindex': '-1', '[attr.aria-selected]': 'selected.toString()', '[attr.aria-disabled]': 'disabled.toString()', @@ -69,7 +68,8 @@ export class MdListOption implements AfterContentInit, OnDestroy, Focusable { /** * Whether the ripple effect on click should be disabled. This applies only to list items that are - * part of a nav list. The value of `disableRipple` on the `md-nav-list` overrides this flag. + * part of a selection list. The value of `disableRipple` on the `md-selection-list` overrides + * this flag */ @Input() get disableRipple() { return this._disableRipple; } @@ -77,10 +77,11 @@ export class MdListOption implements AfterContentInit, OnDestroy, Focusable { @ContentChildren(MdLine) _lines: QueryList; - /** Whether the label should appear after or before the checkbox. Defaults to 'after' */ + /** Whether the label should appear before or after the checkbox. Defaults to 'after' */ @Input() checkboxPosition: 'before' | 'after' = 'after'; /** Whether the option is disabled. */ + @HostBinding('class.mat-list-item-disabled') @Input() get disabled() { return (this.selectionList && this.selectionList.disabled) || this._disabled; } set disabled(value: any) { this._disabled = coerceBooleanProperty(value); } @@ -97,34 +98,36 @@ export class MdListOption implements AfterContentInit, OnDestroy, Focusable { onFocus = new EventEmitter(); /** Emitted when the option is selected. */ - @Output() select = new EventEmitter(); + @Output() selectChange = new EventEmitter(); /** Emitted when the option is deselected. */ - @Output() deselect = new EventEmitter(); + @Output() deselected = new EventEmitter(); /** Emitted when the option is destroyed. */ - @Output() destroy = new EventEmitter(); + @Output() destroyed = new EventEmitter(); constructor(private _renderer: Renderer2, private _element: ElementRef, private _changeDetector: ChangeDetectorRef, - @Optional() public selectionList: MdSelectionList,) { } + @Optional() public selectionList: MdSelectionList) { } ngAfterContentInit() { this._lineSetter = new MdLineSetter(this._lines, this._renderer, this._element); + + if (this.selectionList.disabled) { + this.disabled(true); + } } ngOnDestroy(): void { - this.destroy.emit({option: this}); + this.destroyed.emit({option: this}); } toggle(): void { - if(this._disabled == false) { - this.selected = !this.selected; - this.selectionList.selectedOptions.toggle(this); - this._changeDetector.markForCheck(); - } + this.selected = !this.selected; + this.selectionList.selectedOptions.toggle(this); + this._changeDetector.markForCheck(); } /** Allows for programmatic focusing of the option. */ @@ -138,6 +141,12 @@ export class MdListOption implements AfterContentInit, OnDestroy, Focusable { return !this.disableRipple && !this.selectionList.disableRipple; } + _handleClick() { + if (!this.disabled) { + this.toggle(); + } + } + _handleFocus() { this._hasFocus = true; this._renderer.addClass(this._element.nativeElement, FOCUSED_STYLE); diff --git a/src/lib/list/list.scss b/src/lib/list/list.scss index 3136db6e4f30..850275c6dff5 100644 --- a/src/lib/list/list.scss +++ b/src/lib/list/list.scss @@ -155,18 +155,19 @@ $mat-dense-list-icon-size: 20px; } .mat-list-option { - @include mat-line-base( + @include mat-list-item-base( $mat-list-base-height, $mat-list-avatar-height, $mat-list-two-line-height, $mat-list-three-line-height, $mat-list-icon-size - ) + ); } } .mat-list[dense], .mat-nav-list[dense], .mat-selection-list[dense] { + padding-top: $mat-dense-top-padding; display: block; @@ -193,7 +194,6 @@ $mat-dense-list-icon-size: 20px; $mat-dense-list-icon-size ); } - } .mat-divider { @@ -217,5 +217,3 @@ $mat-dense-list-icon-size: 20px; } } } - - diff --git a/src/lib/list/selection-list.spec.ts b/src/lib/list/selection-list.spec.ts new file mode 100644 index 000000000000..f4bdbae85523 --- /dev/null +++ b/src/lib/list/selection-list.spec.ts @@ -0,0 +1,373 @@ +import {async, TestBed, ComponentFixture, inject} from '@angular/core/testing'; +import {Component, DebugElement} from '@angular/core'; +import {By} from '@angular/platform-browser'; +import {MdSelectionList, MdListOption, MdListModule} from './index'; +import {createKeyboardEvent} from '@angular/cdk/testing'; +import {UP_ARROW, DOWN_ARROW, SPACE} from '../core/keyboard/keycodes'; +import {Platform} from '../core/platform/index'; + + +describe('MdSelectionList', () => { + describe('with list option', () => { + let fixture: ComponentFixture; + let listOption: DebugElement[]; + let listItemEl: DebugElement; + let selectionList: DebugElement; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [MdListModule], + declarations: [ + SelectionListWithListOptions, + SelectionListWithCheckboxPositionAfter, + SelectionListWithListDisabled, + SelectionListWithOnlyOneOption + ], + }); + + TestBed.compileComponents(); + })); + + beforeEach(async(() => { + fixture = TestBed.createComponent(SelectionListWithListOptions); + listOption = fixture.debugElement.queryAll(By.directive(MdListOption)); + listItemEl = fixture.debugElement.query(By.css('.mat-list-item')); + selectionList = fixture.debugElement.query(By.directive(MdSelectionList)); + fixture.detectChanges(); + })); + + it('should add and remove focus class on focus/blur', () => { + expect(listItemEl.nativeElement.classList).not.toContain('mat-list-item-focus'); + + listOption[0].componentInstance._handleFocus(); + fixture.detectChanges(); + expect(listItemEl.nativeElement.className).toContain('mat-list-item-focus'); + + listOption[0].componentInstance._handleBlur(); + fixture.detectChanges(); + expect(listItemEl.nativeElement.className).not.toContain('mat-list-item-focus'); + }); + + it('should be able to dispatch one selected item', () => { + let testListItem = listOption[2].injector.get(MdListOption); + let selectList = selectionList.injector.get(MdSelectionList).selectedOptions; + + expect(selectList.selected.length).toBe(0); + expect(listOption[2].nativeElement.getAttribute('aria-selected')).toBe('false'); + + testListItem.toggle(); + fixture.detectChanges(); + + expect(listOption[2].nativeElement.getAttribute('aria-selected')).toBe('true'); + expect(listOption[2].nativeElement.getAttribute('aria-disabled')).toBe('false'); + expect(selectList.selected.length).toBe(1); + }); + + it('should be able to dispatch multiple selected items', () => { + let testListItem = listOption[2].injector.get(MdListOption); + let testListItem2 = listOption[1].injector.get(MdListOption); + let selectList = selectionList.injector.get(MdSelectionList).selectedOptions; + + expect(selectList.selected.length).toBe(0); + expect(listOption[2].nativeElement.getAttribute('aria-selected')).toBe('false'); + expect(listOption[1].nativeElement.getAttribute('aria-selected')).toBe('false'); + + testListItem.toggle(); + fixture.detectChanges(); + + testListItem2.toggle(); + fixture.detectChanges(); + + expect(selectList.selected.length).toBe(2); + expect(listOption[2].nativeElement.getAttribute('aria-selected')).toBe('true'); + expect(listOption[1].nativeElement.getAttribute('aria-selected')).toBe('true'); + expect(listOption[1].nativeElement.getAttribute('aria-disabled')).toBe('false'); + expect(listOption[2].nativeElement.getAttribute('aria-disabled')).toBe('false'); + }); + + it('should be able to deselect an option', () => { + let testListItem = listOption[2].injector.get(MdListOption); + let selectList = selectionList.injector.get(MdSelectionList).selectedOptions; + + expect(selectList.selected.length).toBe(0); + + testListItem.toggle(); + fixture.detectChanges(); + + expect(selectList.selected.length).toBe(1); + + testListItem.toggle(); + fixture.detectChanges(); + + expect(selectList.selected.length).toBe(0); + }); + + it('should not allow selection of disabled items', () => { + let testListItem = listOption[0].injector.get(MdListOption); + let selectList = selectionList.injector.get(MdSelectionList).selectedOptions; + + expect(selectList.selected.length).toBe(0); + expect(listOption[0].nativeElement.getAttribute('aria-disabled')).toBe('true'); + + testListItem.toggle(); + fixture.detectChanges(); + + expect(selectList.selected.length).toBe(0); + }); + + it('should be able to un-disable disabled items', () => { + let testListItem = listOption[0].injector.get(MdListOption); + + expect(listOption[0].nativeElement.getAttribute('aria-disabled')).toBe('true'); + + testListItem.disabled = false; + fixture.detectChanges(); + + expect(listOption[0].nativeElement.getAttribute('aria-disabled')).toBe('false'); + }); + + it('should be able to use keyboard select with SPACE', () => { + let testListItem = listOption[1].nativeElement as HTMLElement; + let SPACE_EVENT: KeyboardEvent = + createKeyboardEvent('keydown', SPACE, testListItem); + let selectList = selectionList.injector.get(MdSelectionList).selectedOptions; + let options = selectionList.componentInstance.options; + let array = options.toArray(); + let focusItem = array[1]; + expect(selectList.selected.length).toBe(0); + + focusItem.focus(); + selectionList.componentInstance.keydown(SPACE_EVENT); + + fixture.detectChanges(); + + expect(selectList.selected.length).toBe(1); + }); + + it('should focus previous item when press UP ARROW', () => { + let testListItem = listOption[2].nativeElement as HTMLElement; + let UP_EVENT: KeyboardEvent = + createKeyboardEvent('keydown', UP_ARROW, testListItem); + let options = selectionList.componentInstance.options; + let array = options.toArray(); + let focusItem = array[2]; + let manager = selectionList.componentInstance._keyManager; + + focusItem.focus(); + expect(manager.activeItemIndex).toEqual(2); + + selectionList.componentInstance.keydown(UP_EVENT); + + fixture.detectChanges(); + + expect(manager.activeItemIndex).toEqual(1); + }); + + it('should focus next item when press DOWN ARROW', () => { + let testListItem = listOption[2].nativeElement as HTMLElement; + let DOWN_EVENT: KeyboardEvent = + createKeyboardEvent('keydown', DOWN_ARROW, testListItem); + let options = selectionList.componentInstance.options; + let array = options.toArray(); + let focusItem = array[2]; + let manager = selectionList.componentInstance._keyManager; + + focusItem.focus(); + expect(manager.activeItemIndex).toEqual(2); + + selectionList.componentInstance.keydown(DOWN_EVENT); + + fixture.detectChanges(); + + expect(manager.activeItemIndex).toEqual(3); + }); + }); + + describe('with single option', () => { + let fixture: ComponentFixture; + let listOption: DebugElement; + let listItemEl: DebugElement; + let selectionList: DebugElement; + let platform: Platform; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [MdListModule], + declarations: [ + SelectionListWithListOptions, + SelectionListWithCheckboxPositionAfter, + SelectionListWithListDisabled, + SelectionListWithOnlyOneOption + ], + }); + + TestBed.compileComponents(); + })); + + beforeEach(async(() => { + fixture = TestBed.createComponent(SelectionListWithOnlyOneOption); + listOption = fixture.debugElement.query(By.directive(MdListOption)); + listItemEl = fixture.debugElement.query(By.css('.mat-list-item')); + selectionList = fixture.debugElement.query(By.directive(MdSelectionList)); + fixture.detectChanges(); + })); + + beforeEach(inject([Platform], (p: Platform) => { + platform = p; + })); + + it('should be focused when focus on nativeElements', () => { + listOption.nativeElement.focus(); + fixture.detectChanges(); + + expect(listItemEl.nativeElement).toBe(document.activeElement); + if (!platform.TRIDENT) { + expect(listItemEl.nativeElement.className).toContain('mat-list-item-focus'); + } + + listOption.nativeElement.blur(); + fixture.detectChanges(); + + expect(listItemEl.nativeElement.className).not.toContain('mat-list-item-focus'); + }); + }); + + describe('with list disabled', () => { + let fixture: ComponentFixture; + let listOption: DebugElement[]; + let listItemEl: DebugElement; + let selectionList: DebugElement; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [MdListModule], + declarations: [ + SelectionListWithListOptions, + SelectionListWithCheckboxPositionAfter, + SelectionListWithListDisabled, + SelectionListWithOnlyOneOption + ], + }); + + TestBed.compileComponents(); + })); + + beforeEach(async(() => { + fixture = TestBed.createComponent(SelectionListWithListDisabled); + listOption = fixture.debugElement.queryAll(By.directive(MdListOption)); + listItemEl = fixture.debugElement.query(By.css('.mat-list-item')); + selectionList = fixture.debugElement.query(By.directive(MdSelectionList)); + fixture.detectChanges(); + })); + + it('should not allow selection on disabled selection-list', () => { + let testListItem = listOption[2].injector.get(MdListOption); + let selectList = selectionList.injector.get(MdSelectionList).selectedOptions; + + expect(selectList.selected.length).toBe(0); + + testListItem.toggle(); + fixture.detectChanges(); + + expect(selectList.selected.length).toBe(0); + }); + }); + + describe('with checkbox position after', () => { + let fixture: ComponentFixture; + let listOption: DebugElement[]; + let listItemEl: DebugElement; + let selectionList: DebugElement; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [MdListModule], + declarations: [ + SelectionListWithListOptions, + SelectionListWithCheckboxPositionAfter, + SelectionListWithListDisabled, + SelectionListWithOnlyOneOption + ], + }); + + TestBed.compileComponents(); + })); + + beforeEach(async(() => { + fixture = TestBed.createComponent(SelectionListWithCheckboxPositionAfter); + listOption = fixture.debugElement.queryAll(By.directive(MdListOption)); + listItemEl = fixture.debugElement.query(By.css('.mat-list-item')); + selectionList = fixture.debugElement.query(By.directive(MdSelectionList)); + fixture.detectChanges(); + })); + + it('should be able to customize checkbox position', () => { + let listItemContent = fixture.debugElement.query(By.css('.mat-list-item-content')); + expect(listItemContent.nativeElement.classList).toContain('mat-list-item-content-reverse'); + }); + }); +}); + + +@Component({template: ` + + + Inbox (disabled selection-option) + + + Starred + + + Sent Mail + + + Drafts + + `}) +class SelectionListWithListOptions { +} + +@Component({template: ` + + + Inbox (disabled selection-option) + + + Starred + + + Sent Mail + + + Drafts + + `}) +class SelectionListWithCheckboxPositionAfter { +} + +@Component({template: ` + + + Inbox (disabled selection-option) + + + Starred + + + Sent Mail + + + Drafts + + `}) +class SelectionListWithListDisabled { +} + +@Component({template: ` + + + Inbox + + `}) +class SelectionListWithOnlyOneOption { +} diff --git a/src/lib/list/selection-list.ts b/src/lib/list/selection-list.ts index b0ec663c03cd..e92112ed5cc3 100644 --- a/src/lib/list/selection-list.ts +++ b/src/lib/list/selection-list.ts @@ -9,61 +9,54 @@ import { AfterContentInit, Component, - ContentChild, ContentChildren, - Directive, ElementRef, Input, - Optional, - QueryList, - Renderer2, ViewEncapsulation, ChangeDetectionStrategy, OnDestroy, - EventEmitter, - Output, - ChangeDetectorRef } from '@angular/core'; -import {coerceBooleanProperty, MdLine, MdLineSetter, SelectionModel} from '../core'; +import {coerceBooleanProperty, SelectionModel} from '../core'; import {FocusKeyManager} from '../core/a11y/focus-key-manager'; import {Subscription} from 'rxjs/Subscription'; import {SPACE} from '../core/keyboard/keycodes'; import {Focusable} from '../core/a11y/focus-key-manager'; -import {MdListOption} from './list-option'; +import {MdListOption, MdSelectionListOptionEvent} from './list-option'; +import {CanDisable, mixinDisabled} from '../core/common-behaviors/disabled'; +import {RxChain, switchMap, startWith} from '../core/rxjs/index'; +import {merge} from 'rxjs/observable/merge'; + +export class MdSelectionListBase {} +export const _MdSelectionListMixinBase = mixinDisabled(MdSelectionListBase); @Component({ moduleId: module.id, selector: 'md-selection-list, mat-selection-list', + inputs: ['disabled'], host: { 'role': 'listbox', '[attr.tabindex]': '_tabIndex', 'class': 'mat-selection-list', '(focus)': 'focus()', - '(keydown)': 'keydown($event)'}, + '(keydown)': '_keydown($event)', + '[attr.aria-disabled]': 'disabled.toString()'}, template: '', styleUrls: ['list.css'], encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush }) -export class MdSelectionList implements AfterContentInit, OnDestroy { +export class MdSelectionList extends _MdSelectionListMixinBase + implements Focusable, CanDisable, AfterContentInit, OnDestroy { private _disableRipple: boolean = false; - private _disabled: boolean = false; - /** Tab index for the selection-list. */ _tabIndex = 0; - /** Track which options we're listening to for focus/destruction. */ - private _subscribed: WeakMap = new WeakMap(); - - /** Subscription to tabbing out from the selection-list. */ - private _tabOutSubscription: Subscription; + /** Subscription to all list options' onFocus events */ + private _optionFocusSubscription: Subscription; - /** Subscription to option changes from the selection-list. */ - private _optionSubscription: Subscription; - - /** Whether or not the option is selectable. */ - protected _selectable: boolean = true; + /** Subscription to all list options' destroy events */ + private _optionDestroyStream: Subscription; /** The FocusKeyManager which handles focus. */ _keyManager: FocusKeyManager; @@ -82,59 +75,73 @@ export class MdSelectionList implements AfterContentInit, OnDestroy { get disableRipple() { return this._disableRipple; } set disableRipple(value: boolean) { this._disableRipple = coerceBooleanProperty(value); } - /** Whether the selection-list is disabled */ - @Input() - get disabled() { return this._disabled; } - set disabled(value: any) { this._disabled = coerceBooleanProperty(value); } - - constructor(private _element: ElementRef) { } + constructor(private _element: ElementRef) { + super(); + } ngAfterContentInit(): void { this._keyManager = new FocusKeyManager(this.options).withWrap(); - // Prevents the selection-list from capturing focus and redirecting - // it back to the first option when the user tabs out. - this._tabOutSubscription = this._keyManager.tabOut.subscribe(() => { + if (this.disabled) { this._tabIndex = -1; - setTimeout(() => this._tabIndex = 0); - }); - - // Go ahead and subscribe all of the initial options - this._subscribeOptions(this.options); + } - // When the list changes, re-subscribe - this._optionSubscription = - this.options.changes.subscribe((options: QueryList) => { - this._subscribeOptions(options); - }); + this._optionFocusSubscription = this._onFocusSubscription(); + this._optionDestroyStream = this._onDestroySubscription(); } ngOnDestroy(): void { - if (this._tabOutSubscription) { - this._tabOutSubscription.unsubscribe(); + if (this._optionDestroyStream) { + this._optionDestroyStream.unsubscribe(); } - if (this._optionSubscription) { - this._optionSubscription.unsubscribe(); + if (this._optionFocusSubscription) { + this._optionFocusSubscription.unsubscribe(); } } + focus() { + this._element.nativeElement.focus(); + } + /** - * Whether or not this option is selectable. When a option is not selectable, - * it's selected state is always ignored. + * Map all the options' destroy event subscriptions and merge them into one stream. */ - @Input() - get selectable(): boolean { return this._selectable; } - set selectable(value: boolean) { - this._selectable = coerceBooleanProperty(value); + private _onDestroySubscription(): Subscription { + return RxChain.from(this.options.changes) + .call(startWith, this.options) + .call(switchMap, (options: MdListOption[]) => { + return merge(...options.map(option => option.destroyed)); + }).subscribe((e: MdSelectionListOptionEvent) => { + let optionIndex: number = this.options.toArray().indexOf(e.option); + if (e.option._hasFocus) { + // Check whether the option is the last item + if (optionIndex < this.options.length - 1) { + this._keyManager.setActiveItem(optionIndex); + } else if (optionIndex - 1 >= 0) { + this._keyManager.setActiveItem(optionIndex - 1); + } + } + e.option.destroyed.unsubscribe(); + }); } - focus() { - this._element.nativeElement.focus(); + /** + * Map all the options' onFocus event subscriptions and merge them into one stream. + */ + private _onFocusSubscription(): Subscription { + return RxChain.from(this.options.changes) + .call(startWith, this.options) + .call(switchMap, (options: MdListOption[]) => { + return merge(...options.map(option => option.onFocus)); + }).subscribe((e: MdSelectionListOptionEvent) => { + let optionIndex: number = this.options.toArray().indexOf(e.option); + this._keyManager.updateActiveItemIndex(optionIndex); + }); } /** Passes relevant key presses to our key manager. */ - keydown(event: KeyboardEvent) { + _keydown(event: KeyboardEvent) { switch (event.keyCode) { case SPACE: this._toggleSelectOnFocusedOption(); @@ -147,14 +154,10 @@ export class MdSelectionList implements AfterContentInit, OnDestroy { } /** Toggles the selected state of the currently focused option. */ - protected _toggleSelectOnFocusedOption(): void { - if (!this.selectable) { - return; - } - + private _toggleSelectOnFocusedOption(): void { let focusedIndex = this._keyManager.activeItemIndex; - if (typeof focusedIndex === 'number' && this._isValidIndex(focusedIndex)) { + if (focusedIndex != null && this._isValidIndex(focusedIndex)) { let focusedOption: MdListOption = this.options.toArray()[focusedIndex]; if (focusedOption) { @@ -163,60 +166,6 @@ export class MdSelectionList implements AfterContentInit, OnDestroy { } } - - /** - * Iterate through the list of options and add them to our list of - * subscribed options. - * - * @param options The list of options to be subscribed. - */ - protected _subscribeOptions(options: QueryList): void { - options.forEach(option => this._addOption(option)); - } - - /** - * Add a specific option to our subscribed list. If the option has - * already been subscribed, this ensures it is only subscribed - * once. - * - * @param option The option to be subscribed (or checked for existing - * subscription). - */ - protected _addOption(option: MdListOption) { - // If we've already been subscribed to a parent, do nothing - if (this._subscribed.has(option)) { - return; - } - - // Watch for focus events outside of the keyboard navigation - option.onFocus.subscribe(() => { - let optionIndex: number = this.options.toArray().indexOf(option); - - if (this._isValidIndex(optionIndex)) { - this._keyManager.updateActiveItemIndex(optionIndex); - } - }); - - // On destroy, remove the item from our list, and check focus - option.destroy.subscribe(() => { - let optionIndex: number = this.options.toArray().indexOf(option); - - if (this._isValidIndex(optionIndex) && option._hasFocus) { - // Check whether the option is the last item - if (optionIndex < this.options.length - 1) { - this._keyManager.setActiveItem(optionIndex); - } else if (optionIndex - 1 >= 0) { - this._keyManager.setActiveItem(optionIndex - 1); - } - } - - this._subscribed.delete(option); - option.destroy.unsubscribe(); - }); - - this._subscribed.set(option, true); - } - /** * Utility to ensure all indexes are valid. * From 7f728535d9d441bf552e6a8936e3caa510b17d99 Mon Sep 17 00:00:00 2001 From: Lin Sun <2808411862@qq.com> Date: Thu, 10 Aug 2017 12:25:42 -0700 Subject: [PATCH 03/13] fix after rebase --- src/lib/core/theming/_palette.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/core/theming/_palette.scss b/src/lib/core/theming/_palette.scss index ed79c14b3b52..23a2915b4c64 100644 --- a/src/lib/core/theming/_palette.scss +++ b/src/lib/core/theming/_palette.scss @@ -678,6 +678,7 @@ $mat-dark-theme-background: ( selected-disabled-button: map_get($mat-grey, 800), disabled-button-toggle: map_get($mat-grey, 1000), unselected-chip: map_get($mat-grey, 700), + disabled-list-option: map_get($mat-grey, 1000), ); // Foreground palette for light themes. From 9a875828f0f030aa8eb6738b4bedf56cdf9f1bee Mon Sep 17 00:00:00 2001 From: Lin Sun <2808411862@qq.com> Date: Thu, 10 Aug 2017 14:02:53 -0700 Subject: [PATCH 04/13] fix --- src/lib/list/list-option.ts | 5 ++--- src/lib/list/selection-list.ts | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/lib/list/list-option.ts b/src/lib/list/list-option.ts index 569855ebb0e2..4989f1a72c28 100644 --- a/src/lib/list/list-option.ts +++ b/src/lib/list/list-option.ts @@ -24,7 +24,7 @@ import { ChangeDetectorRef } from '@angular/core'; import {coerceBooleanProperty, MdLine, MdLineSetter} from '../core'; -import {Focusable} from '../core/a11y/focus-key-manager'; +import {FocusableOption} from '../core/a11y/focus-key-manager'; import {MdSelectionList} from './selection-list'; export interface MdSelectionListOptionEvent { @@ -55,7 +55,7 @@ const FOCUSED_STYLE: string = 'mat-list-item-focus'; encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush }) -export class MdListOption implements AfterContentInit, OnDestroy, Focusable { +export class MdListOption implements AfterContentInit, OnDestroy, FocusableOption { private _lineSetter: MdLineSetter; private _disableRipple: boolean = false; private _selected: boolean = false; @@ -81,7 +81,6 @@ export class MdListOption implements AfterContentInit, OnDestroy, Focusable { @Input() checkboxPosition: 'before' | 'after' = 'after'; /** Whether the option is disabled. */ - @HostBinding('class.mat-list-item-disabled') @Input() get disabled() { return (this.selectionList && this.selectionList.disabled) || this._disabled; } set disabled(value: any) { this._disabled = coerceBooleanProperty(value); } diff --git a/src/lib/list/selection-list.ts b/src/lib/list/selection-list.ts index e92112ed5cc3..05195011af5e 100644 --- a/src/lib/list/selection-list.ts +++ b/src/lib/list/selection-list.ts @@ -20,7 +20,7 @@ import {coerceBooleanProperty, SelectionModel} from '../core'; import {FocusKeyManager} from '../core/a11y/focus-key-manager'; import {Subscription} from 'rxjs/Subscription'; import {SPACE} from '../core/keyboard/keycodes'; -import {Focusable} from '../core/a11y/focus-key-manager'; +import {FocusableOption} from '../core/a11y/focus-key-manager'; import {MdListOption, MdSelectionListOptionEvent} from './list-option'; import {CanDisable, mixinDisabled} from '../core/common-behaviors/disabled'; import {RxChain, switchMap, startWith} from '../core/rxjs/index'; @@ -46,7 +46,7 @@ export const _MdSelectionListMixinBase = mixinDisabled(MdSelectionListBase); changeDetection: ChangeDetectionStrategy.OnPush }) export class MdSelectionList extends _MdSelectionListMixinBase - implements Focusable, CanDisable, AfterContentInit, OnDestroy { + implements FocusableOption, CanDisable, AfterContentInit, OnDestroy { private _disableRipple: boolean = false; /** Tab index for the selection-list. */ From 73975a905e3fba89cd66c3fa153cbc137399f189 Mon Sep 17 00:00:00 2001 From: Lin Sun <2808411862@qq.com> Date: Thu, 10 Aug 2017 14:12:20 -0700 Subject: [PATCH 05/13] change 'grey, 1000' to 'black' --- src/lib/core/theming/_palette.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/core/theming/_palette.scss b/src/lib/core/theming/_palette.scss index 23a2915b4c64..6785050550bd 100644 --- a/src/lib/core/theming/_palette.scss +++ b/src/lib/core/theming/_palette.scss @@ -676,9 +676,9 @@ $mat-dark-theme-background: ( focused-button: $white-6-opacity, selected-button: map_get($mat-grey, 900), selected-disabled-button: map_get($mat-grey, 800), - disabled-button-toggle: map_get($mat-grey, 1000), + disabled-button-toggle: black, unselected-chip: map_get($mat-grey, 700), - disabled-list-option: map_get($mat-grey, 1000), + disabled-list-option: black, ); // Foreground palette for light themes. From 3328d6eb3ea6a93bb2e72c720e1f1a91abc03307 Mon Sep 17 00:00:00 2001 From: Lin Sun <2808411862@qq.com> Date: Thu, 10 Aug 2017 14:12:58 -0700 Subject: [PATCH 06/13] remove unused import --- src/lib/list/list-option.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/list/list-option.ts b/src/lib/list/list-option.ts index 4989f1a72c28..bc48a917fef1 100644 --- a/src/lib/list/list-option.ts +++ b/src/lib/list/list-option.ts @@ -20,7 +20,6 @@ import { OnDestroy, EventEmitter, Output, - HostBinding, ChangeDetectorRef } from '@angular/core'; import {coerceBooleanProperty, MdLine, MdLineSetter} from '../core'; From 748cfca3d84f492bb96afa356306626d32120917 Mon Sep 17 00:00:00 2001 From: Lin Sun <2808411862@qq.com> Date: Thu, 10 Aug 2017 15:18:45 -0700 Subject: [PATCH 07/13] fix test --- src/lib/list/selection-list.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/list/selection-list.spec.ts b/src/lib/list/selection-list.spec.ts index f4bdbae85523..ea3c6215961a 100644 --- a/src/lib/list/selection-list.spec.ts +++ b/src/lib/list/selection-list.spec.ts @@ -109,7 +109,7 @@ describe('MdSelectionList', () => { expect(selectList.selected.length).toBe(0); expect(listOption[0].nativeElement.getAttribute('aria-disabled')).toBe('true'); - testListItem.toggle(); + testListItem._handleClick(); fixture.detectChanges(); expect(selectList.selected.length).toBe(0); @@ -266,7 +266,7 @@ describe('MdSelectionList', () => { expect(selectList.selected.length).toBe(0); - testListItem.toggle(); + testListItem._handleClick(); fixture.detectChanges(); expect(selectList.selected.length).toBe(0); From 1d6478286cd366d623da2a1be793dba96b0810b2 Mon Sep 17 00:00:00 2001 From: Lin Sun <2808411862@qq.com> Date: Fri, 11 Aug 2017 10:22:46 -0700 Subject: [PATCH 08/13] put selection-list.ts and list-option.ts into one file~ and fix the test --- src/lib/list/index.ts | 4 +- src/lib/list/list-option.ts | 161 ---------------------------- src/lib/list/selection-list.spec.ts | 8 +- src/lib/list/selection-list.ts | 145 ++++++++++++++++++++++++- 4 files changed, 148 insertions(+), 170 deletions(-) delete mode 100644 src/lib/list/list-option.ts diff --git a/src/lib/list/index.ts b/src/lib/list/index.ts index bb84aef208e4..d5960fe69a19 100644 --- a/src/lib/list/index.ts +++ b/src/lib/list/index.ts @@ -20,8 +20,7 @@ import { MdDividerCssMatStyler, MdListSubheaderCssMatStyler } from './list'; -import {MdSelectionList} from './selection-list'; -import {MdListOption} from './list-option'; +import {MdSelectionList, MdListOption} from './selection-list'; @NgModule({ @@ -61,4 +60,3 @@ export class MdListModule {} export * from './list'; export * from './selection-list'; -export * from './list-option'; diff --git a/src/lib/list/list-option.ts b/src/lib/list/list-option.ts deleted file mode 100644 index bc48a917fef1..000000000000 --- a/src/lib/list/list-option.ts +++ /dev/null @@ -1,161 +0,0 @@ -/** - * @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 { - AfterContentInit, - Component, - ContentChildren, - ElementRef, - Input, - Optional, - QueryList, - Renderer2, - ViewEncapsulation, - ChangeDetectionStrategy, - OnDestroy, - EventEmitter, - Output, - ChangeDetectorRef -} from '@angular/core'; -import {coerceBooleanProperty, MdLine, MdLineSetter} from '../core'; -import {FocusableOption} from '../core/a11y/focus-key-manager'; -import {MdSelectionList} from './selection-list'; - -export interface MdSelectionListOptionEvent { - option: MdListOption; -} - -const FOCUSED_STYLE: string = 'mat-list-item-focus'; - -/** - * Component for list-options of selection-list. Each list-option can automatically - * generate a checkbox and can put current item into the selectionModel of selection-list - * if the current item is checked. - */ -@Component({ - moduleId: module.id, - selector: 'md-list-option, mat-list-option', - host: { - 'role': 'option', - 'class': 'mat-list-item mat-list-option', - '(focus)': '_handleFocus()', - '(blur)': '_handleBlur()', - '(click)': '_handleClick()', - 'tabindex': '-1', - '[attr.aria-selected]': 'selected.toString()', - '[attr.aria-disabled]': 'disabled.toString()', - }, - templateUrl: 'list-option.html', - encapsulation: ViewEncapsulation.None, - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class MdListOption implements AfterContentInit, OnDestroy, FocusableOption { - private _lineSetter: MdLineSetter; - private _disableRipple: boolean = false; - private _selected: boolean = false; - /** Whether the checkbox is disabled. */ - private _disabled: boolean = false; - private _value: any; - - /** Whether the option has focus. */ - _hasFocus: boolean = false; - - /** - * Whether the ripple effect on click should be disabled. This applies only to list items that are - * part of a selection list. The value of `disableRipple` on the `md-selection-list` overrides - * this flag - */ - @Input() - get disableRipple() { return this._disableRipple; } - set disableRipple(value: boolean) { this._disableRipple = coerceBooleanProperty(value); } - - @ContentChildren(MdLine) _lines: QueryList; - - /** Whether the label should appear before or after the checkbox. Defaults to 'after' */ - @Input() checkboxPosition: 'before' | 'after' = 'after'; - - /** Whether the option is disabled. */ - @Input() - get disabled() { return (this.selectionList && this.selectionList.disabled) || this._disabled; } - set disabled(value: any) { this._disabled = coerceBooleanProperty(value); } - - @Input() - get value() { return this._value; } - set value( val: any) { this._value = coerceBooleanProperty(val); } - - @Input() - get selected() { return this._selected; } - set selected( val: boolean) { this._selected = coerceBooleanProperty(val); } - - /** Emitted when the option is focused. */ - onFocus = new EventEmitter(); - - /** Emitted when the option is selected. */ - @Output() selectChange = new EventEmitter(); - - /** Emitted when the option is deselected. */ - @Output() deselected = new EventEmitter(); - - /** Emitted when the option is destroyed. */ - @Output() destroyed = new EventEmitter(); - - constructor(private _renderer: Renderer2, - private _element: ElementRef, - private _changeDetector: ChangeDetectorRef, - @Optional() public selectionList: MdSelectionList) { } - - - ngAfterContentInit() { - this._lineSetter = new MdLineSetter(this._lines, this._renderer, this._element); - - if (this.selectionList.disabled) { - this.disabled(true); - } - } - - ngOnDestroy(): void { - this.destroyed.emit({option: this}); - } - - toggle(): void { - this.selected = !this.selected; - this.selectionList.selectedOptions.toggle(this); - this._changeDetector.markForCheck(); - } - - /** Allows for programmatic focusing of the option. */ - focus(): void { - this._element.nativeElement.focus(); - this.onFocus.emit({option: this}); - } - - /** Whether this list item should show a ripple effect when clicked. */ - isRippleEnabled() { - return !this.disableRipple && !this.selectionList.disableRipple; - } - - _handleClick() { - if (!this.disabled) { - this.toggle(); - } - } - - _handleFocus() { - this._hasFocus = true; - this._renderer.addClass(this._element.nativeElement, FOCUSED_STYLE); - } - - _handleBlur() { - this._renderer.removeClass(this._element.nativeElement, FOCUSED_STYLE); - } - - /** Retrieves the DOM element of the component host. */ - _getHostElement(): HTMLElement { - return this._element.nativeElement; - } -} diff --git a/src/lib/list/selection-list.spec.ts b/src/lib/list/selection-list.spec.ts index ea3c6215961a..926913c6e3c7 100644 --- a/src/lib/list/selection-list.spec.ts +++ b/src/lib/list/selection-list.spec.ts @@ -137,7 +137,7 @@ describe('MdSelectionList', () => { expect(selectList.selected.length).toBe(0); focusItem.focus(); - selectionList.componentInstance.keydown(SPACE_EVENT); + selectionList.componentInstance._keydown(SPACE_EVENT); fixture.detectChanges(); @@ -156,7 +156,7 @@ describe('MdSelectionList', () => { focusItem.focus(); expect(manager.activeItemIndex).toEqual(2); - selectionList.componentInstance.keydown(UP_EVENT); + selectionList.componentInstance._keydown(UP_EVENT); fixture.detectChanges(); @@ -175,7 +175,7 @@ describe('MdSelectionList', () => { focusItem.focus(); expect(manager.activeItemIndex).toEqual(2); - selectionList.componentInstance.keydown(DOWN_EVENT); + selectionList.componentInstance._keydown(DOWN_EVENT); fixture.detectChanges(); @@ -221,7 +221,7 @@ describe('MdSelectionList', () => { fixture.detectChanges(); expect(listItemEl.nativeElement).toBe(document.activeElement); - if (!platform.TRIDENT) { + if (platform.SAFARI || platform.FIREFOX) { expect(listItemEl.nativeElement.className).toContain('mat-list-item-focus'); } diff --git a/src/lib/list/selection-list.ts b/src/lib/list/selection-list.ts index 05195011af5e..91e513344c6c 100644 --- a/src/lib/list/selection-list.ts +++ b/src/lib/list/selection-list.ts @@ -12,16 +12,21 @@ import { ContentChildren, ElementRef, Input, + QueryList, ViewEncapsulation, + Optional, + Renderer2, + EventEmitter, + Output, ChangeDetectionStrategy, + ChangeDetectorRef, OnDestroy, } from '@angular/core'; -import {coerceBooleanProperty, SelectionModel} from '../core'; +import {coerceBooleanProperty, SelectionModel, MdLine, MdLineSetter} from '../core'; import {FocusKeyManager} from '../core/a11y/focus-key-manager'; import {Subscription} from 'rxjs/Subscription'; import {SPACE} from '../core/keyboard/keycodes'; import {FocusableOption} from '../core/a11y/focus-key-manager'; -import {MdListOption, MdSelectionListOptionEvent} from './list-option'; import {CanDisable, mixinDisabled} from '../core/common-behaviors/disabled'; import {RxChain, switchMap, startWith} from '../core/rxjs/index'; import {merge} from 'rxjs/observable/merge'; @@ -29,6 +34,142 @@ import {merge} from 'rxjs/observable/merge'; export class MdSelectionListBase {} export const _MdSelectionListMixinBase = mixinDisabled(MdSelectionListBase); + +export interface MdSelectionListOptionEvent { + option: MdListOption; +} + +const FOCUSED_STYLE: string = 'mat-list-item-focus'; + +/** + * Component for list-options of selection-list. Each list-option can automatically + * generate a checkbox and can put current item into the selectionModel of selection-list + * if the current item is checked. + */ +@Component({ + moduleId: module.id, + selector: 'md-list-option, mat-list-option', + host: { + 'role': 'option', + 'class': 'mat-list-item mat-list-option', + '(focus)': '_handleFocus()', + '(blur)': '_handleBlur()', + '(click)': '_handleClick()', + 'tabindex': '-1', + '[attr.aria-selected]': 'selected.toString()', + '[attr.aria-disabled]': 'disabled.toString()', + }, + templateUrl: 'list-option.html', + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class MdListOption implements AfterContentInit, OnDestroy, FocusableOption { + private _lineSetter: MdLineSetter; + private _disableRipple: boolean = false; + private _selected: boolean = false; + /** Whether the checkbox is disabled. */ + private _disabled: boolean = false; + private _value: any; + + /** Whether the option has focus. */ + _hasFocus: boolean = false; + + /** + * Whether the ripple effect on click should be disabled. This applies only to list items that are + * part of a selection list. The value of `disableRipple` on the `md-selection-list` overrides + * this flag + */ + @Input() + get disableRipple() { return this._disableRipple; } + set disableRipple(value: boolean) { this._disableRipple = coerceBooleanProperty(value); } + + @ContentChildren(MdLine) _lines: QueryList; + + /** Whether the label should appear before or after the checkbox. Defaults to 'after' */ + @Input() checkboxPosition: 'before' | 'after' = 'after'; + + /** Whether the option is disabled. */ + @Input() + get disabled() { return (this.selectionList && this.selectionList.disabled) || this._disabled; } + set disabled(value: any) { this._disabled = coerceBooleanProperty(value); } + + @Input() + get value() { return this._value; } + set value( val: any) { this._value = coerceBooleanProperty(val); } + + @Input() + get selected() { return this._selected; } + set selected( val: boolean) { this._selected = coerceBooleanProperty(val); } + + /** Emitted when the option is focused. */ + onFocus = new EventEmitter(); + + /** Emitted when the option is selected. */ + @Output() selectChange = new EventEmitter(); + + /** Emitted when the option is deselected. */ + @Output() deselected = new EventEmitter(); + + /** Emitted when the option is destroyed. */ + @Output() destroyed = new EventEmitter(); + + constructor(private _renderer: Renderer2, + private _element: ElementRef, + private _changeDetector: ChangeDetectorRef, + @Optional() public selectionList: MdSelectionList) { } + + + ngAfterContentInit() { + this._lineSetter = new MdLineSetter(this._lines, this._renderer, this._element); + + if (this.selectionList.disabled) { + this.disabled = true; + } + } + + ngOnDestroy(): void { + this.destroyed.emit({option: this}); + } + + toggle(): void { + this.selected = !this.selected; + this.selectionList.selectedOptions.toggle(this); + this._changeDetector.markForCheck(); + } + + /** Allows for programmatic focusing of the option. */ + focus(): void { + this._element.nativeElement.focus(); + this.onFocus.emit({option: this}); + } + + /** Whether this list item should show a ripple effect when clicked. */ + isRippleEnabled() { + return !this.disableRipple && !this.selectionList.disableRipple; + } + + _handleClick() { + if (!this.disabled) { + this.toggle(); + } + } + + _handleFocus() { + this._hasFocus = true; + this._renderer.addClass(this._element.nativeElement, FOCUSED_STYLE); + } + + _handleBlur() { + this._renderer.removeClass(this._element.nativeElement, FOCUSED_STYLE); + } + + /** Retrieves the DOM element of the component host. */ + _getHostElement(): HTMLElement { + return this._element.nativeElement; + } +} + + @Component({ moduleId: module.id, selector: 'md-selection-list, mat-selection-list', From 7a30b48f0fee6374cca5e6892485085f08bfbb23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AD=99=E7=90=B3?= <2808411862@qq.com> Date: Fri, 11 Aug 2017 22:11:29 -0700 Subject: [PATCH 09/13] fix google3 --- src/lib/list/selection-list.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/list/selection-list.ts b/src/lib/list/selection-list.ts index 91e513344c6c..0f1b264f5034 100644 --- a/src/lib/list/selection-list.ts +++ b/src/lib/list/selection-list.ts @@ -21,6 +21,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, OnDestroy, + Self, } from '@angular/core'; import {coerceBooleanProperty, SelectionModel, MdLine, MdLineSetter} from '../core'; import {FocusKeyManager} from '../core/a11y/focus-key-manager'; @@ -116,7 +117,7 @@ export class MdListOption implements AfterContentInit, OnDestroy, FocusableOptio constructor(private _renderer: Renderer2, private _element: ElementRef, private _changeDetector: ChangeDetectorRef, - @Optional() public selectionList: MdSelectionList) { } + @Self() @Optional() @Inject(forwardRef(() => MdButton)) public selectionList: MdSelectionList) { } ngAfterContentInit() { From 022caa3291aa79abbe69e91eceaa5c77e6c721f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AD=99=E7=90=B3?= <2808411862@qq.com> Date: Fri, 11 Aug 2017 22:13:02 -0700 Subject: [PATCH 10/13] fix --- src/lib/list/selection-list.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/list/selection-list.ts b/src/lib/list/selection-list.ts index 0f1b264f5034..625a730e55f5 100644 --- a/src/lib/list/selection-list.ts +++ b/src/lib/list/selection-list.ts @@ -117,7 +117,7 @@ export class MdListOption implements AfterContentInit, OnDestroy, FocusableOptio constructor(private _renderer: Renderer2, private _element: ElementRef, private _changeDetector: ChangeDetectorRef, - @Self() @Optional() @Inject(forwardRef(() => MdButton)) public selectionList: MdSelectionList) { } + @Self() @Optional() @Inject(forwardRef(() => MdSelectionList)) public selectionList: MdSelectionList) { } ngAfterContentInit() { From 63111b663b6dd71dac62d47abbce1541e06735e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AD=99=E7=90=B3?= <2808411862@qq.com> Date: Fri, 11 Aug 2017 22:13:52 -0700 Subject: [PATCH 11/13] Add imports --- src/lib/list/selection-list.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/list/selection-list.ts b/src/lib/list/selection-list.ts index 625a730e55f5..5613341a54d8 100644 --- a/src/lib/list/selection-list.ts +++ b/src/lib/list/selection-list.ts @@ -22,6 +22,7 @@ import { ChangeDetectorRef, OnDestroy, Self, + forwardRef, } from '@angular/core'; import {coerceBooleanProperty, SelectionModel, MdLine, MdLineSetter} from '../core'; import {FocusKeyManager} from '../core/a11y/focus-key-manager'; From 9f8a1706a644af23bdc99d4c8f9808592b11d56e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AD=99=E7=90=B3?= <2808411862@qq.com> Date: Fri, 11 Aug 2017 22:29:17 -0700 Subject: [PATCH 12/13] fix --- src/lib/list/selection-list.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/list/selection-list.ts b/src/lib/list/selection-list.ts index 5613341a54d8..219c1421a112 100644 --- a/src/lib/list/selection-list.ts +++ b/src/lib/list/selection-list.ts @@ -23,6 +23,7 @@ import { OnDestroy, Self, forwardRef, + Inject, } from '@angular/core'; import {coerceBooleanProperty, SelectionModel, MdLine, MdLineSetter} from '../core'; import {FocusKeyManager} from '../core/a11y/focus-key-manager'; @@ -118,7 +119,7 @@ export class MdListOption implements AfterContentInit, OnDestroy, FocusableOptio constructor(private _renderer: Renderer2, private _element: ElementRef, private _changeDetector: ChangeDetectorRef, - @Self() @Optional() @Inject(forwardRef(() => MdSelectionList)) public selectionList: MdSelectionList) { } + @Optional() @Inject(forwardRef(() => MdSelectionList)) public selectionList: MdSelectionList) { } ngAfterContentInit() { From ae84d3b61438bedd31dac63cffcb8cb41c7e3e14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AD=99=E7=90=B3?= <2808411862@qq.com> Date: Fri, 11 Aug 2017 22:56:02 -0700 Subject: [PATCH 13/13] Deleted unused import --- src/lib/list/selection-list.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/list/selection-list.ts b/src/lib/list/selection-list.ts index 219c1421a112..1eda2a70c32c 100644 --- a/src/lib/list/selection-list.ts +++ b/src/lib/list/selection-list.ts @@ -21,7 +21,6 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, OnDestroy, - Self, forwardRef, Inject, } from '@angular/core';