diff --git a/src/demo-app/chips/chips-demo.html b/src/demo-app/chips/chips-demo.html index a84fba98a81a..a305eb085c97 100644 --- a/src/demo-app/chips/chips-demo.html +++ b/src/demo-app/chips/chips-demo.html @@ -25,11 +25,12 @@

Advanced

Selected/Colored - cancel + (destroy)="displayMessage('chip destroyed')" (remove)="toggleVisible()"> With Events + cancel +
{{message}}
@@ -44,17 +45,17 @@

Input Container

<md-input-container>.

- - + + {{person.name}} cancel + -

diff --git a/src/demo-app/chips/chips-demo.scss b/src/demo-app/chips/chips-demo.scss index 66082bb19203..47d857492417 100644 --- a/src/demo-app/chips/chips-demo.scss +++ b/src/demo-app/chips/chips-demo.scss @@ -27,5 +27,8 @@ .mat-chip-remove.mat-icon { font-size: 18px; + width: 1em; + height: 1em; + vertical-align: middle; } } diff --git a/src/demo-app/chips/chips-demo.ts b/src/demo-app/chips/chips-demo.ts index 2b68de3b771c..8749f947bc07 100644 --- a/src/demo-app/chips/chips-demo.ts +++ b/src/demo-app/chips/chips-demo.ts @@ -22,9 +22,10 @@ export class ChipsDemo { selectable: boolean = true; removable: boolean = true; addOnBlur: boolean = true; + message: string = ''; // Enter, comma, semi-colon - separatorKeys = [ENTER, COMMA, 186]; + separatorKeysCodes = [ENTER, COMMA, 186]; people: Person[] = [ { name: 'Kara' }, @@ -42,8 +43,8 @@ export class ChipsDemo { { name: 'Warn', color: 'warn' } ]; - alert(message: string): void { - alert(message); + displayMessage(message: string): void { + this.message = message; } add(event: MdChipInputEvent): void { @@ -51,7 +52,7 @@ export class ChipsDemo { let value = event.value; // Add our person - if (value && value.trim() != '') { + if ((value || '').trim()) { this.people.push({ name: value.trim() }); } diff --git a/src/lib/chips/_chips-theme.scss b/src/lib/chips/_chips-theme.scss index 36203c6c1195..4ade4ba38636 100644 --- a/src/lib/chips/_chips-theme.scss +++ b/src/lib/chips/_chips-theme.scss @@ -6,6 +6,23 @@ $mat-chip-font-size: 13px; $mat-chip-line-height: 16px; +@mixin mat-chips-theme-color($color) { + @include mat-chips-color(mat-contrast($color, 500), mat-color($color, 500)); +} + +@mixin mat-chips-color($foreground, $background) { + background-color: $background; + color: $foreground; + + .mat-chip-remove { + color: $foreground; + opacity: 0.4; + } + + .mat-chip-remove:hover { + opacity: 0.54; + } +} @mixin mat-chips-theme($theme) { $is-dark-theme: map-get($theme, is-dark); @@ -30,9 +47,8 @@ $mat-chip-line-height: 16px; $focus-color: mat-color($foreground, secondary-text); - .mat-chip:not(.mat-basic-chip) { - background-color: $unselected-background; - color: $unselected-foreground; + .mat-chip { + @include mat-chips-color($unselected-foreground, $unselected-background); .mat-chip-focus-border { pointer-events: none; @@ -42,87 +58,27 @@ $mat-chip-line-height: 16px; outline: none; border: 2px solid $focus-color; } - - .mat-chip-remove { - color: $unselected-foreground; - opacity: 0.3; - } - - .mat-chip-remove:hover { - opacity: 0.54; - } } - .mat-chip.mat-chip-selected:not(.mat-basic-chip) { - background-color: $selected-background; - color: $selected-foreground; - - .mat-chip-remove { - color: $selected-foreground; - opacity: 0.4; - } - - .mat-chip-remove:hover { - opacity: 0.54; - } + .mat-chip.mat-chip-selected { + @include mat-chips-color($selected-foreground, $selected-background); &.mat-primary { - background-color: mat-color($primary); - color: mat-color($primary, default-contrast); - } - - &.mat-accent { - background-color: mat-color($accent); - color: mat-color($accent, default-contrast); + @include mat-chips-theme-color($primary); } &.mat-warn { - background-color: mat-color($warn); - color: mat-color($warn, default-contrast); - - .mat-chip-remove { - color: mat-contrast($primary, 500); - opacity: 0.4; - } - - .mat-chip-remove:hover { - opacity: 0.54; - } - + @include mat-chips-theme-color($warn); } &.mat-accent { - background-color: mat-color($accent, 500); - color: mat-contrast($accent, 500); - - .mat-chip-remove { - color: mat-contrast($accent, 500); - opacity: 0.4; - } - - .mat-chip-remove:hover { - opacity: 0.54; - } - } - - &.mat-warn { - background-color: mat-color($warn, 500); - color: mat-contrast($warn, 500); - - .mat-chip-remove { - color: mat-contrast($warn, 500); - opacity: 0.4; - } - - .mat-chip-remove:hover { - opacity: 0.54; - } + @include mat-chips-theme-color($accent); } } } @mixin mat-chips-typography($config) { - .mat-chip:not(.mat-basic-chip) { + .mat-chip{ font-size: $mat-chip-font-size; line-height: $mat-chip-line-height; } diff --git a/src/lib/chips/chip-input.spec.ts b/src/lib/chips/chip-input.spec.ts index 28607f063592..6b30db410219 100644 --- a/src/lib/chips/chip-input.spec.ts +++ b/src/lib/chips/chip-input.spec.ts @@ -42,7 +42,7 @@ describe('MdChipInput', () => { })); describe('basic behavior', () => { - it('emits the (chipAdded) on enter keyup', () => { + it('emits the (chipEnd) on enter keyup', () => { let ENTER_EVENT = createKeyboardEvent('keydown', ENTER, inputNativeElement) as any; spyOn(testChipInput, 'add'); @@ -53,7 +53,7 @@ describe('MdChipInput', () => { }); describe('[addOnBlur]', () => { - it('allows (chipAdded) when true', () => { + it('allows (chipEnd) when true', () => { spyOn(testChipInput, 'add'); testChipInput.addOnBlur = true; @@ -63,7 +63,7 @@ describe('MdChipInput', () => { expect(testChipInput.add).toHaveBeenCalled(); }); - it('disallows (chipAdded) when false', () => { + it('disallows (chipEnd) when false', () => { spyOn(testChipInput, 'add'); testChipInput.addOnBlur = false; @@ -74,8 +74,8 @@ describe('MdChipInput', () => { }); }); - describe('[separatorKeys]', () => { - it('does not emit (chipAdded) when a non-separator key is pressed', () => { + describe('[separatorKeysCodes]', () => { + it('does not emit (chipEnd) when a non-separator key is pressed', () => { let ENTER_EVENT = createKeyboardEvent('keydown', ENTER, inputNativeElement) as any; spyOn(testChipInput, 'add'); @@ -86,7 +86,7 @@ describe('MdChipInput', () => { expect(testChipInput.add).not.toHaveBeenCalled(); }); - it('emits (chipAdded) when a custom separator keys is pressed', () => { + it('emits (chipEnd) when a custom separator keys is pressed', () => { let COMMA_EVENT = createKeyboardEvent('keydown', COMMA, inputNativeElement) as any; spyOn(testChipInput, 'add'); @@ -102,9 +102,9 @@ describe('MdChipInput', () => { @Component({ template: ` + - ` }) class TestChipInput { diff --git a/src/lib/chips/chip-input.ts b/src/lib/chips/chip-input.ts index 4a8a1d7f219e..6af06d981afb 100644 --- a/src/lib/chips/chip-input.ts +++ b/src/lib/chips/chip-input.ts @@ -1,4 +1,5 @@ -import {Directive, Output, EventEmitter, Renderer, ElementRef, Input} from '@angular/core'; +import {Directive, Output, EventEmitter, ElementRef, Input} from '@angular/core'; +import {coerceBooleanProperty} from '../core/coercion/boolean-property'; import {ENTER} from '../core/keyboard/keycodes'; import {MdChipList} from './chip-list'; @@ -20,8 +21,8 @@ export class MdChipInput { _chipList: MdChipList; /** Register input for chip list */ - @Input() - set mdChipList(value: MdChipList) { + @Input('mdChipList') + set chipList(value: MdChipList) { if (value) { this._chipList = value; this._chipList.registerInput(this._inputElement); @@ -29,57 +30,55 @@ export class MdChipInput { } /** - * Whether or not the chipAdded event will be emitted when the input is blurred. - * - * Default `false`. + * Whether or not the chipEnd event will be emitted when the input is blurred. */ - @Input() addOnBlur = false; + @Input('mdChipListAddOnBlur') + get addOnBlur() { return this._addOnBlur; } + set addOnBlur(value) { this._addOnBlur = coerceBooleanProperty(value); } + _addOnBlur: boolean = false; /** - * The list of key codes that will trigger a chipAdded event. + * The list of key codes that will trigger a chipEnd event. * * Defaults to `[ENTER]`. */ - @Input() separatorKeys: number[] = [ENTER]; + // TODO(tinayuangao): Support Set here + @Input() separatorKeysCodes: number[] = [ENTER]; /** Emitted when a chip is to be added. */ - @Output() chipAdded = new EventEmitter(); + @Output('mdChipEnd') + chipEnd = new EventEmitter(); + + @Input('matChipList') + set matChipList(value: MdChipList) { this.chipList = value; } + + @Input('matChipListAddOnBlur') + get matAddOnBlur() { return this._addOnBlur; } + set matAddOnBlur(value) { this.addOnBlur = value; } /** The native input element to which this directive is attached. */ protected _inputElement: HTMLInputElement; - constructor(protected _renderer: Renderer, protected _elementRef: ElementRef) { + constructor(protected _elementRef: ElementRef) { this._inputElement = this._elementRef.nativeElement as HTMLInputElement; } - /** - * Utility method to make host definition/tests more clear. - * - * @private - */ + /** Utility method to make host definition/tests more clear. */ _keydown(event?: KeyboardEvent) { - this._add(event); + this._emitChipEnd(event); } - /** - * Checks to see if the blur should emit the (chipAdded) event. - * - * @private - */ + /** Checks to see if the blur should emit the (chipEnd) event. */ _blur() { if (this.addOnBlur) { - this._add(); + this._emitChipEnd(); } } - /** - * Checks to see if the (chipAdded) event needs to be emitted. - * - * @private - */ - _add(event?: KeyboardEvent) { - if (!event || this.separatorKeys.indexOf(event.keyCode) > -1) { - this.chipAdded.emit({ input: this._inputElement, value: this._inputElement.value }); + /** Checks to see if the (chipEnd) event needs to be emitted. */ + _emitChipEnd(event?: KeyboardEvent) { + if (!event || this.separatorKeysCodes.indexOf(event.keyCode) > -1) { + this.chipEnd.emit({ input: this._inputElement, value: this._inputElement.value }); if (event) { event.preventDefault(); diff --git a/src/lib/chips/chip-list.spec.ts b/src/lib/chips/chip-list.spec.ts index edbd1c29fcf7..7fa8a701d282 100644 --- a/src/lib/chips/chip-list.spec.ts +++ b/src/lib/chips/chip-list.spec.ts @@ -331,12 +331,12 @@ class StandardChipList { @Component({ template: ` - + Chip 1 Chip 1 Chip 1 + - ` }) diff --git a/src/lib/chips/chip-list.ts b/src/lib/chips/chip-list.ts index c6a3004af93a..fa22ea944de9 100644 --- a/src/lib/chips/chip-list.ts +++ b/src/lib/chips/chip-list.ts @@ -21,13 +21,16 @@ import { } from '../core/keyboard/keycodes'; import {Dir} from '../core/rtl/dir'; -@Directive({ - selector: '[mdChipListContainer], [matChipListContainer]', - host: { - '[class.mat-chip-list-container]': 'true' +/** Utility to check if an input element has no value. */ +function _isInputEmpty(element: HTMLElement): boolean { + if (element && element.nodeName.toLowerCase() == 'input') { + let input = element as HTMLInputElement; + + return !input.value; } -}) -export class MdChipListContainer {} + + return false; +} /** * A material design chips component (named ChipList for it's similarity to the List component). @@ -44,11 +47,9 @@ export class MdChipListContainer {} selector: 'md-chip-list, mat-chip-list', template: `

`, host: { - // Properties '[attr.tabindex]': '_tabIndex', 'role': 'listbox', '[class.mat-chip-list]': 'true', - // Actions '(focus)': 'focus($event)', '(keydown)': '_keydown($event)' }, @@ -65,7 +66,7 @@ export class MdChipList implements AfterContentInit, OnDestroy { protected _destroyedIndex: number = null; /** Track which chips we're listening to for focus/destruction. */ - protected _subscribed: WeakMap = new WeakMap(); + protected _chipSet: WeakMap = new WeakMap(); /** Subscription to tabbing out from the chip list. */ private _tabOutSubscription: Subscription; @@ -164,7 +165,7 @@ export class MdChipList implements AfterContentInit, OnDestroy { _keydown(event: KeyboardEvent) { let code = event.keyCode; let target = event.target as HTMLElement; - let isInputEmpty = MdChipList._isInputEmpty(target); + let isInputEmpty = _isInputEmpty(target); let isRtl = this._dir.value == 'rtl'; let isPrevKey = (code == (isRtl ? RIGHT_ARROW : LEFT_ARROW)); @@ -228,7 +229,7 @@ export class MdChipList implements AfterContentInit, OnDestroy { */ protected _addChip(chip: MdChip) { // If we've already been subscribed to a parent, do nothing - if (this._subscribed.has(chip)) { + if (this._chipSet.has(chip)) { return; } @@ -249,11 +250,11 @@ export class MdChipList implements AfterContentInit, OnDestroy { this._destroyedIndex = chipIndex; } - this._subscribed.delete(chip); + this._chipSet.delete(chip); chip.destroy.unsubscribe(); }); - this._subscribed.set(chip, true); + this._chipSet.set(chip, true); } /** @@ -291,15 +292,4 @@ export class MdChipList implements AfterContentInit, OnDestroy { private _isValidIndex(index: number): boolean { return index >= 0 && index < this.chips.length; } - - /** Utility to check if an input element has no value. */ - private static _isInputEmpty(element: HTMLElement): boolean { - if (element && element.nodeName.toLowerCase() == 'input') { - let input = element as HTMLInputElement; - - return input.value == '' || input.value == null; - } - - return false; - } } diff --git a/src/lib/chips/chip-remove.ts b/src/lib/chips/chip-remove.ts index 240502794110..d586e9360bc2 100644 --- a/src/lib/chips/chip-remove.ts +++ b/src/lib/chips/chip-remove.ts @@ -1,7 +1,9 @@ -import {Directive, Renderer2, ElementRef, OnInit, OnDestroy} from '@angular/core'; +import {Directive, Renderer2, ElementRef, Input, OnInit, OnDestroy} from '@angular/core'; import {MdChip} from './chip'; import {Subscription} from 'rxjs/Subscription'; +import {coerceBooleanProperty} from '../core/coercion/boolean-property'; + /** * Applies proper (click) support and adds styling for use with the Material Design "cancel" icon * available at https://material.io/icons/#ic_cancel. @@ -19,36 +21,23 @@ import {Subscription} from 'rxjs/Subscription'; selector: '[mdChipRemove], [matChipRemove]', host: { 'class': 'mat-chip-remove', - '[class.mat-chip-remove-hidden]': '!_isVisible', + '[class.mat-chip-remove-hidden]': '!visible', '(click)': '_handleClick($event)' } }) -export class MdChipRemove implements OnInit, OnDestroy { +export class MdChipRemove { /** Whether or not the remove icon is visible. */ - _isVisible: boolean = false; + _isVisible: boolean = true; /** Subscription for our onRemoveChange Observable */ _onRemoveChangeSubscription: Subscription; - constructor(protected _renderer: Renderer2, protected _elementRef: ElementRef, - protected _parentChip: MdChip) { - if (this._parentChip) { - this._onRemoveChangeSubscription = this._parentChip.onRemovableChange - .subscribe((value) => { - this._updateParent(value); - }); - } - } + @Input('mdChipRemoveVisible') + get visible() { return this._isVisible; } + set visible(value) {this._isVisible = coerceBooleanProperty(value);} - ngOnInit() { - this._updateParent(true); - } - - ngOnDestroy() { - this._updateParent(false); - this._onRemoveChangeSubscription.unsubscribe(); - } + constructor(protected _parentChip: MdChip) {} /** Calls the parent chip's public `remove()` method if applicable. */ _handleClick(event: Event) { @@ -56,11 +45,4 @@ export class MdChipRemove implements OnInit, OnDestroy { this._parentChip.remove(); } } - - /** Informs the parent chip whether or not it contains a remove icon. */ - _updateParent(isRemovable: boolean) { - this._isVisible = isRemovable; - this._parentChip._setHasRemoveIcon(isRemovable); - } - } diff --git a/src/lib/chips/chip.ts b/src/lib/chips/chip.ts index 78d6a1855983..0dd8d9251bff 100644 --- a/src/lib/chips/chip.ts +++ b/src/lib/chips/chip.ts @@ -1,5 +1,6 @@ import { Component, + ContentChild, ElementRef, EventEmitter, Input, @@ -14,6 +15,8 @@ import {Focusable} from '../core/a11y/focus-key-manager'; import {coerceBooleanProperty} from '../core/coercion/boolean-property'; import {SPACE, BACKSPACE, DELETE} from '../core/keyboard/keycodes'; +import {MdChipRemove} from './chip-remove'; + export interface MdChipEvent { chip: MdChip; } @@ -31,7 +34,6 @@ export interface MdChipEvent { 'role': 'option', '[class.mat-chip-selected]': 'selected', - '[class.mat-chip-has-remove-icon]': '_hasRemoveIcon', '[attr.disabled]': 'disabled', '[attr.aria-disabled]': '_isAriaDisabled', @@ -41,6 +43,8 @@ export interface MdChipEvent { }) export class MdChip implements Focusable, OnInit, OnDestroy { + @ContentChild(MdChipRemove) _chipRemove: MdChipRemove; + /** Whether or not the chip is disabled. Disabled chips cannot be focused. */ protected _disabled: boolean = null; @@ -56,13 +60,6 @@ export class MdChip implements Focusable, OnInit, OnDestroy { /** The palette color of selected chips. */ protected _color: string = 'primary'; - /** Whether or not the chip is displaying the remove icon. */ - _hasRemoveIcon: boolean = false; - - /** Emitted when the removable property changes. */ - private _onRemovableChange = new EventEmitter(); - onRemovableChange: Observable = this._onRemovableChange.asObservable(); - /** Emitted when the chip is focused. */ onFocus = new EventEmitter(); @@ -125,7 +122,7 @@ export class MdChip implements Focusable, OnInit, OnDestroy { set removable(value: boolean) { this._removable = coerceBooleanProperty(value); - this._onRemovableChange.emit(this._removable); + if (this._chipRemove) { this._chipRemove.visible = this._removable; } } /** Whether or not this chip is selected. */ @@ -180,7 +177,7 @@ export class MdChip implements Focusable, OnInit, OnDestroy { /** Ensures events fire properly upon click. */ _handleClick(event: Event) { // Check disabled - if (this._checkDisabled(event)) { + if (this.disabled) { return; } @@ -192,7 +189,7 @@ export class MdChip implements Focusable, OnInit, OnDestroy { /** Handle custom key presses. */ _handleKeydown(event: KeyboardEvent) { - if (this._checkDisabled(event)) { + if (this.disabled) { return; } @@ -219,23 +216,6 @@ export class MdChip implements Focusable, OnInit, OnDestroy { } } - /** - * Sets whether or not this chip is displaying a remove icon. Adds/removes the - * `md-chip-has-remove-icon` class. - */ - _setHasRemoveIcon(value: boolean) { - this._hasRemoveIcon = value; - } - - protected _checkDisabled(event: Event): boolean { - if (this.disabled) { - event.preventDefault(); - event.stopPropagation(); - } - - return this.disabled; - } - /** Initializes the appropriate CSS classes based on the chip type (basic or standard). */ private _addDefaultCSSClass() { let el: HTMLElement = this._elementRef.nativeElement; diff --git a/src/lib/chips/chips.scss b/src/lib/chips/chips.scss index 626d38aa2699..4940fb8442b7 100644 --- a/src/lib/chips/chips.scss +++ b/src/lib/chips/chips.scss @@ -12,6 +12,30 @@ $mat-chip-remove-icon-offset: 3px; $mat-chip-remove-size: 24px; $mat-chip-remove-font-size: 18px; +@mixin chip-margin() { + [dir='rtl'] & { + margin-left: 0; + margin-right: $mat-chip-margin; + } + + [dir='ltr'] & { + margin-right: 0; + margin-left: $mat-chip-margin; + } +} + +@mixin chip-margin-reverse() { + [dir='ltr'] & { + margin-left: 0; + margin-right: $mat-chip-margin; + } + + [dir='rtl'] & { + margin-right: 0; + margin-left: $mat-chip-margin; + } +} + .mat-chip-list-wrapper { display: flex; @@ -19,30 +43,18 @@ $mat-chip-remove-font-size: 18px; flex-wrap: wrap; align-items: flex-start; - .mat-chip:not(.mat-basic-chip) { + .mat-chip { margin: $mat-chip-margin; // Do not apply the special margins inside an input container // Remove the margin from the first element (in both LTR and RTL) &:first-child { - margin-left: 0; - margin-right: $mat-chip-margin; - - [dir='rtl'] & { - margin-right: 0; - margin-left: $mat-chip-margin; - } + @include chip-margin-reverse(); } // Remove the margin from the last element (in both LTR and RTL) &:last-child { - margin-right: 0; - margin-left: $mat-chip-margin; - - [dir='rtl'] & { - margin-left: 0; - margin-right: $mat-chip-margin; - } + @include chip-margin(); } } @@ -52,7 +64,7 @@ $mat-chip-remove-font-size: 18px; flex-wrap: nowrap; } -.mat-chip:not(.mat-basic-chip) { +.mat-chip { display: inline-block; position: relative; @@ -62,56 +74,28 @@ $mat-chip-remove-font-size: 18px; font-size: $mat-chip-font-size; line-height: $mat-chip-line-height; - - &.mat-chip-has-remove-icon { - padding-right: $mat-chip-remove-margin; - } } .mat-chip-list-stacked .mat-chip-list-wrapper { display: block; - .mat-chip:not(.mat-basic-chip) { + .mat-chip { display: block; margin: 0; margin-bottom: $mat-chip-vertical-padding; } } -.mat-chip-remove { - position: absolute; - top: $mat-chip-border-width; - right: $mat-chip-border-width * 2; - width: $mat-chip-remove-size; - height: $mat-chip-remove-size - $mat-chip-remove-icon-offset; - padding-top: $mat-chip-remove-icon-offset; - font-size: $mat-chip-remove-font-size; - text-align: center; - cursor: default; +md-chip-list .mat-chip-input { + line-height: 34px; + @include chip-margin(); } -.mat-chip-remove.mat-chip-remove-hidden { - display: none; +md-chip-list .mat-input-placeholder-wrapper { + top: 0; + @include chip-margin(); } -.mat-chip-list-container .mat-input-placeholder-wrapper { - top: -4px; - - [dir='rtl'] & { - right: 8px; - } - - [dir='ltr'] & { - margin-left: 8px; - } -} - -.mat-chip-input { - [dir='rtl'] & { - margin-right: 8px; - } - - [dir='ltr'] & { - margin-left: 8px; - } +.mat-chip-remove.mat-chip-remove-hidden { + display: none; } diff --git a/src/lib/chips/index.ts b/src/lib/chips/index.ts index 8240cabfbb5e..d777112cb44d 100644 --- a/src/lib/chips/index.ts +++ b/src/lib/chips/index.ts @@ -1,5 +1,5 @@ import {NgModule} from '@angular/core'; -import {MdChipList, MdChipListContainer} from './chip-list'; +import {MdChipList} from './chip-list'; import {MdChip} from './chip'; import {MdChipInput} from './chip-input'; import {MdChipRemove} from './chip-remove'; @@ -11,7 +11,7 @@ export * from './chip-remove'; @NgModule({ imports: [], - exports: [MdChipList, MdChip, MdChipInput, MdChipRemove, MdChipListContainer], - declarations: [MdChipList, MdChip, MdChipInput, MdChipRemove, MdChipListContainer] + exports: [MdChipList, MdChip, MdChipInput, MdChipRemove], + declarations: [MdChipList, MdChip, MdChipInput, MdChipRemove] }) export class MdChipsModule {}