feat(md-checkbox): Implement md-checkbox.

This commit adds the `md-checkbox` component to the material2 repo. It
includes unit tests and demos for the component. Note that while there
is a demo which exhibits all functionality of the component, the "Nested
Checklist" demo is still a WIP.

Fixes #35

Closes #131
This commit is contained in:
Travis Kaufman
2016-03-01 10:56:28 -05:00
committed by Jeremy Elbourn
parent f513be83b4
commit 431141a827
12 changed files with 1632 additions and 17 deletions
+22
View File
@@ -0,0 +1,22 @@
<div class="md-checkbox-layout">
<div class="md-checkbox-inner-container">
<div class="md-checkbox-frame"></div>
<div class="md-checkbox-background">
<svg version="1.1"
class="md-checkbox-checkmark"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
xml:space="preserve">
<path class="md-checkbox-checkmark-path"
fill="none"
stroke="white"
d="M4.1,12.7 9,17.6 20.3,6.3"/>
</svg>
<!-- Element for rendering the indeterminate state checkbox. -->
<div class="md-checkbox-mixedmark"></div>
</div>
</div>
<label [id]="labelId">
<ng-content></ng-content>
</label>
</div>
+460
View File
@@ -0,0 +1,460 @@
@import "default-theme";
@import "theme-functions";
@import "variables";
/** The width/height of the checkbox element. */
$md-checkbox-size: 18px !default;
/** The width of the line used to draw the checkmark / mixedmark. */
$md-checkbox-mark-stroke-size: 2/15 * $md-checkbox-size !default;
/** The width of the checkbox border shown when the checkbox is unchecked. */
$md-checkbox-border-width: 2px;
/** The color of the checkbox border. */
$md-checkbox-border-color: if($md-is-dark-theme, rgba(white, 0.7), rgba(black, 0.54)) !default;
/** The color of the checkbox's checkmark / mixedmark. */
$md-checkbox-mark-color: md-color($md-background, background);
/** The color that is used as the checkbox background when it is checked. */
$md-checkbox-background-color: md-color($md-primary, 500);
/** The base duration used for the majority of transitions for the checkbox. */
$md-checkbox-transition-duration: 140ms;
/** The amount of spacing between the checkbox and its label. */
$md-checkbox-item-spacing: 4px;
// Manual calculation done on SVG
$_md-checkbox-mark-path-length: 22.910259;
$_md-checkbox-indeterminate-checked-easing-function: cubic-bezier(0.14, 0, 0, 1);
/**
* Fades in the background of the checkbox when it goes from unchecked -> {checked,indeterminate}.
*/
@keyframes md-checkbox-fade-in-background {
0% {
opacity: 0;
}
50% {
opacity: 1;
}
}
/**
* Fades out the background of the checkbox when it goes from {checked,indeterminate} -> unchecked.
*/
@keyframes md-checkbox-fade-out-background {
0%, 50% {
opacity: 1;
}
100% {
opacity: 0;
}
}
/**
* "Draws" in the checkmark when the checkbox goes from unchecked -> checked.
*/
@keyframes md-checkbox-unchecked-checked-checkmark-path {
0%, 50% {
stroke-dashoffset: $_md-checkbox-mark-path-length;
}
50% {
animation-timing-function: $md-linear-out-slow-in-timing-function;
}
100% {
stroke-dashoffset: 0;
}
}
/**
* Horizontally expands the mixedmark when the checkbox goes from unchecked -> indeterminate.
*/
@keyframes md-checkbox-unchecked-indeterminate-mixedmark {
0%, 68.2% {
transform: scaleX(0);
}
68.2% {
animation-timing-function: cubic-bezier(0, 0, 0, 1);
}
100% {
transform: scaleX(1);
}
}
/**
* "Erases" the checkmark when the checkbox goes from checked -> unchecked.
*/
@keyframes md-checkbox-checked-unchecked-checkmark-path {
from {
animation-timing-function: $md-fast-out-linear-in-timing-function;
stroke-dashoffset: 0;
}
to {
stroke-dashoffset: $_md-checkbox-mark-path-length * -1;
}
}
/**
* Rotates and fades out the checkmark when the checkbox goes from checked -> indeterminate. This
* animation helps provide the illusion of the checkmark "morphing" into the mixedmark.
*/
@keyframes md-checkbox-checked-indeterminate-checkmark {
from {
animation-timing-function: $md-linear-out-slow-in-timing-function;
opacity: 1;
transform: rotate(0deg);
}
to {
opacity: 0;
transform: rotate(45deg);
}
}
/**
* Rotates and fades the checkmark back into position when the checkbox goes from indeterminate ->
* checked. This animation helps provide the illusion that the mixedmark is "morphing" into the
* checkmark.
*/
@keyframes md-checkbox-indeterminate-checked-checkmark {
from {
animation-timing-function: $_md-checkbox-indeterminate-checked-easing-function;
opacity: 0;
transform: rotate(45deg);
}
to {
opacity: 1;
transform: rotate(360deg);
}
}
/**
* Rotates and fades in the mixedmark when the checkbox goes from checked -> indeterminate. This
* animation, similar to md-checkbox-checked-indeterminate-checkmark, helps provide an illusion
* of "morphing" from checkmark -> mixedmark.
*/
@keyframes md-checkbox-checked-indeterminate-mixedmark {
from {
animation-timing-function: $md-linear-out-slow-in-timing-function;
opacity: 0;
transform: rotate(-45deg);
}
to {
opacity: 1;
transform: rotate(0deg);
}
}
/**
* Rotates and fades out the mixedmark when the checkbox goes from indeterminate -> checked. This
* animation, similar to md-checkbox-indeterminate-checked-checkmark, helps provide an illusion
* of "morphing" from mixedmark -> checkmark.
*/
@keyframes md-checkbox-indeterminate-checked-mixedmark {
from {
animation-timing-function: $_md-checkbox-indeterminate-checked-easing-function;
opacity: 1;
transform: rotate(0deg);
}
to {
opacity: 0;
transform: rotate(315deg);
}
}
/**
* Horizontally collapses and fades out the mixedmark when the checkbox goes from indeterminate ->
* unchecked.
*/
@keyframes md-checkbox-indeterminate-unchecked-mixedmark {
0% {
animation-timing-function: linear;
opacity: 1;
transform: scaleX(1);
}
32.8%, 100% {
opacity: 0;
transform: scaleX(0);
}
}
/**
* Applied to elements that cover the checkbox's entire inner container.
*/
%md-checkbox-cover-element {
bottom: 0;
left: 0;
position: absolute;
right: 0;
top: 0;
}
/**
* Applied to elements that are considered "marks" within the checkbox, e.g. the checkmark and
* the mixedmark.
*/
%md-checkbox-mark {
$width-padding-inset: 2 * $md-checkbox-border-width;
width: calc(100% - #{$width-padding-inset});
will-change: opacity, transform;
}
/**
* Applied to elements that appear to make up the outer box of the checkmark, such as the frame
* that contains the border and the actual background element that contains the marks.
*/
%md-checkbox-outer-box {
@extend %md-checkbox-cover-element;
border-radius: 2px;
box-sizing: border-box;
pointer-events: none;
}
.md-checkbox {
cursor: pointer;
&:focus {
// TODO(traviskaufman): Add ink ripple on focus state, once ripple is implemented.
outline: none;
}
}
.md-checkbox-layout {
align-items: baseline;
display: inline-flex;
}
.md-checkbox-inner-container {
display: inline-block;
height: $md-checkbox-size;
line-height: 0;
margin: auto;
margin-right: $md-checkbox-item-spacing;
order: 0;
position: relative;
vertical-align: middle;
white-space: nowrap;
width: $md-checkbox-size;
[dir="rtl"] & {
margin:{
left: $md-checkbox-item-spacing;
right: auto;
}
}
}
.md-checkbox-frame {
@extend %md-checkbox-outer-box;
background-color: transparent;
border: $md-checkbox-border-width solid $md-checkbox-border-color;
transition: border-color $md-checkbox-transition-duration $md-linear-out-slow-in-timing-function;
will-change: border-color;
}
.md-checkbox-background {
@extend %md-checkbox-outer-box;
align-items: center;
background-color: $md-checkbox-background-color;
opacity: 0;
display: inline-flex;
justify-content: center;
transition: background-color $md-checkbox-transition-duration
$md-linear-out-slow-in-timing-function,
opacity $md-checkbox-transition-duration $md-linear-out-slow-in-timing-function;
will-change: background-color, opacity;
}
.md-checkbox-checkmark {
@extend %md-checkbox-cover-element;
@extend %md-checkbox-mark;
fill: $md-checkbox-mark-color;
width: 100%;
}
.md-checkbox-checkmark-path {
// !important is needed here because a stroke must be set as an attribute on the SVG in order
// for line animation to work properly.
stroke: $md-checkbox-mark-color !important;
stroke: {
dashoffset: $_md-checkbox-mark-path-length;
dasharray: $_md-checkbox-mark-path-length;
width: $md-checkbox-mark-stroke-size;
}
}
.md-checkbox-mixedmark {
@extend %md-checkbox-mark;
background-color: $md-checkbox-mark-color;
height: $md-checkbox-mark-stroke-size;
opacity: 0;
transform: scaleX(0) rotate(0deg);
}
.md-checkbox-align-end {
.md-checkbox-inner-container {
order: 1;
margin: {
left: $md-checkbox-item-spacing;
right: auto;
}
[dir="rtl"] & {
margin: {
left: auto;
right: $md-checkbox-item-spacing;
}
}
}
}
.md-checkbox-checked {
.md-checkbox-checkmark {
opacity: 1;
}
.md-checkbox-checkmark-path {
stroke-dashoffset: 0;
}
.md-checkbox-mixedmark {
transform: scaleX(1) rotate(-45deg);
}
.md-checkbox-background {
opacity: 1;
}
}
.md-checkbox-indeterminate {
.md-checkbox-background {
opacity: 1;
}
.md-checkbox-checkmark {
opacity: 0;
transform: rotate(45deg);
}
.md-checkbox-checkmark-path {
stroke-dashoffset: 0;
}
.md-checkbox-mixedmark {
opacity: 1;
transform: scaleX(1) rotate(0deg);
}
}
.md-checkbox-disabled {
// NOTE(traviskaufman): While the spec calls for translucent blacks/whites for disabled colors,
// this does not work well with elements layered on top of one another. To get around this we
// blend the colors together based on the base color and the theme background.
$white-30pct-opacity-on-dark-theme: #686868;
$black-26pct-opacity-on-light-theme: #b0b0b0;
$disabled-color: if(
$md-is-dark-theme, $white-30pct-opacity-on-dark-theme, $black-26pct-opacity-on-light-theme);
cursor: default;
&.md-checkbox-checked,
&.md-checkbox-indeterminate {
.md-checkbox-background {
background-color: $disabled-color;
}
}
&:not(.md-checkbox-checked) {
.md-checkbox-frame {
border-color: $disabled-color;
}
}
}
.md-checkbox-anim {
$indeterminate-change-duration: 500ms;
&-unchecked-checked {
.md-checkbox-background {
animation: $md-checkbox-transition-duration * 2 linear 0s md-checkbox-fade-in-background;
}
.md-checkbox-checkmark-path {
// Instead of delaying the animation, we simply multiply its length by 2 and begin the
// animation at 50% in order to prevent a flash of styles applied to a checked checkmark
// as the background is fading in before the animation begins.
animation:
$md-checkbox-transition-duration * 2 linear 0s md-checkbox-unchecked-checked-checkmark-path;
}
}
&-unchecked-indeterminate {
.md-checkbox-background {
animation: $md-checkbox-transition-duration * 2 linear 0s md-checkbox-fade-in-background;
}
.md-checkbox-mixedmark {
animation:
$md-checkbox-transition-duration linear 0s md-checkbox-unchecked-indeterminate-mixedmark;
}
}
&-checked-unchecked {
.md-checkbox-background {
animation: $md-checkbox-transition-duration * 2 linear 0s md-checkbox-fade-out-background;
}
.md-checkbox-checkmark-path {
animation:
$md-checkbox-transition-duration linear 0s md-checkbox-checked-unchecked-checkmark-path;
}
}
&-checked-indeterminate {
.md-checkbox-checkmark {
animation:
$md-checkbox-transition-duration linear 0s md-checkbox-checked-indeterminate-checkmark;
}
.md-checkbox-mixedmark {
animation:
$md-checkbox-transition-duration linear 0s md-checkbox-checked-indeterminate-mixedmark;
}
}
&-indeterminate-checked {
.md-checkbox-checkmark {
animation:
$indeterminate-change-duration linear 0s md-checkbox-indeterminate-checked-checkmark;
}
.md-checkbox-mixedmark {
animation:
$indeterminate-change-duration linear 0s md-checkbox-indeterminate-checked-mixedmark;
}
}
&-indeterminate-unchecked {
.md-checkbox-background {
animation: $md-checkbox-transition-duration * 2 linear 0s md-checkbox-fade-out-background;
}
.md-checkbox-mixedmark {
animation:
$indeterminate-change-duration * 0.6 linear 0s
md-checkbox-indeterminate-unchecked-mixedmark;
}
}
}
+733
View File
@@ -0,0 +1,733 @@
import {
fakeAsync,
inject,
tick,
ComponentFixture,
TestComponentBuilder,
} from 'angular2/testing';
import {
it,
iit,
describe,
ddescribe,
expect,
beforeEach,
} from '../../core/facade/testing';
import {Component, DebugElement, EventEmitter} from 'angular2/core';
import {By} from 'angular2/platform/browser';
import {MdCheckbox} from './checkbox';
// IE11 does not support event constructors, so we need to perform this check.
var BROWSER_SUPPORTS_EVENT_CONSTRUCTORS: boolean = (function() {
// See: https://github.com/rauschma/event_constructors_check/blob/gh-pages/index.html#L39
try {
return new Event('submit', { bubbles: false }).bubbles === false &&
new Event('submit', { bubbles: true }).bubbles === true;
} catch (e) {
return false;
}
})();
export function main() {
describe('MdCheckbox', function() {
var builder: TestComponentBuilder;
beforeEach(inject([TestComponentBuilder], function(tcb: TestComponentBuilder) {
builder = tcb;
}));
it('attaches a class "md-checkbox" to the host element', function(done: () => void) {
builder.createAsync(CheckboxController).then(function(fixture) {
fixture.detectChanges();
let el = fixture.debugElement.query(By.css('.md-checkbox'));
expect(el).not.toBeNull();
}).then(done).catch(done);
});
it('attaches a unique id to the host element', function(done: () => void) {
builder.createAsync(CheckboxMultiController).then(function(fixture) {
fixture.detectChanges();
let first = fixture.debugElement.query(By.css('.md-checkbox:first-of-type'));
let second = fixture.debugElement.query(By.css('.md-checkbox:nth-of-type(2)'));
expect(first.nativeElement.id).toMatch(/^md\-checkbox\-\d$/g);
expect(second.nativeElement.id).toMatch(/^md\-checkbox\-\d$/g);
expect(first.nativeElement.id).not.toEqual(second.nativeElement.id);
}).then(done).catch(done);
});
it('allows clients to provide their own id', function(done: () => void) {
builder.createAsync(CheckboxCustomIdController).then(function(fixture) {
fixture.detectChanges();
let component = fixture.componentInstance;
let el = fixture.debugElement.query(By.css('.md-checkbox'));
expect(el.nativeElement.id).toEqual(component.checkboxId);
}).then(done).catch(done);
});
it('creates a label with an id based off the checkbox id', function(done: () => void) {
builder.createAsync(CheckboxController).then(function(fixture) {
fixture.detectChanges();
let el = fixture.debugElement.query(By.css('.md-checkbox'));
let label = el.nativeElement.querySelector('label');
expect(label.id).toEqual(`${el.nativeElement.id}-label`);
}).then(done).catch(done);
});
it('uses <ng-content></ng-content> for the label markup', function(done: () => void) {
builder.createAsync(CheckboxController).then(function(fixture) {
fixture.detectChanges();
let el = fixture.debugElement.query(By.css('.md-checkbox'));
let label = el.nativeElement.querySelector('label');
expect(label.innerHTML.trim()).toEqual('<em>my</em> checkbox');
}).then(done).catch(done);
});
it('adds a checkbox role attribute to the host element', function(done: () => void) {
builder.createAsync(CheckboxController).then(function(fixture) {
fixture.detectChanges();
let el = fixture.debugElement.query(By.css('.md-checkbox'));
expect(el.nativeElement.getAttribute('role')).toEqual('checkbox');
}).then(done).catch(done);
});
it('defaults "aria-checked" to "false" on the host element', function(done: () => void) {
builder.createAsync(CheckboxController).then(function(fixture) {
fixture.detectChanges();
let el = fixture.debugElement.query(By.css('.md-checkbox'));
expect(el.nativeElement.getAttribute('aria-checked')).toEqual('false');
}).then(done).catch(done);
});
it('defaults "aria-disabled" to "false" on the host element', function(done: () => void) {
builder.createAsync(CheckboxController).then(function(fixture) {
fixture.detectChanges();
let el = fixture.debugElement.query(By.css('.md-checkbox'));
expect(el.nativeElement.getAttribute('aria-disabled')).toEqual('false');
}).then(done).catch(done);
});
it('defaults tabindex to 0 to the host element', function(done: () => void) {
builder.createAsync(CheckboxController).then(function(fixture) {
fixture.detectChanges();
let el = fixture.debugElement.query(By.css('.md-checkbox'));
expect(el.nativeElement.tabIndex).toEqual(0);
}).then(done).catch(done);
});
it('allows clients to provide their own tabindex', function(done: () => void) {
builder.createAsync(CheckboxCustomTabindexController).then(function(fixture) {
fixture.detectChanges();
let component = fixture.componentInstance;
let el = fixture.debugElement.query(By.css('.md-checkbox'));
expect(el.nativeElement.tabIndex).toEqual(component.checkboxTabindex);
}).then(done).catch(done);
});
it('allows clients to provide an aria-label', function(done: () => void) {
builder.createAsync(CheckboxCustomArialabelController).then(function(fixture) {
fixture.detectChanges();
let component = fixture.componentInstance;
let el = fixture.debugElement.query(By.css('.md-checkbox'));
expect(el.nativeElement.getAttribute('aria-label')).toEqual(component.checkboxLabel);
}).then(done).catch(done);
});
it('sets the "aria-labelledby" attribute to the id of the label', function(done: () => void) {
builder.createAsync(CheckboxController).then(function(fixture) {
fixture.detectChanges();
let el = fixture.debugElement.query(By.css('.mc-checkbox'));
let label = el.nativeElement.querySelector('label');
expect(el.nativeElement.getAttribute('aria-labelledby')).toEqual(label.id);
}).then(done).catch(done);
});
describe('when given an "align" input with a value of "end"', function() {
var fixture: ComponentFixture;
beforeEach(function(done: () => void) {
builder.createAsync(CheckboxEndAlignedController).then(function(f) {
fixture = f;
fixture.detectChanges();
}).then(done).catch(done);
});
it('sets an "md-checkbox-align-end" class on the host element', function() {
let el = fixture.debugElement.query(By.css('.md-checkbox'));
expect(el.nativeElement.className).toContain('md-checkbox-align-end');
});
});
describe(`when the checkbox's checked value is set`, function() {
var fixture: ComponentFixture;
var controller: CheckboxController;
var changePromise: Promise<boolean>;
var waitingForChange: boolean;
function waitForChange(): Promise<boolean> {
if (waitingForChange) {
throw new Error('You are already waiting for a change!');
}
waitingForChange = true;
return new Promise(function(resolve, reject) {
controller.eventProxy.subscribe(resolve, reject);
}).then(function(val) {
waitingForChange = false;
return val;
});
}
beforeEach(function(done: () => void) {
builder.createAsync(CheckboxController).then(function(f) {
fixture = f;
controller = fixture.componentInstance;
changePromise = waitForChange();
controller.isChecked = true;
fixture.detectChanges();
}).then(done).catch(done);
});
it('adds a "md-checkbox-checked" modifier class to the host element', function() {
let el = fixture.debugElement.query(By.css('.md-checkbox'));
expect(el.nativeElement.className).toContain('md-checkbox-checked');
});
it('sets "aria-checked" to be "true" on the host element', function() {
let el = fixture.debugElement.query(By.css('.md-checkbox'));
expect(el.nativeElement.getAttribute('aria-checked')).toEqual('true');
});
it('emits a change event with the currently checked value', function(done: () => void) {
changePromise.then(function(isChecked) {
expect(isChecked).toBe(true);
let nextChangePromise = waitForChange();
controller.isChecked = false;
fixture.detectChanges();
return nextChangePromise;
}).then(function(isChecked) {
expect(isChecked).toBe(false);
}).then(done).catch(done);
});
});
describe('when the checkbox is indeterminate', function() {
var fixture: ComponentFixture;
var controller: CheckboxController;
beforeEach(function(done: () => void) {
builder.createAsync(CheckboxController).then(function(f) {
fixture = f;
controller = fixture.componentInstance;
controller.isIndeterminate = true;
fixture.detectChanges();
}).then(done).catch(done);
});
it('adds a "md-checkbox-indeterminate" class to the host element', function() {
let el = fixture.debugElement.query(By.css('.md-checkbox'));
expect(el.nativeElement.className).toContain('md-checkbox-indeterminate');
});
it('sets "aria-checked" to "mixed" on the host element', function() {
let el = fixture.debugElement.query(By.css('.md-checkbox'));
expect(el.nativeElement.getAttribute('aria-checked')).toEqual('mixed');
});
describe('when re-checked to true', function() {
beforeEach(function() {
controller.isChecked = true;
fixture.detectChanges();
});
it('removes md-checkbox-indeterminate', function() {
let el = fixture.debugElement.query(By.css('.md-checkbox'));
expect(el.nativeElement.className).not.toContain('md-checkbox-indeterminate');
});
});
describe('when re-checked to false', function() {
beforeEach(function() {
controller.isChecked = true;
fixture.detectChanges();
controller.isIndeterminate = true;
fixture.detectChanges();
controller.isChecked = false;
fixture.detectChanges();
});
it('removes md-checkbox-indeterminate', function() {
let el = fixture.debugElement.query(By.css('.md-checkbox'));
expect(el.nativeElement.className).not.toContain('md-checkbox-indeterminate');
});
});
});
describe('when the checkbox is disabled', function() {
var fixture: ComponentFixture;
var controller: CheckboxController;
beforeEach(function(done: () => void) {
builder.createAsync(CheckboxController).then(function(f) {
fixture = f;
controller = fixture.componentInstance;
fixture.detectChanges();
controller.isDisabled = true;
fixture.detectChanges();
}).then(done).catch(done);
});
it('adds a "md-checkbox-disabled" class to the host element', function() {
let el = fixture.debugElement.query(By.css('.md-checkbox'));
expect(el.nativeElement.className).toContain('md-checkbox-disabled');
});
it('sets the tabindex to -1 on the host element', function() {
let el = fixture.debugElement.query(By.css('.md-checkbox'));
expect(el.nativeElement.getAttribute('tabindex')).toEqual('-1');
});
it('sets "aria-disabled" to "true" on the host element', function() {
let el = fixture.debugElement.query(By.css('.md-checkbox'));
expect(el.nativeElement.getAttribute('aria-disabled')).toEqual('true');
});
it('restores the previously set tabindex when re-enabled', function(done: () => void) {
builder.createAsync(CheckboxCustomTabindexController).then(function(f) {
fixture = f;
let tabindexController: CheckboxCustomTabindexController = fixture.componentInstance;
tabindexController.isDisabled = true;
fixture.detectChanges();
let el = fixture.debugElement.query(By.css('.md-checkbox'));
expect(el.nativeElement.getAttribute('tabindex')).toEqual('-1');
tabindexController.isDisabled = false;
fixture.detectChanges();
el = fixture.debugElement.query(By.css('.md-checkbox'));
expect(el.nativeElement.getAttribute('tabindex')).toEqual(
String(tabindexController.checkboxTabindex));
}).then(done).catch(done);
});
describe('when the tabindex input is changed while disabled', function() {
var tabindexController: CheckboxCustomTabindexController;
var newTabindex: number;
beforeEach(function(done: () => void) {
newTabindex = 10;
builder.createAsync(CheckboxCustomTabindexController).then(function(f) {
fixture = f;
tabindexController = fixture.componentInstance;
tabindexController.isDisabled = true;
tabindexController.checkboxTabindex = newTabindex;
fixture.detectChanges();
}).then(done).catch(done);
});
it('keeps the tabindex at -1', function() {
let el = fixture.debugElement.query(By.css('.md-checkbox'));
expect(el.nativeElement.getAttribute('tabindex')).toEqual('-1');
});
it('uses the newly changed tabindex when re-enabled', function() {
tabindexController.isDisabled = false;
fixture.detectChanges();
let el = fixture.debugElement.query(By.css('.md-checkbox'));
expect(el.nativeElement.getAttribute('tabindex')).toEqual(String(newTabindex));
});
});
});
describe('when the checkbox is clicked', function() {
var fixture: ComponentFixture;
var controller: CheckboxController;
var el: DebugElement;
function clickCheckbox(): Event {
return click(el, fixture);
}
beforeEach(function(done: () => void) {
builder.createAsync(CheckboxController).then(function(f) {
fixture = f;
controller = fixture.componentInstance;
fixture.detectChanges();
el = fixture.debugElement.query(By.css('.md-checkbox'));
}).then(done).catch(done);
});
it('toggles the checked value', function() {
clickCheckbox();
expect(el.nativeElement.className).toContain('md-checkbox-checked');
clickCheckbox();
expect(el.nativeElement.className).not.toContain('md-checkbox-checked');
});
describe('when the checkbox is disabled', function() {
beforeEach(function() {
controller.isDisabled = true;
fixture.detectChanges();
});
it('stops the click event from propagating', function() {
let evt = clickCheckbox();
expect(evt.stopPropagation).toHaveBeenCalled();
});
it('does not alter the checked value', function() {
clickCheckbox();
expect(el.nativeElement.className).not.toContain('md-checkbox-checked');
});
});
});
describe('when a spacebar press occurs on the checkbox', function() {
var fixture: ComponentFixture;
var controller: CheckboxController;
var el: DebugElement;
function spacePress(): Event {
return keyup(el, ' ', fixture);
}
beforeEach(function(done: () => void) {
builder.createAsync(CheckboxController).then(function(f) {
fixture = f;
controller = fixture.componentInstance;
fixture.detectChanges();
el = fixture.debugElement.query(By.css('.md-checkbox'));
}).then(done).catch(done);
});
it('toggles the checked value', function() {
spacePress();
expect(el.nativeElement.className).toContain('md-checkbox-checked');
spacePress();
expect(el.nativeElement.className).not.toContain('md-checkbox-checked');
});
describe('when the checkbox is disabled', function() {
beforeEach(function() {
controller.isDisabled = true;
fixture.detectChanges();
});
it('stops the click event from propagating', function() {
let evt = spacePress();
expect(evt.stopPropagation).toHaveBeenCalled();
});
it('does not alter the checked value', function() {
spacePress();
expect(el.nativeElement.className).not.toContain('md-checkbox-checked');
});
});
});
describe('usage as a form control', function() {
var fixture: ComponentFixture;
var controller: CheckboxFormcontrolController;
beforeEach(function(done: () => void) {
builder.createAsync(CheckboxFormcontrolController).then(function(f) {
fixture = f;
controller = fixture.componentInstance;
fixture.detectChanges();
}).then(done).catch(done);
});
// NOTE(traviskaufman): This test is not that elegant, but I have not found a better way
// to test through ngModel as of now.
// See: https://github.com/angular/angular/issues/7409
it('supports ngModel/ngControl', function(done: () => void) {
var el: DebugElement;
var invalidMsg: DebugElement;
fakeAsync(function() {
el = fixture.debugElement.query(By.css('.md-checkbox'));
invalidMsg = fixture.debugElement.query(By.css('#invalid-msg'));
fixture.detectChanges();
tick();
expect(el.nativeElement.className).toContain('ng-untouched');
expect(el.nativeElement.className).toContain('ng-pristine');
expect(invalidMsg.nativeElement.hidden).toBe(true);
controller.model.isChecked = true;
fixture.detectChanges();
tick();
fixture.detectChanges();
expect(el.nativeElement.className).toContain('md-checkbox-checked');
expect(el.nativeElement.className).toContain('ng-dirty');
expect(el.nativeElement.className).toContain('ng-valid');
var blur: Event;
if (BROWSER_SUPPORTS_EVENT_CONSTRUCTORS) {
blur = new Event('blur');
} else {
blur = document.createEvent('UIEvent');
(<UIEvent>blur).initUIEvent('blur', true, true, window, 0);
}
el.nativeElement.dispatchEvent(blur);
fixture.detectChanges();
tick();
expect(el.nativeElement.className).toContain('ng-touched');
})();
let onceChanged = controller.model.waitForChange();
click(el, fixture);
onceChanged.then(function() {
expect(controller.model.isChecked).toBe(false);
}).then(done).catch(done);
});
});
describe('applying transition classes', function() {
var fixture: ComponentFixture;
var controller: CheckboxController;
beforeEach(function(done: () => void) {
builder.createAsync(CheckboxController).then(function(f) {
fixture = f;
controller = fixture.componentInstance;
fixture.detectChanges();
}).then(done).catch(done);
});
it('applies transition classes when going from unchecked <-> checked', function() {
let el = fixture.debugElement.query(By.css('.md-checkbox'));
controller.isChecked = true;
fixture.detectChanges();
expect(el.nativeElement.className).toContain('md-checkbox-anim-unchecked-checked');
controller.isChecked = false;
fixture.detectChanges();
expect(el.nativeElement.className).not.toContain('md-checkbox-anim-unchecked-checked');
expect(el.nativeElement.className).toContain('md-checkbox-anim-checked-unchecked');
});
it('applies transition classes when going from unchecked <-> indeterminate', function() {
let el = fixture.debugElement.query(By.css('.md-checkbox'));
controller.isIndeterminate = true;
fixture.detectChanges();
expect(el.nativeElement.className).toContain('md-checkbox-anim-unchecked-indeterminate');
controller.isIndeterminate = false;
fixture.detectChanges();
expect(el.nativeElement.className).not.toContain(
'md-checkbox-anim-unchecked-indeterminate');
expect(el.nativeElement.className).toContain('md-checkbox-anim-indeterminate-unchecked');
});
it('applies a transition class when going from checked -> indeterminate', function() {
let el = fixture.debugElement.query(By.css('.md-checkbox'));
controller.isChecked = true;
fixture.detectChanges();
controller.isIndeterminate = true;
fixture.detectChanges();
expect(el.nativeElement.className).not.toContain('md-checkbox-anim-unchecked-checked');
expect(el.nativeElement.className).toContain('md-checkbox-anim-checked-indeterminate');
});
it('applies a transition class when going from indeterminate -> checked', function() {
let el = fixture.debugElement.query(By.css('.md-checkbox'));
controller.isIndeterminate = true;
fixture.detectChanges();
controller.isChecked = true;
fixture.detectChanges();
expect(el.nativeElement.className).not.toContain(
'md-checkbox-anim-unchecked-indeterminate');
expect(el.nativeElement.className).toContain('md-checkbox-anim-indeterminate-checked');
});
it('does not apply any transition classes when there is nothing to transition', function() {
let el = fixture.debugElement.query(By.css('.md-checkbox'));
controller.isChecked = controller.isChecked;
fixture.detectChanges();
expect(el.nativeElement.className).not.toMatch(/^md\-checkbox\-anim/g);
controller.isIndeterminate = controller.isIndeterminate;
fixture.detectChanges();
expect(el.nativeElement.className).not.toMatch(/^md\-checkbox\-anim/g);
});
it('does not apply any transition classes when the component is initialized', function() {
let el = fixture.debugElement.query(By.css('.md-checkbox'));
expect(el.nativeElement.className).not.toMatch(/^md\-checkbox\-anim/g);
});
});
});
}
function click(el: DebugElement, fixture: ComponentFixture) {
var clickEvent: Event;
if (BROWSER_SUPPORTS_EVENT_CONSTRUCTORS) {
clickEvent = new Event('click');
} else {
clickEvent = document.createEvent('UIEvent');
(<UIEvent>clickEvent).initUIEvent('click', true, true, window, 1);
}
spyOn(clickEvent, 'stopPropagation').and.callThrough();
el.nativeElement.dispatchEvent(clickEvent);
fixture.detectChanges();
return clickEvent;
}
// TODO(traviskaufman): Reinvestigate implementation of this method once tests in Dart begin to run.
function keyup(el: DebugElement, key: string, fixture: ComponentFixture) {
var kbdEvent: Event;
if (BROWSER_SUPPORTS_EVENT_CONSTRUCTORS) {
kbdEvent = new KeyboardEvent('keyup');
} else {
kbdEvent = document.createEvent('Events');
kbdEvent.initEvent('keyup', true, true);
}
// Hack DOM Level 3 Events "key" prop into keyboard event.
Object.defineProperty(kbdEvent, 'key', {
value: ' ',
enumerable: false,
writable: false,
configurable: true
});
spyOn(kbdEvent, 'stopPropagation').and.callThrough();
el.nativeElement.dispatchEvent(kbdEvent);
fixture.detectChanges();
return kbdEvent;
}
@Component({
selector: 'checkbox-controller',
template: `
<md-checkbox [checked]="isChecked"
[indeterminate]="isIndeterminate"
[disabled]="isDisabled"
(change)="eventProxy.emit($event)">
<em>my</em> checkbox
</md-checkbox>
`,
directives: [MdCheckbox]
})
class CheckboxController {
isChecked: boolean = false;
isIndeterminate: boolean = false;
isDisabled: boolean = false;
eventProxy: EventEmitter<boolean> = new EventEmitter<boolean>();
}
@Component({
selector: 'checkbox-multi-controller',
template: `
<md-checkbox></md-checkbox>
<md-checkbox></md-checkbox>
`,
directives: [MdCheckbox]
})
class CheckboxMultiController {}
@Component({
selector: 'checkbox-custom-id-controller',
template: `
<md-checkbox [id]="checkboxId"></md-checkbox>
`,
directives: [MdCheckbox]
})
class CheckboxCustomIdController {
checkboxId: string = 'my-checkbox';
}
@Component({
selector: 'checkbox-custom-tabindex-controller',
template: `
<md-checkbox [tabindex]="checkboxTabindex" [disabled]="isDisabled"></md-checkbox>
`,
directives: [MdCheckbox]
})
class CheckboxCustomTabindexController {
checkboxTabindex: number = 5;
isDisabled: boolean = false;
}
@Component({
selector: 'checkbox-custom-arialabel-controller',
template: `
<md-checkbox [aria-label]="checkboxLabel"></md-checkbox>
`,
directives: [MdCheckbox]
})
class CheckboxCustomArialabelController {
checkboxLabel: string = 'My awesome checkbox';
}
class FormcontrolModel {
private _isChecked = false;
private _changeEmitter = new EventEmitter<boolean>();
get isChecked(): boolean {
return this._isChecked;
}
set isChecked(isChecked: boolean) {
let shouldEmitChange = this._isChecked !== isChecked;
this._isChecked = isChecked;
if (shouldEmitChange) {
this._changeEmitter.emit(this._isChecked);
}
}
waitForChange(): Promise<boolean> {
return new Promise((resolve, reject) => {
let subscription: {unsubscribe: () => void};
let subscriber = function(isChecked: boolean) {
subscription.unsubscribe();
resolve(isChecked);
};
subscription = this._changeEmitter.subscribe(subscriber, reject);
});
}
}
@Component({
selector: 'checkbox-formcontrol-controller',
template: `
<form>
<md-checkbox [(ngModel)]="model.isChecked"
ngControl="cb" #cb="ngForm">
</md-checkbox>
<p id="invalid-msg" [hidden]="cb.valid || cb.pristine">INVALID!</p>
</form>
`,
directives: [MdCheckbox]
})
class CheckboxFormcontrolController {
model = new FormcontrolModel();
}
@Component({
selector: 'checkbox-end-aligned-controller',
template: `<md-checkbox [align]="'end'"></md-checkbox>`,
directives: [MdCheckbox]
})
class CheckboxEndAlignedController {}
+256
View File
@@ -0,0 +1,256 @@
import {
ChangeDetectionStrategy,
Component,
ElementRef,
EventEmitter,
Input,
Output,
Provider,
Renderer,
ViewEncapsulation,
forwardRef
} from 'angular2/core';
import {
NG_VALUE_ACCESSOR,
ControlValueAccessor
} from 'angular2/src/common/forms/directives/control_value_accessor';
import {CONST_EXPR} from 'angular2/src/facade/lang';
import { OneOf } from '../../core/annotations/one-of';
/**
* Monotonically increasing integer used to auto-generate unique ids for checkbox components.
*/
let nextId = 0;
/**
* Provider Expression that allows md-checkbox to register as a ControlValueAccessor. This allows it
* to support [(ngModel)] and ngControl.
*/
const MD_CHECKBOX_CONTROL_VALUE_ACCESSOR = CONST_EXPR(new Provider(
NG_VALUE_ACCESSOR, {
useExisting: forwardRef(() => MdCheckbox),
multi: true
}));
/**
* Represents the different states that require custom transitions between them.
*/
enum TransitionCheckState {
/** The initial state of the component before any user interaction. */
Init,
/** The state representing the component when it's becoming checked. */
Checked,
/** The state representing the component when it's becoming unchecked. */
Unchecked,
/** The state representing the component when it's becoming indeterminate. */
Indeterminate
}
/**
* A material design checkbox component. Supports all of the functionality of an HTML5 checkbox,
* and exposes a similar API. An MdCheckbox can be either checked, unchecked, indeterminate, or
* disabled. Note that all additional accessibility attributes are taken care of by the component,
* so there is no need to provide them yourself. However, if you want to omit a label and still
* have the checkbox be accessible, you may supply an [aria-label] input.
* See: https://www.google.com/design/spec/components/selection-controls.html
*/
@Component({
selector: 'md-checkbox',
templateUrl: './components/checkbox/checkbox.html',
styleUrls: ['./components/checkbox/checkbox.css'],
host: {
'role': 'checkbox',
'[id]': 'id',
'[class.md-checkbox]': 'true',
'[class.md-checkbox-indeterminate]': 'indeterminate',
'[class.md-checkbox-checked]': 'checked',
'[class.md-checkbox-disabled]': 'disabled',
'[class.md-checkbox-align-end]': 'align == "end"',
'[tabindex]': 'disabled ? -1 : tabindex',
'[attr.aria-label]': 'ariaLabel',
'[attr.aria-labelledby]': 'labelId',
'[attr.aria-checked]': 'getAriaChecked()',
'[attr.aria-disabled]': 'disabled',
'(click)': 'onInteractionEvent($event)',
'(keyup.space)': 'onInteractionEvent($event)',
'(blur)': 'onTouched()'
},
providers: [MD_CHECKBOX_CONTROL_VALUE_ACCESSOR],
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class MdCheckbox implements ControlValueAccessor {
/**
* Attached to the aria-label attribute of the host element. In most cases, arial-labelledby will
* take precedence so this may be omitted.
*/
@Input('aria-label') ariaLabel: string = '';
/** A unique id for the checkbox. If one is not supplied, it is auto-generated. */
@Input() id: string = `md-checkbox-${++nextId}`;
/** Whether or not the checkbox should come before or after the label. */
@Input() @OneOf(['start', 'end']) align: string = 'start';
/**
* Whether the checkbox is disabled. When the checkbox is disabled it cannot be interacted with.
* The correct ARIA attributes are applied to denote this to assistive technology.
*/
@Input() disabled: boolean = false;
/**
* The tabindex attribute for the checkbox. Note that when the checkbox is disabled, the attribute
* on the host element will be set to -1, regardless of the actual tabindex value.
*/
@Input() tabindex: number = 0;
/** Event emitted when the checkbox's `checked` value changes. */
@Output() change: EventEmitter<boolean> = new EventEmitter();
/** Called when the checkbox is blurred. Needed to properly implement ControlValueAccessor. */
onTouched: () => any = () => {};
private _currentAnimationClass: string = '';
private _currentCheckState: TransitionCheckState = TransitionCheckState.Init;
private _checked: boolean = false;
private _indeterminate: boolean = false;
private _changeSubscription: {unsubscribe: () => any} = null;
constructor(private _renderer: Renderer, private _elementRef: ElementRef) {}
/**
* Whether the checkbox is checked. Note that setting `checked` will immediately set
* `indeterminate` to false.
*/
@Input() get checked() {
return this._checked;
}
set checked(checked: boolean) {
this._indeterminate = false;
this._checked = checked;
this._transitionCheckState(
this._checked ? TransitionCheckState.Checked : TransitionCheckState.Unchecked);
this.change.emit(this._checked);
}
/**
* Whether the checkbox is indeterminate. This is also known as "mixed" mode and can be used to
* represent a checkbox with three states, e.g. a checkbox that represents a nested list of
* checkable items. Note that whenever `checked` is set, indeterminate is immediately set to
* false. This differs from the web platform in that indeterminate state on native
* checkboxes is only remove when the user manually checks the checkbox (rather than setting the
* `checked` property programmatically). However, we feel that this behavior is more accommodating
* to the way consumers would envision using this component.
*/
@Input() get indeterminate() {
return this._indeterminate;
}
set indeterminate(indeterminate: boolean) {
this._indeterminate = indeterminate;
if (this._indeterminate) {
this._transitionCheckState(TransitionCheckState.Indeterminate);
} else {
this._transitionCheckState(
this.checked ? TransitionCheckState.Checked : TransitionCheckState.Unchecked);
}
}
/** The id that is attached to the checkbox's label. */
get labelId() {
return `${this.id}-label`;
}
/** Returns the proper aria-checked attribute value based on the checkbox's state. */
getAriaChecked() {
if (this.indeterminate) {
return 'mixed';
}
return this.checked ? 'true' : 'false';
}
/** Toggles the checked state of the checkbox. If the checkbox is disabled, this does nothing. */
toggle() {
this.checked = !this.checked;
}
/**
* Event handler used for both (click) and (keyup.space) events. Delegates to toggle().
*/
onInteractionEvent(event: Event) {
if (this.disabled) {
event.stopPropagation();
return;
}
this.toggle();
}
/** Implemented as part of ControlValueAccessor. */
writeValue(value: any) {
this.checked = !!value;
}
/** Implemented as part of ControlValueAccessor. */
registerOnChange(fn: any) {
if (this._changeSubscription) {
this._changeSubscription.unsubscribe();
}
this._changeSubscription = <{unsubscribe: () => any}>this.change.subscribe(fn);
}
/** Implemented as part of ControlValueAccessor. */
registerOnTouched(fn: any) {
this.onTouched = fn;
}
private _transitionCheckState(newState: TransitionCheckState) {
let oldState = this._currentCheckState;
let renderer = this._renderer;
let elementRef = this._elementRef;
if (oldState === newState) {
return;
}
if (this._currentAnimationClass.length > 0) {
renderer.setElementClass(elementRef.nativeElement, this._currentAnimationClass, false);
}
this._currentAnimationClass = this._getAnimationClassForCheckStateTransition(
oldState, newState);
this._currentCheckState = newState;
if (this._currentAnimationClass.length > 0) {
renderer.setElementClass(elementRef.nativeElement, this._currentAnimationClass, true);
}
}
private _getAnimationClassForCheckStateTransition(
oldState: TransitionCheckState, newState: TransitionCheckState): string {
var animSuffix: string;
switch (oldState) {
case TransitionCheckState.Init:
return '';
case TransitionCheckState.Unchecked:
animSuffix = newState === TransitionCheckState.Checked ?
'unchecked-checked' : 'unchecked-indeterminate';
break;
case TransitionCheckState.Checked:
animSuffix = newState === TransitionCheckState.Unchecked ?
'checked-unchecked' : 'checked-indeterminate';
break;
case TransitionCheckState.Indeterminate:
animSuffix = newState === TransitionCheckState.Checked ?
'indeterminate-checked' : 'indeterminate-unchecked';
}
return `md-checkbox-anim-${animSuffix}`;
}
}
+5
View File
@@ -18,6 +18,11 @@ $pi: 3.14159265;
// Easing Curves
// TODO(jelbourn): all of these need to be revisited
// The default animation curves used by material design.
$md-linear-out-slow-in-timing-function: cubic-bezier(0.0, 0.0, 0.2, 0.1) !default;
$md-fast-out-slow-in-timing-function: cubic-bezier(0.4, 0.0, 0.2, 1) !default;
$md-fast-out-linear-in-timing-function: cubic-bezier(0.4, 0.0, 1, 1) !default;
$ease-in-out-curve-function: cubic-bezier(0.35, 0, 0.25, 1) !default;
$swift-ease-out-duration: 0.4s !default;
+41
View File
@@ -0,0 +1,41 @@
<h1>md-checkbox: Basic Example</h1>
<md-checkbox [(ngModel)]="isChecked"
(change)="isIndeterminate = false"
[indeterminate]="isIndeterminate"
[disabled]="isDisabled"
[align]="alignment">
Do you want to <em>foobar</em> the <em>bazquux</em>?
</md-checkbox> - <strong>{{printResult()}}</strong>
<div>
<input id="indeterminate-toggle"
type="checkbox"
[(ngModel)]="isIndeterminate"
[disabled]="isDisabled">
<label for="indeterminate-toggle">Toggle Indeterminate</label>
<input id="disabled-toggle" type="checkbox" [(ngModel)]="isDisabled">
<label for="disabled-toggle">Toggle Disabled</label>
</div>
<div>
<p>Alignment:</p>
<div>
<input #start type="radio"
value="start"
id="align-start"
name="alignment"
(click)="alignment = start.value"
checked>
<label for="align-start">Start</label>
</div>
<div>
<input #end type="radio"
value="end"
id="align-end"
name="alignment"
(click)="alignment = end.value">
<label for="align-end">End</label>
</div>
</div>
<h1>Application Example: Nested Checklist</h1>
<h2><em>Caution: WIP!</em></h2>
<md-checkbox-demo-nested-checklist></md-checkbox-demo-nested-checklist>
+78
View File
@@ -0,0 +1,78 @@
import {Component} from 'angular2/core';
import {MdCheckbox} from '../../components/checkbox/checkbox';
interface Task {
name: string;
completed: boolean;
subtasks?: Task[];
}
@Component({
selector: 'md-checkbox-demo-nested-checklist',
styles: [`
li {
margin-bottom: 4px;
}
`],
templateUrl: 'demo-app/checkbox/nested-checklist.html',
directives: [MdCheckbox]
})
class MdCheckboxDemoNestedChecklist {
tasks: Task[] = [
{
name: 'Reminders',
completed: false,
subtasks: [
{ name: 'Cook Dinner', completed: false },
{ name: 'Read the Material Design Spec', completed: false },
{ name: 'Upgrade Application to Angular2', completed: false }
]
},
{
name: 'Groceries',
completed: false,
subtasks: [
{ name: 'Organic Eggs', completed: false },
{ name: 'Protein Powder', completed: false },
{ name: 'Almond Meal Flour', completed: false }
]
}
];
allComplete(tasks: Task[]): boolean {
return tasks.every(t => t.completed);
}
someComplete(tasks: Task[]): boolean {
const numComplete = tasks.filter(t => t.completed).length;
return numComplete > 0 && numComplete < tasks.length;
}
setAllCompleted(tasks: Task[], completed: boolean) {
tasks.forEach(t => t.completed = completed);
}
updateOnSubtaskChange(task: Task) {
task.completed = this.allComplete(task.subtasks);
}
}
@Component({
selector: 'md-checkbox-demo',
templateUrl: 'demo-app/checkbox/checkbox-demo.html',
styleUrls: ['demo-app/checkbox/checkbox-demo.css'],
directives: [MdCheckbox, MdCheckboxDemoNestedChecklist]
})
export class CheckboxDemo {
isIndeterminate: boolean = false;
isChecked: boolean = false;
isDisabled: boolean = false;
alignment: string = 'start';
printResult() {
if (this.isIndeterminate) {
return 'Maybe!';
}
return this.isChecked ? 'Yes!' : 'No!';
}
}
@@ -0,0 +1,18 @@
<h2>Tasks</h2>
<ul>
<li *ngFor="#task of tasks">
<md-checkbox [(ngModel)]="task.completed"
[checked]="allComplete(task.subtasks)"
[indeterminate]="someComplete(task.subtasks)"
(change)="setAllCompleted(task.subtasks, $event)">
<h3>{{task.name}}</h3>
</md-checkbox>
<ul>
<li *ngFor="#subtask of task.subtasks">
<md-checkbox [(ngModel)]="subtask.completed">
{{subtask.name}}
</md-checkbox>
</li>
</ul>
</li>
</ul>
+14 -15
View File
@@ -1,16 +1,15 @@
<div class="demo-content">
<h1 class="title">Angular Material2 Demos</h1>
<ul>
<li><a [routerLink]="['ButtonDemo']">Button demo</a></li>
<li><a [routerLink]="['CardDemo']">Card demo</a></li>
<li><a [routerLink]="['SidenavDemo']">Sidenav demo</a></li>
<li><a [routerLink]="['ProgressCircleDemo']">Progress Circle demo</a></li>
<li><a [routerLink]="['PortalDemo']">Portal demo</a></li>
</ul>
<button md-raised-button (click)="root.dir = (root.dir == 'rtl' ? 'ltr' : 'rtl')">
{{root.dir.toUpperCase()}}
</button>
<div #root="$implicit" dir="ltr">
<router-outlet></router-outlet>
</div>
<h1 class="title">Angular Material2 Demos</h1>
<ul>
<li><a [routerLink]="['ButtonDemo']">Button demo</a></li>
<li><a [routerLink]="['CardDemo']">Card demo</a></li>
<li><a [routerLink]="['SidenavDemo']">Sidenav demo</a></li>
<li><a [routerLink]="['ProgressCircleDemo']">Progress Circle demo</a></li>
<li><a [routerLink]="['PortalDemo']">Portal demo</a></li>
<li><a [routerLink]="['CheckboxDemo']">Checkbox demo</a></li>
</ul>
<button md-raised-button (click)="root.dir = (root.dir == 'rtl' ? 'ltr' : 'rtl')">
{{root.dir.toUpperCase()}}
</button>
<div #root="$implicit" dir="ltr">
<router-outlet></router-outlet>
</div>
+3 -1
View File
@@ -1,9 +1,10 @@
import {Component} from 'angular2/core';
import {Route, RouteConfig, ROUTER_DIRECTIVES} from 'angular2/router';
import {CardDemo} from './card/card-demo';
import {ButtonDemo} from './button/button-demo';
import {SidenavDemo} from './sidenav/sidenav-demo';
import {ProgressCircleDemo} from './progress-circle/progress-circle-demo';
import {Route, RouteConfig, ROUTER_DIRECTIVES} from 'angular2/router';
import {CheckboxDemo} from './checkbox/checkbox-demo';
import {Dir} from '../directives/dir/dir';
import {MdButton} from '../components/button/button';
import {PortalDemo} from './portal/portal-demo';
@@ -30,5 +31,6 @@ export class Home {}
new Route({path: '/sidenav', name: 'SidenavDemo', component: SidenavDemo}),
new Route({path: '/progress-circle', name: 'ProgressCircleDemo', component: ProgressCircleDemo}),
new Route({path: '/portal', name: 'PortalDemo', component: PortalDemo}),
new Route({path: '/checkbox', name: 'CheckboxDemo', component: CheckboxDemo})
])
export class DemoApp { }
+2 -1
View File
@@ -86,7 +86,8 @@ export function config(config) {
},
browserDisconnectTimeout: 20000,
browserNoActivityTimeout: 100000,
browserNoActivityTimeout: 240000,
captureTimeout: 120000,
browsers: ['Chrome'],
singleRun: false