forked from angular/components
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add basic focus/keyboard support for chips. - Up/down arrows navigate chips. - Clicking a chip properly focuses it for subsequent keyboard navigation. - More demos. References angular#120.
- Loading branch information
1 parent
cf1b4b9
commit 1333718
Showing
22 changed files
with
815 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
<div class="chips-demo"> | ||
<section> | ||
<h3>Static Chips</h3> | ||
|
||
<h5>Simple</h5> | ||
|
||
<md-chip-list> | ||
<md-chip>Chip 1</md-chip> | ||
<md-chip>Chip 2</md-chip> | ||
<md-chip>Chip 3</md-chip> | ||
</md-chip-list> | ||
|
||
<h5>Advanced</h5> | ||
|
||
<md-chip-list> | ||
<md-chip class="md-accent selected">Selected/Colored</md-chip> | ||
<md-chip class="md-warn" *ngIf="visible" | ||
(destroy)="alert('chip destroyed')" (click)="toggleVisible()"> | ||
With Events | ||
</md-chip> | ||
</md-chip-list> | ||
|
||
<h5>Unstyled</h5> | ||
|
||
<md-chip-list> | ||
<md-basic-chip>Basic Chip 1</md-basic-chip> | ||
<md-basic-chip>Basic Chip 2</md-basic-chip> | ||
<md-basic-chip>Basic Chip 3</md-basic-chip> | ||
</md-chip-list> | ||
|
||
<h3>Material Contributors</h3> | ||
|
||
<md-chip-list> | ||
<md-chip *ngFor="let person of people; let even = even" [ngClass]="[color, even ? 'selected' : '' ]"> | ||
{{person.name}} | ||
</md-chip> | ||
</md-chip-list> | ||
|
||
<br /> | ||
|
||
<md-input #input (keyup.enter)="add(input)" (blur)="add(input)" placeholder="New Contributor..."> | ||
</md-input> | ||
|
||
<h3>Stacked Chips</h3> | ||
|
||
<p> | ||
You can also stack the chips if you want them on top of each other. | ||
</p> | ||
|
||
<md-chip-list class="md-chip-list-stacked"> | ||
<md-chip (didfocus)="color = ''" class="selected"> | ||
None | ||
</md-chip> | ||
|
||
<md-chip (didfocus)="color = 'md-primary'" class="selected md-primary"> | ||
Primary | ||
</md-chip> | ||
|
||
<md-chip (didfocus)="color = 'md-accent'" class="selected md-accent"> | ||
Accent | ||
</md-chip> | ||
|
||
<md-chip (didfocus)="color = 'md-warn'" class="selected md-warn"> | ||
Warn | ||
</md-chip> | ||
</md-chip-list> | ||
</section> | ||
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
.chips-demo { | ||
.md-chip-list-stacked { | ||
display: block; | ||
max-width: 200px; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import {Component} from '@angular/core'; | ||
|
||
export interface Person { | ||
name: string; | ||
} | ||
|
||
@Component({ | ||
moduleId: module.id, | ||
selector: 'chips-demo', | ||
templateUrl: 'chips-demo.html', | ||
styleUrls: ['chips-demo.css'] | ||
}) | ||
export class ChipsDemo { | ||
visible: boolean = true; | ||
color: string = ''; | ||
|
||
people: Person[] = [ | ||
{ name: 'Kara' }, | ||
{ name: 'Jeremy' }, | ||
{ name: 'Topher' }, | ||
{ name: 'Elad' }, | ||
{ name: 'Kristiyan' }, | ||
{ name: 'Paul' } | ||
]; | ||
favorites: Person[] = []; | ||
|
||
alert(message: string): void { | ||
alert(message); | ||
} | ||
|
||
add(input: HTMLInputElement): void { | ||
if (input.value && input.value.trim() != '') { | ||
this.people.push({ name: input.value.trim() }); | ||
input.value = ''; | ||
} | ||
} | ||
|
||
toggleVisible(): void { | ||
this.visible = false; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
# md-chips | ||
|
||
`md-chips` provides a horizontal display of (optionally) selectable, addable, and removable, | ||
items and an input to create additional ones (again; optional). You can read more about chips | ||
in the [Material Design spec](https://material.google.com/components/chips.html). | ||
|
||
This is a placeholder README for the eventual chips component. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
@import '../core/theming/theming'; | ||
|
||
@mixin md-chips-theme($theme) { | ||
$is-dark-theme: map-get($theme, is-dark); | ||
$primary: map-get($theme, primary); | ||
$accent: map-get($theme, accent); | ||
$warn: map-get($theme, warn); | ||
$background: map-get($theme, background); | ||
|
||
|
||
.md-chip.selected { | ||
// TODO: Based on spec, this should be #808080, but we can only use md-contrast with a palette | ||
background-color: md-color($md-grey, 600); | ||
color: md-contrast($md-grey, 600); | ||
|
||
&.md-primary { | ||
background-color: md-color($primary, 500); | ||
color: md-contrast($primary, 500); | ||
} | ||
&.md-accent { | ||
background-color: md-color($accent, 500); | ||
color: md-contrast($accent, 500); | ||
} | ||
&.md-warn { | ||
background-color: md-color($warn, 500); | ||
color: md-contrast($warn, 500); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
import {QueryList} from '@angular/core'; | ||
import {async, TestBed} from '@angular/core/testing'; | ||
import {MdBasicChip} from './index'; | ||
import {ChipListKeyManager} from './chip-list-key-manager'; | ||
|
||
class FakeChip extends MdBasicChip { | ||
constructor() { | ||
// Pass in null for the renderer/elementRef | ||
super(null, null); | ||
} | ||
|
||
// Override the focus() method to NOT call the underlying renderer (which is null) | ||
focus() { | ||
this.didfocus.emit(); | ||
} | ||
} | ||
|
||
describe('ChipListKeyManager', () => { | ||
let items: QueryList<MdBasicChip>; | ||
let manager: ChipListKeyManager; | ||
|
||
beforeEach(async(() => { | ||
items = new QueryList<MdBasicChip>(); | ||
items.reset([ | ||
new FakeChip(), | ||
new FakeChip(), | ||
new FakeChip(), | ||
new FakeChip(), | ||
new FakeChip() | ||
]); | ||
|
||
manager = new ChipListKeyManager(items); | ||
|
||
TestBed.compileComponents(); | ||
})); | ||
|
||
describe('basic behaviors', () => { | ||
it('watches for chip focus', () => { | ||
let array = items.toArray(); | ||
let lastIndex = array.length - 1; | ||
let lastItem = array[lastIndex]; | ||
|
||
lastItem.focus(); | ||
|
||
expect(manager.focusedItemIndex).toBe(lastIndex); | ||
}); | ||
|
||
describe('on chip destroy', () => { | ||
it('focuses the next item', () => { | ||
let array = items.toArray(); | ||
let midItem = array[2]; | ||
|
||
// Focus the middle item | ||
midItem.focus(); | ||
|
||
// Destroy the middle item | ||
midItem.destroy.emit(); | ||
|
||
// It focuses the 4th item (now at index 2) | ||
expect(manager.focusedItemIndex).toEqual(2); | ||
}); | ||
|
||
it('focuses the previous item', () => { | ||
let array = items.toArray(); | ||
let lastIndex = array.length - 1; | ||
let lastItem = array[lastIndex]; | ||
|
||
// Focus the last item | ||
lastItem.focus(); | ||
|
||
// Destroy the last item | ||
lastItem.destroy.emit(); | ||
|
||
// It focuses the next-to-last item | ||
expect(manager.focusedItemIndex).toEqual(lastIndex - 1); | ||
}); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
import {QueryList} from '@angular/core'; | ||
import {ListKeyManager} from '../core/a11y/list-key-manager'; | ||
import {MdBasicChip} from './chip'; | ||
|
||
/** | ||
* Manages keyboard events for the chip list and its chips. When instantiated | ||
* with a QueryList of MdBasicChip (i.e. any chip), it will ensure focus and | ||
* keyboard navigation are properly handled. | ||
*/ | ||
export class ChipListKeyManager extends ListKeyManager { | ||
private _subscribed: MdBasicChip[] = []; | ||
|
||
constructor(private _chips: QueryList<MdBasicChip>) { | ||
super(_chips); | ||
|
||
// Go ahead and subscribe all of the initial chips | ||
this.subscribeChips(this._chips); | ||
|
||
// When the list changes, re-subscribe | ||
this._chips.changes.subscribe((chips: QueryList<MdBasicChip>) => { | ||
this.subscribeChips(chips); | ||
}); | ||
} | ||
|
||
/** | ||
* Iterate through the list of chips and add them to our list of | ||
* subscribed chips. | ||
* | ||
* @param chips The list of chips to be subscribed. | ||
*/ | ||
protected subscribeChips(chips: QueryList<MdBasicChip>): void { | ||
chips.forEach((chip: MdBasicChip) => { | ||
this.addChip(chip); | ||
}); | ||
} | ||
|
||
/** | ||
* Add a specific chip to our subscribed list. If the chip has | ||
* already been subscribed, this ensures it is only subscribed | ||
* once. | ||
* | ||
* @param chip The chip to be subscribed (or checked for existing | ||
* subscription). | ||
*/ | ||
protected addChip(chip: MdBasicChip) { | ||
// If we've already been subscribed to a parent, do nothing | ||
if (this._subscribed.indexOf(chip) > -1) { | ||
return; | ||
} | ||
|
||
// Watch for focus events outside of the keyboard navigation | ||
chip.didfocus.subscribe(() => { | ||
let chipIndex: number = this._chips.toArray().indexOf(chip); | ||
|
||
if (this.isValidIndex(chipIndex)) { | ||
this.setFocus(chipIndex, false); | ||
} | ||
}); | ||
|
||
// On destroy, remove the item from our list, and check focus | ||
chip.destroy.subscribe(() => { | ||
let chipIndex: number = this._chips.toArray().indexOf(chip); | ||
|
||
if (this.isValidIndex(chipIndex)) { | ||
// | ||
if (chipIndex < this._chips.length - 1) { | ||
this.setFocus(chipIndex); | ||
} else if (chipIndex - 1 >= 0) { | ||
this.setFocus(chipIndex - 1); | ||
} | ||
} | ||
|
||
this._subscribed.splice(this._subscribed.indexOf(chip), 1); | ||
chip.destroy.unsubscribe(); | ||
}); | ||
|
||
this._subscribed.push(chip); | ||
} | ||
|
||
/** | ||
* Utility to ensure all indexes are valid. | ||
* | ||
* @param index The index to be checked. | ||
* @returns {boolean} True if the index is valid for our list of chips. | ||
*/ | ||
private isValidIndex(index: number): boolean { | ||
return index >= 0 && index < this._chips.length; | ||
} | ||
} |
Oops, something went wrong.