diff --git a/src/lib/checkbox/_checkbox-theme.scss b/src/lib/checkbox/_checkbox-theme.scss
index 6b0e22a5c740..6693d8dcc550 100644
--- a/src/lib/checkbox/_checkbox-theme.scss
+++ b/src/lib/checkbox/_checkbox-theme.scss
@@ -2,71 +2,9 @@
@mixin md-checkbox-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);
-
-
- // The color of the checkbox border.
- $checkbox-border-color: if($is-dark-theme, rgba(white, 0.7), rgba(black, 0.54)) !default;
-
- // The color of the checkbox's checkmark / mixedmark.
- $checkbox-mark-color: md-color($background, background);
-
- // 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: #686868;
- $black-26pct-opacity-on-light: #b0b0b0;
- $disabled-color: if($is-dark-theme, $white-30pct-opacity-on-dark, $black-26pct-opacity-on-light);
-
- .md-checkbox-frame {
- border-color: $checkbox-border-color;
- }
-
- .md-checkbox-checkmark {
- fill: $checkbox-mark-color;
- }
-
- .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: $checkbox-mark-color !important;
- }
-
- .md-checkbox-mixedmark {
- background-color: $checkbox-mark-color;
- }
-
- .md-checkbox-indeterminate, .md-checkbox-checked {
- &.md-primary .md-checkbox-background {
- background-color: md-color($primary, 500);
- }
-
- &.md-accent .md-checkbox-background {
- background-color: md-color($accent, 500);
- }
-
- &.md-warn .md-checkbox-background {
- background-color: md-color($warn, 500);
- }
- }
-
- .md-checkbox-disabled {
- &.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:not(.md-checkbox-disabled) {
&.md-primary .md-checkbox-ripple .md-ripple-foreground {
diff --git a/src/lib/checkbox/checkbox.html b/src/lib/checkbox/checkbox.html
index 3196048f4992..cc3f8c59185a 100644
--- a/src/lib/checkbox/checkbox.html
+++ b/src/lib/checkbox/checkbox.html
@@ -20,21 +20,12 @@
[mdRippleCentered]="true"
[mdRippleSpeedFactor]="0.3"
mdRippleBackgroundColor="rgba(0, 0, 0, 0)">
-
-
+
+
diff --git a/src/lib/checkbox/checkbox.scss b/src/lib/checkbox/checkbox.scss
index 1e0a0d040002..0fa9bc59818b 100644
--- a/src/lib/checkbox/checkbox.scss
+++ b/src/lib/checkbox/checkbox.scss
@@ -1,202 +1,18 @@
-@import '../core/theming/theming';
-@import '../core/style/elevation';
@import '../core/style/variables';
@import '../core/ripple/ripple';
-
-// The width/height of the checkbox element.
-$md-checkbox-size: $md-toggle-size !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 base duration used for the majority of transitions for the checkbox.
-$md-checkbox-transition-duration: 90ms;
-// The amount of spacing between the checkbox and its label.
-$md-checkbox-item-spacing: $md-toggle-padding;
-
-// 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);
-
// The ripple size of the checkbox
$md-checkbox-ripple-size: 15px;
-// 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});
-}
-
-// 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;
-}
+// The amount of spacing between the checkbox and its label.
+$md-checkbox-item-spacing: $md-toggle-padding;
md-checkbox {
cursor: pointer;
+}
- // Animation
- transition: background $swift-ease-out-duration $swift-ease-out-timing-function,
- md-elevation-transition-property-value();
+.md-checkbox-disabled {
+ cursor: default;
}
.md-checkbox-layout {
@@ -210,15 +26,12 @@ md-checkbox {
.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;
flex-shrink: 0;
[dir='rtl'] & {
@@ -234,50 +47,6 @@ md-checkbox {
line-height: 24px;
}
-.md-checkbox-frame {
- @extend %md-checkbox-outer-box;
-
- background-color: transparent;
- border: $md-checkbox-border-width solid;
- 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;
- 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;
-
- width: 100%;
-}
-
-.md-checkbox-checkmark-path {
- 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;
-
- height: floor($md-checkbox-mark-stroke-size);
- opacity: 0;
- transform: scaleX(0) rotate(0deg);
-}
-
.md-checkbox-label-before {
.md-checkbox-inner-container {
order: 1;
@@ -295,123 +64,6 @@ md-checkbox {
}
}
-.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-indeterminate {
- .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-unchecked {
- .md-checkbox-background {
- background-color: transparent;
- }
-}
-
-.md-checkbox-disabled {
- cursor: default;
-}
-
-.md-checkbox-anim {
- $indeterminate-change-duration: 500ms;
-
- &-unchecked-checked {
- .md-checkbox-background {
- animation: $md-checkbox-transition-duration * 2 linear 0ms 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 0ms md-checkbox-unchecked-checked-checkmark-path;
- }
- }
-
- &-unchecked-indeterminate {
- .md-checkbox-background {
- animation: $md-checkbox-transition-duration * 2 linear 0ms md-checkbox-fade-in-background;
- }
-
- .md-checkbox-mixedmark {
- animation:
- $md-checkbox-transition-duration linear 0ms md-checkbox-unchecked-indeterminate-mixedmark;
- }
- }
-
- &-checked-unchecked {
- .md-checkbox-background {
- animation: $md-checkbox-transition-duration * 2 linear 0ms md-checkbox-fade-out-background;
- }
-
- .md-checkbox-checkmark-path {
- animation:
- $md-checkbox-transition-duration linear 0ms md-checkbox-checked-unchecked-checkmark-path;
- }
- }
-
- &-checked-indeterminate {
- .md-checkbox-checkmark {
- animation:
- $md-checkbox-transition-duration linear 0ms md-checkbox-checked-indeterminate-checkmark;
- }
-
- .md-checkbox-mixedmark {
- animation:
- $md-checkbox-transition-duration linear 0ms md-checkbox-checked-indeterminate-mixedmark;
- }
- }
-
- &-indeterminate-checked {
- .md-checkbox-checkmark {
- animation:
- $indeterminate-change-duration linear 0ms md-checkbox-indeterminate-checked-checkmark;
- }
-
- .md-checkbox-mixedmark {
- animation:
- $indeterminate-change-duration linear 0ms md-checkbox-indeterminate-checked-mixedmark;
- }
- }
-
- &-indeterminate-unchecked {
- .md-checkbox-background {
- animation: $md-checkbox-transition-duration * 2 linear 0ms md-checkbox-fade-out-background;
- }
-
- .md-checkbox-mixedmark {
- animation:
- $indeterminate-change-duration * 0.6 linear 0ms
- md-checkbox-indeterminate-unchecked-mixedmark;
- }
- }
-}
-
.md-checkbox-input {
// Move the input to the bottom and in the middle.
// Visual improvement to properly show browser popups when being required.
diff --git a/src/lib/checkbox/checkbox.spec.ts b/src/lib/checkbox/checkbox.spec.ts
index ff830032ffa6..43e106f759d6 100644
--- a/src/lib/checkbox/checkbox.spec.ts
+++ b/src/lib/checkbox/checkbox.spec.ts
@@ -18,7 +18,6 @@ import {ViewportRuler} from '../core/overlay/position/viewport-ruler';
import {FakeViewportRuler} from '../core/overlay/position/fake-viewport-ruler';
-
describe('MdCheckbox', () => {
let fixture: ComponentFixture;
@@ -66,40 +65,34 @@ describe('MdCheckbox', () => {
it('should add and remove the checked state', () => {
expect(checkboxInstance.checked).toBe(false);
- expect(checkboxNativeElement.classList).not.toContain('md-checkbox-checked');
expect(inputElement.checked).toBe(false);
testComponent.isChecked = true;
fixture.detectChanges();
expect(checkboxInstance.checked).toBe(true);
- expect(checkboxNativeElement.classList).toContain('md-checkbox-checked');
expect(inputElement.checked).toBe(true);
testComponent.isChecked = false;
fixture.detectChanges();
expect(checkboxInstance.checked).toBe(false);
- expect(checkboxNativeElement.classList).not.toContain('md-checkbox-checked');
expect(inputElement.checked).toBe(false);
});
it('should add and remove indeterminate state', () => {
- expect(checkboxNativeElement.classList).not.toContain('md-checkbox-checked');
expect(inputElement.checked).toBe(false);
expect(inputElement.indeterminate).toBe(false);
testComponent.isIndeterminate = true;
fixture.detectChanges();
- expect(checkboxNativeElement.classList).toContain('md-checkbox-indeterminate');
expect(inputElement.checked).toBe(false);
expect(inputElement.indeterminate).toBe(true);
testComponent.isIndeterminate = false;
fixture.detectChanges();
- expect(checkboxNativeElement.classList).not.toContain('md-checkbox-indeterminate');
expect(inputElement.checked).toBe(false);
expect(inputElement.indeterminate).toBe(false);
});
@@ -228,13 +221,13 @@ describe('MdCheckbox', () => {
spyOn(testComponent, 'onCheckboxClick');
expect(inputElement.checked).toBe(false);
- expect(checkboxNativeElement.classList).not.toContain('md-checkbox-checked');
+ expect(checkboxInstance.checked).toBe(false);
labelElement.click();
fixture.detectChanges();
- expect(checkboxNativeElement.classList).toContain('md-checkbox-checked');
expect(inputElement.checked).toBe(true);
+ expect(checkboxInstance.checked).toBe(true);
expect(testComponent.onCheckboxClick).toHaveBeenCalledTimes(1);
});
@@ -243,13 +236,13 @@ describe('MdCheckbox', () => {
spyOn(testComponent, 'onCheckboxChange');
expect(inputElement.checked).toBe(false);
- expect(checkboxNativeElement.classList).not.toContain('md-checkbox-checked');
+ expect(checkboxInstance.checked).toBe(false);
labelElement.click();
fixture.detectChanges();
expect(inputElement.checked).toBe(true);
- expect(checkboxNativeElement.classList).toContain('md-checkbox-checked');
+ expect(checkboxInstance.checked).toBe(true);
// Wait for the fixture to become stable, because the EventEmitter for the change event,
// will only fire after the zone async change detection has finished.
@@ -264,13 +257,13 @@ describe('MdCheckbox', () => {
spyOn(testComponent, 'onCheckboxChange');
expect(inputElement.checked).toBe(false);
- expect(checkboxNativeElement.classList).not.toContain('md-checkbox-checked');
+ expect(checkboxInstance.checked).toBe(false);
testComponent.isChecked = true;
fixture.detectChanges();
expect(inputElement.checked).toBe(true);
- expect(checkboxNativeElement.classList).toContain('md-checkbox-checked');
+ expect(checkboxInstance.checked).toBe(true);
// Wait for the fixture to become stable, because the EventEmitter for the change event,
// will only fire after the zone async change detection has finished.
@@ -303,89 +296,6 @@ describe('MdCheckbox', () => {
expect(document.activeElement).toBe(inputElement);
});
- describe('color behaviour', () => {
- it('should apply class based on color attribute', () => {
- testComponent.checkboxColor = 'primary';
- fixture.detectChanges();
- expect(checkboxDebugElement.nativeElement.classList.contains('md-primary')).toBe(true);
-
- testComponent.checkboxColor = 'accent';
- fixture.detectChanges();
- expect(checkboxDebugElement.nativeElement.classList.contains('md-accent')).toBe(true);
- });
-
- it('should should not clear previous defined classes', () => {
- checkboxDebugElement.nativeElement.classList.add('custom-class');
-
- testComponent.checkboxColor = 'primary';
- fixture.detectChanges();
-
- expect(checkboxDebugElement.nativeElement.classList.contains('md-primary')).toBe(true);
- expect(checkboxDebugElement.nativeElement.classList.contains('custom-class')).toBe(true);
-
- testComponent.checkboxColor = 'accent';
- fixture.detectChanges();
-
- expect(checkboxDebugElement.nativeElement.classList.contains('md-primary')).toBe(false);
- expect(checkboxDebugElement.nativeElement.classList.contains('md-accent')).toBe(true);
- expect(checkboxDebugElement.nativeElement.classList.contains('custom-class')).toBe(true);
-
- });
- });
-
- describe('state transition css classes', () => {
- it('should transition unchecked -> checked -> unchecked', () => {
- testComponent.isChecked = true;
- fixture.detectChanges();
- expect(checkboxNativeElement.classList).toContain('md-checkbox-anim-unchecked-checked');
-
- testComponent.isChecked = false;
- fixture.detectChanges();
- expect(checkboxNativeElement.classList).not.toContain('md-checkbox-anim-unchecked-checked');
- expect(checkboxNativeElement.classList).toContain('md-checkbox-anim-checked-unchecked');
- });
-
- it('should transition unchecked -> indeterminate -> unchecked', () => {
- testComponent.isIndeterminate = true;
- fixture.detectChanges();
-
- expect(checkboxNativeElement.classList)
- .toContain('md-checkbox-anim-unchecked-indeterminate');
-
- testComponent.isIndeterminate = false;
- fixture.detectChanges();
-
- expect(checkboxNativeElement.classList)
- .not.toContain('md-checkbox-anim-unchecked-indeterminate');
- expect(checkboxNativeElement.classList)
- .toContain('md-checkbox-anim-indeterminate-unchecked');
- });
-
- it('should transition indeterminate -> checked', () => {
- testComponent.isIndeterminate = true;
- fixture.detectChanges();
-
- testComponent.isChecked = true;
- fixture.detectChanges();
-
- expect(checkboxNativeElement.classList).not.toContain(
- 'md-checkbox-anim-unchecked-indeterminate');
- expect(checkboxNativeElement.classList).toContain('md-checkbox-anim-indeterminate-checked');
- });
-
- it('should not apply transition classes when there is no state change', () => {
- testComponent.isChecked = checkboxInstance.checked;
- fixture.detectChanges();
- expect(checkboxNativeElement).not.toMatch(/^md\-checkbox\-anim/g);
-
- testComponent.isIndeterminate = checkboxInstance.indeterminate;
- expect(checkboxNativeElement).not.toMatch(/^md\-checkbox\-anim/g);
- });
-
- it('should not initially have any transition classes', () => {
- expect(checkboxNativeElement).not.toMatch(/^md\-checkbox\-anim/g);
- });
- });
});
describe('with change event and no initial value', () => {
diff --git a/src/lib/checkbox/checkbox.ts b/src/lib/checkbox/checkbox.ts
index f89f3146df3a..538038c5cab8 100644
--- a/src/lib/checkbox/checkbox.ts
+++ b/src/lib/checkbox/checkbox.ts
@@ -16,7 +16,7 @@ import {
import {CommonModule} from '@angular/common';
import {NG_VALUE_ACCESSOR, ControlValueAccessor} from '@angular/forms';
import {coerceBooleanProperty} from '../core/coercion/boolean-property';
-import {MdRippleModule, DefaultStyleCompatibilityModeModule} from '../core';
+import {MdRippleModule, MdSelectionModule, DefaultStyleCompatibilityModeModule} from '../core';
/** Monotonically increasing integer used to auto-generate unique ids for checkbox components. */
@@ -33,20 +33,6 @@ export const MD_CHECKBOX_CONTROL_VALUE_ACCESSOR: any = {
multi: true
};
-/**
- * Represents the different states that require custom transitions between them.
- * @docs-private
- */
-export 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
-}
/** Change event object emitted by MdCheckbox. */
export class MdCheckboxChange {
@@ -68,11 +54,8 @@ export class MdCheckboxChange {
templateUrl: 'checkbox.html',
styleUrls: ['checkbox.css'],
host: {
- '[class.md-checkbox-indeterminate]': 'indeterminate',
- '[class.md-checkbox-checked]': 'checked',
- '[class.md-checkbox-disabled]': 'disabled',
'[class.md-checkbox-label-before]': 'labelPosition == "before"',
- '[class.md-checkbox-focused]': '_hasFocus',
+ '[class.md-checkbox-disabled]': 'disabled',
},
providers: [MD_CHECKBOX_CONTROL_VALUE_ACCESSOR],
encapsulation: ViewEncapsulation.None,
@@ -156,25 +139,16 @@ export class MdCheckbox implements ControlValueAccessor {
*/
onTouched: () => any = () => {};
- private _currentAnimationClass: string = '';
-
- private _currentCheckState: TransitionCheckState = TransitionCheckState.Init;
-
private _checked: boolean = false;
- private _indeterminate: boolean = false;
-
- private _color: string;
-
private _controlValueAccessorChangeFn: (value: any) => void = (value) => {};
_hasFocus: boolean = false;
- constructor(private _renderer: Renderer,
- private _elementRef: ElementRef,
- private _changeDetectorRef: ChangeDetectorRef) {
- this.color = 'accent';
- }
+ constructor(
+ private _renderer: Renderer,
+ private _elementRef: ElementRef,
+ private _changeDetectorRef: ChangeDetectorRef) { }
/**
* Whether the checkbox is checked. Note that setting `checked` will immediately set
@@ -186,10 +160,8 @@ export class MdCheckbox implements ControlValueAccessor {
set checked(checked: boolean) {
if (checked != this.checked) {
- this._indeterminate = false;
+ this.indeterminate = false;
this._checked = checked;
- this._transitionCheckState(
- this._checked ? TransitionCheckState.Checked : TransitionCheckState.Unchecked);
this._changeDetectorRef.markForCheck();
}
}
@@ -203,36 +175,10 @@ export class MdCheckbox implements ControlValueAccessor {
* `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);
- }
- }
+ @Input() indeterminate: boolean = false;
/** The color of the button. Can be `primary`, `accent`, or `warn`. */
- @Input()
- get color(): string { return this._color; }
- set color(value: string) { this._updateColor(value); }
-
- _updateColor(newColor: string) {
- this._setElementColor(this._color, false);
- this._setElementColor(newColor, true);
- this._color = newColor;
- }
-
- _setElementColor(color: string, isAdd: boolean) {
- if (color != null && color != '') {
- this._renderer.setElementClass(this._elementRef.nativeElement, `md-${color}`, isAdd);
- }
- }
+ @Input() color: string = 'accent';
_isRippleDisabled() {
return this.disableRipple || this.disabled;
@@ -272,27 +218,6 @@ export class MdCheckbox implements ControlValueAccessor {
this.disabled = isDisabled;
}
- 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 _emitChangeEvent() {
let event = new MdCheckboxChange();
event.source = this;
@@ -356,36 +281,6 @@ export class MdCheckbox implements ControlValueAccessor {
event.stopPropagation();
}
- private _getAnimationClassForCheckStateTransition(
- oldState: TransitionCheckState, newState: TransitionCheckState): string {
- var animSuffix: string;
-
- switch (oldState) {
- case TransitionCheckState.Init:
- // Handle edge case where user interacts with checkbox that does not have [(ngModel)] or
- // [checked] bound to it.
- if (newState === TransitionCheckState.Checked) {
- animSuffix = 'unchecked-checked';
- } else {
- return '';
- }
- break;
- 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}`;
- }
-
_getHostElement() {
return this._elementRef.nativeElement;
}
@@ -393,7 +288,12 @@ export class MdCheckbox implements ControlValueAccessor {
@NgModule({
- imports: [CommonModule, MdRippleModule, DefaultStyleCompatibilityModeModule],
+ imports: [
+ CommonModule,
+ MdRippleModule,
+ MdSelectionModule,
+ DefaultStyleCompatibilityModeModule
+ ],
exports: [MdCheckbox, DefaultStyleCompatibilityModeModule],
declarations: [MdCheckbox],
})
diff --git a/src/lib/core/_core.scss b/src/lib/core/_core.scss
index 0df852437e9e..be56f00b134f 100644
--- a/src/lib/core/_core.scss
+++ b/src/lib/core/_core.scss
@@ -5,6 +5,7 @@
@import 'ripple/ripple';
@import 'option/option';
@import 'option/option-theme';
+@import 'selection/pseudo-checkbox/pseudo-checkbox-theme';
// Mixin that renders all of the core styles that are not theme-dependent.
@mixin md-core() {
@@ -27,4 +28,5 @@
@mixin md-core-theme($theme) {
@include md-ripple-theme($theme);
@include md-option-theme($theme);
+ @include md-pseudo-checkbox-theme($theme);
}
diff --git a/src/lib/core/core.ts b/src/lib/core/core.ts
index 52c9cec19928..56b7fb801cc0 100644
--- a/src/lib/core/core.ts
+++ b/src/lib/core/core.ts
@@ -3,6 +3,7 @@ import {MdLineModule} from './line/line';
import {RtlModule} from './rtl/dir';
import {ObserveContentModule} from './observe-content/observe-content';
import {MdOptionModule} from './option/option';
+import {MdSelectionModule} from './selection/index';
import {MdRippleModule} from './ripple/ripple';
import {PortalModule} from './portal/portal-directives';
import {OverlayModule} from './overlay/overlay-directives';
@@ -17,6 +18,9 @@ export {ObserveContentModule, ObserveContent} from './observe-content/observe-co
export {MdOptionModule, MdOption} from './option/option';
+// Selection
+export * from './selection/index';
+
// Portals
export {
Portal,
@@ -128,7 +132,8 @@ export {NoConflictStyleCompatibilityMode} from './compatibility/no-conflict-mode
PortalModule,
OverlayModule,
A11yModule,
- MdOptionModule
+ MdOptionModule,
+ MdSelectionModule,
],
exports: [
MdLineModule,
@@ -138,8 +143,9 @@ export {NoConflictStyleCompatibilityMode} from './compatibility/no-conflict-mode
PortalModule,
OverlayModule,
A11yModule,
- MdOptionModule
- ],
+ MdOptionModule,
+ MdSelectionModule,
+ ]
})
export class MdCoreModule {
/** @deprecated */
diff --git a/src/lib/core/selection/index.ts b/src/lib/core/selection/index.ts
new file mode 100644
index 000000000000..9ab6875da845
--- /dev/null
+++ b/src/lib/core/selection/index.ts
@@ -0,0 +1,17 @@
+import {NgModule, ModuleWithProviders} from '@angular/core';
+import {MdPseudoCheckbox} from './pseudo-checkbox/pseudo-checkbox';
+
+export * from './pseudo-checkbox/pseudo-checkbox';
+
+@NgModule({
+ exports: [MdPseudoCheckbox],
+ declarations: [MdPseudoCheckbox]
+})
+export class MdSelectionModule {
+ static forRoot(): ModuleWithProviders {
+ return {
+ ngModule: MdSelectionModule,
+ providers: []
+ };
+ }
+}
diff --git a/src/lib/core/selection/pseudo-checkbox/_pseudo-checkbox-theme.scss b/src/lib/core/selection/pseudo-checkbox/_pseudo-checkbox-theme.scss
new file mode 100644
index 000000000000..fa8f122b3267
--- /dev/null
+++ b/src/lib/core/selection/pseudo-checkbox/_pseudo-checkbox-theme.scss
@@ -0,0 +1,70 @@
+@import '../../theming/theming';
+
+
+@mixin md-pseudo-checkbox-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);
+
+
+ // The color of the checkbox border.
+ $checkbox-border-color: if($is-dark-theme, rgba(white, 0.7), rgba(black, 0.54)) !default;
+
+ // The color of the checkbox's checkmark / mixedmark.
+ $checkbox-mark-color: md-color($background, background);
+
+ // 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: #686868;
+ $black-26pct-opacity-on-light: #b0b0b0;
+ $disabled-color: if($is-dark-theme, $white-30pct-opacity-on-dark, $black-26pct-opacity-on-light);
+
+ .md-pseudo-checkbox-frame {
+ border-color: $checkbox-border-color;
+ }
+
+ .md-pseudo-checkbox-checkmark {
+ fill: $checkbox-mark-color;
+ }
+
+ .md-pseudo-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: $checkbox-mark-color !important;
+ }
+
+ .md-pseudo-checkbox-mixedmark {
+ background-color: $checkbox-mark-color;
+ }
+
+ .md-pseudo-checkbox-indeterminate, .md-pseudo-checkbox-checked {
+ &.md-primary .md-pseudo-checkbox-background {
+ background-color: md-color($primary, 500);
+ }
+
+ &.md-accent .md-pseudo-checkbox-background {
+ background-color: md-color($accent, 500);
+ }
+
+ &.md-warn .md-pseudo-checkbox-background {
+ background-color: md-color($warn, 500);
+ }
+ }
+
+ .md-pseudo-checkbox-disabled {
+ &.md-pseudo-checkbox-checked, &.md-pseudo-checkbox-indeterminate {
+ .md-pseudo-checkbox-background {
+ background-color: $disabled-color;
+ }
+ }
+
+ &:not(.md-pseudo-checkbox-checked) {
+ .md-pseudo-checkbox-frame {
+ border-color: $disabled-color;
+ }
+ }
+ }
+}
diff --git a/src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.html b/src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.html
new file mode 100644
index 000000000000..5374ae40f629
--- /dev/null
+++ b/src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.html
@@ -0,0 +1,15 @@
+
+
diff --git a/src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.scss b/src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.scss
new file mode 100644
index 000000000000..e847a3306846
--- /dev/null
+++ b/src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.scss
@@ -0,0 +1,357 @@
+@import '../../style/variables';
+@import '../../style/elevation';
+
+// The width/height of the checkbox element.
+$md-pseudo-checkbox-size: $md-toggle-size !default;
+// The width of the line used to draw the checkmark / mixedmark.
+$md-pseudo-checkbox-mark-stroke-size: 2 / 15 * $md-pseudo-checkbox-size !default;
+// The width of the checkbox border shown when the checkbox is unchecked.
+$md-pseudo-checkbox-border-width: 2px;
+// The base duration used for the majority of transitions for the checkbox.
+$md-pseudo-checkbox-transition-duration: 90ms;
+
+// Manual calculation done on SVG
+$_md-pseudo-checkbox-mark-path-length: 22.910259;
+$_md-pseudo-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-pseudo-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-pseudo-checkbox-fade-out-background {
+ 0%, 50% {
+ opacity: 1;
+ }
+
+ 100% {
+ opacity: 0;
+ }
+}
+
+// "Draws" in the checkmark when the checkbox goes from unchecked -> checked.
+@keyframes md-pseudo-checkbox-unchecked-checked-checkmark-path {
+ 0%, 50% {
+ stroke-dashoffset: $_md-pseudo-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-pseudo-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-pseudo-checkbox-checked-unchecked-checkmark-path {
+ from {
+ animation-timing-function: $md-fast-out-linear-in-timing-function;
+ stroke-dashoffset: 0;
+ }
+
+ to {
+ stroke-dashoffset: $_md-pseudo-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-pseudo-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-pseudo-checkbox-indeterminate-checked-checkmark {
+ from {
+ animation-timing-function: $_md-pseudo-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-pseudo-checkbox-checked-indeterminate-checkmark, helps provide an
+// illusion of "morphing" from checkmark -> mixedmark.
+@keyframes md-pseudo-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-pseudo-checkbox-indeterminate-checked-checkmark, helps provide an
+// illusion of "morphing" from mixedmark -> checkmark.
+@keyframes md-pseudo-checkbox-indeterminate-checked-mixedmark {
+ from {
+ animation-timing-function: $_md-pseudo-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-pseudo-checkbox-indeterminate-unchecked-mixedmark {
+ 0% {
+ animation-timing-function: linear;
+ opacity: 1;
+ transform: scaleX(1);
+ }
+
+ 32.8%, 100% {
+ opacity: 0;
+ transform: scaleX(0);
+ }
+}
+
+md-pseudo-checkbox {
+ transition: background $swift-ease-out-duration $swift-ease-out-timing-function,
+ md-elevation-transition-property-value();
+
+ height: $md-pseudo-checkbox-size;
+ width: $md-pseudo-checkbox-size;
+ display: inline-block;
+ position: relative;
+ vertical-align: middle;
+}
+
+// Applied to elements that are considered "marks" within the checkbox, e.g. the checkmark and
+// the mixedmark.
+%md-pseudo-checkbox-mark {
+ $width-padding-inset: 2 * $md-pseudo-checkbox-border-width;
+ width: calc(100% - #{$width-padding-inset});
+}
+
+// Applied to elements that cover the checkbox's entire inner container.
+%md-pseudo-checkbox-cover-element {
+ bottom: 0;
+ left: 0;
+ position: absolute;
+ right: 0;
+ top: 0;
+}
+
+// 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-pseudo-checkbox-outer-box {
+ @extend %md-pseudo-checkbox-cover-element;
+ border-radius: 2px;
+ box-sizing: border-box;
+ pointer-events: none;
+}
+
+.md-pseudo-checkbox-frame {
+ @extend %md-pseudo-checkbox-outer-box;
+
+ background-color: transparent;
+ border: $md-pseudo-checkbox-border-width solid;
+ will-change: border-color;
+ transition: border-color $md-pseudo-checkbox-transition-duration
+ $md-linear-out-slow-in-timing-function;
+}
+
+.md-pseudo-checkbox-background {
+ @extend %md-pseudo-checkbox-outer-box;
+
+ align-items: center;
+ display: inline-flex;
+ justify-content: center;
+ transition: background-color $md-pseudo-checkbox-transition-duration
+ $md-linear-out-slow-in-timing-function,
+ opacity $md-pseudo-checkbox-transition-duration
+ $md-linear-out-slow-in-timing-function;
+ will-change: background-color, opacity;
+}
+
+.md-pseudo-checkbox-checkmark {
+ @extend %md-pseudo-checkbox-cover-element;
+ @extend %md-pseudo-checkbox-mark;
+
+ width: 100%;
+}
+
+.md-pseudo-checkbox-checkmark-path {
+ stroke: {
+ dashoffset: $_md-pseudo-checkbox-mark-path-length;
+ dasharray: $_md-pseudo-checkbox-mark-path-length;
+ width: $md-pseudo-checkbox-mark-stroke-size;
+ }
+}
+
+.md-pseudo-checkbox-mixedmark {
+ @extend %md-pseudo-checkbox-mark;
+
+ height: floor($md-pseudo-checkbox-mark-stroke-size);
+ opacity: 0;
+ transform: scaleX(0) rotate(0deg);
+}
+
+.md-pseudo-checkbox-checked {
+ .md-pseudo-checkbox-checkmark {
+ opacity: 1;
+ }
+
+ .md-pseudo-checkbox-checkmark-path {
+ stroke-dashoffset: 0;
+ }
+
+ .md-pseudo-checkbox-mixedmark {
+ transform: scaleX(1) rotate(-45deg);
+ }
+}
+
+.md-pseudo-checkbox-indeterminate {
+ .md-pseudo-checkbox-checkmark {
+ opacity: 0;
+ transform: rotate(45deg);
+ }
+
+ .md-pseudo-checkbox-checkmark-path {
+ stroke-dashoffset: 0;
+ }
+
+ .md-pseudo-checkbox-mixedmark {
+ opacity: 1;
+ transform: scaleX(1) rotate(0deg);
+ }
+}
+
+.md-pseudo-checkbox-unchecked {
+ .md-pseudo-checkbox-background {
+ background-color: transparent;
+ }
+}
+
+.md-pseudo-checkbox-anim {
+ $indeterminate-change-duration: 500ms;
+
+ &-unchecked-checked {
+ .md-pseudo-checkbox-background {
+ animation: $md-pseudo-checkbox-transition-duration * 2 linear 0ms
+ md-pseudo-checkbox-fade-in-background;
+ }
+
+ .md-pseudo-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-pseudo-checkbox-transition-duration * 2 linear 0ms
+ md-pseudo-checkbox-unchecked-checked-checkmark-path;
+ }
+ }
+
+ &-unchecked-indeterminate {
+ .md-pseudo-checkbox-background {
+ animation: $md-pseudo-checkbox-transition-duration * 2 linear 0ms
+ md-pseudo-checkbox-fade-in-background;
+ }
+
+ .md-pseudo-checkbox-mixedmark {
+ animation: $md-pseudo-checkbox-transition-duration linear 0ms
+ md-pseudo-checkbox-unchecked-indeterminate-mixedmark;
+ }
+ }
+
+ &-checked-unchecked {
+ .md-pseudo-checkbox-background {
+ animation: $md-pseudo-checkbox-transition-duration * 2 linear 0ms
+ md-pseudo-checkbox-fade-out-background;
+ }
+
+ .md-pseudo-checkbox-checkmark-path {
+ animation: $md-pseudo-checkbox-transition-duration linear 0ms
+ md-pseudo-checkbox-checked-unchecked-checkmark-path;
+ }
+ }
+
+ &-checked-indeterminate {
+ .md-pseudo-checkbox-checkmark {
+ animation: $md-pseudo-checkbox-transition-duration linear 0ms
+ md-pseudo-checkbox-checked-indeterminate-checkmark;
+ }
+
+ .md-pseudo-checkbox-mixedmark {
+ animation: $md-pseudo-checkbox-transition-duration linear 0ms
+ md-pseudo-checkbox-checked-indeterminate-mixedmark;
+ }
+ }
+
+ &-indeterminate-checked {
+ .md-pseudo-checkbox-checkmark {
+ animation: $indeterminate-change-duration linear 0ms
+ md-pseudo-checkbox-indeterminate-checked-checkmark;
+ }
+
+ .md-pseudo-checkbox-mixedmark {
+ animation: $indeterminate-change-duration linear 0ms
+ md-pseudo-checkbox-indeterminate-checked-mixedmark;
+ }
+ }
+
+ &-indeterminate-unchecked {
+ .md-pseudo-checkbox-background {
+ animation: $md-pseudo-checkbox-transition-duration * 2 linear 0ms
+ md-pseudo-checkbox-fade-out-background;
+ }
+
+ .md-pseudo-checkbox-mixedmark {
+ animation: $indeterminate-change-duration * 0.6 linear 0ms
+ md-pseudo-checkbox-indeterminate-unchecked-mixedmark;
+ }
+ }
+}
+
diff --git a/src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.spec.ts b/src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.spec.ts
new file mode 100644
index 000000000000..c5428d24bfad
--- /dev/null
+++ b/src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.spec.ts
@@ -0,0 +1,189 @@
+import {
+ async,
+ ComponentFixture,
+ TestBed,
+} from '@angular/core/testing';
+import {Component, DebugElement} from '@angular/core';
+import {By} from '@angular/platform-browser';
+import {MdSelectionModule} from '../index';
+import {MdPseudoCheckbox} from './pseudo-checkbox';
+
+
+describe('MdPseudoCheckbox', () => {
+ let fixture: ComponentFixture;
+ let checkboxDebugElement: DebugElement;
+ let checkboxNativeElement: HTMLElement;
+ let checkboxInstance: MdPseudoCheckbox;
+ let testComponent: SimplePseudoCheckbox;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [MdSelectionModule.forRoot()],
+ declarations: [SimplePseudoCheckbox],
+ });
+
+ TestBed.compileComponents().then(() => {
+ fixture = TestBed.createComponent(SimplePseudoCheckbox);
+ fixture.detectChanges();
+
+ checkboxDebugElement = fixture.debugElement.query(By.directive(MdPseudoCheckbox));
+ checkboxNativeElement = checkboxDebugElement.nativeElement;
+ checkboxInstance = checkboxDebugElement.componentInstance;
+ testComponent = fixture.debugElement.componentInstance;
+ });
+ }));
+
+ it('should add and remove the checked state', () => {
+ expect(checkboxInstance.checked).toBe(false);
+ expect(checkboxNativeElement.classList).not.toContain('md-pseudo-checkbox-checked');
+
+ testComponent.checked = true;
+ fixture.detectChanges();
+
+ expect(checkboxInstance.checked).toBe(true);
+ expect(checkboxNativeElement.classList).toContain('md-pseudo-checkbox-checked');
+
+ testComponent.checked = false;
+ fixture.detectChanges();
+
+ expect(checkboxInstance.checked).toBe(false);
+ expect(checkboxNativeElement.classList).not.toContain('md-pseudo-checkbox-checked');
+ });
+
+ it('should add and remove indeterminate state', () => {
+ expect(checkboxNativeElement.classList).not.toContain('md-pseudo-checkbox-checked');
+
+ testComponent.indeterminate = true;
+ fixture.detectChanges();
+
+ expect(checkboxNativeElement.classList).toContain('md-pseudo-checkbox-indeterminate');
+
+ testComponent.indeterminate = false;
+ fixture.detectChanges();
+
+ expect(checkboxNativeElement.classList).not.toContain('md-pseudo-checkbox-indeterminate');
+ });
+
+ it('should add and remove disabled state', () => {
+ expect(checkboxInstance.disabled).toBe(false);
+ expect(checkboxNativeElement.classList).not.toContain('md-pseudo-checkbox-disabled');
+
+ testComponent.disabled = true;
+ fixture.detectChanges();
+
+ expect(checkboxInstance.disabled).toBe(true);
+ expect(checkboxNativeElement.classList).toContain('md-pseudo-checkbox-disabled');
+
+ testComponent.disabled = false;
+ fixture.detectChanges();
+
+ expect(checkboxInstance.disabled).toBe(false);
+ expect(checkboxNativeElement.classList).not.toContain('md-pseudo-checkbox-disabled');
+ });
+
+ describe('transition classes', () => {
+ it('should transition unchecked -> checked -> unchecked', () => {
+ testComponent.checked = true;
+ fixture.detectChanges();
+ expect(checkboxNativeElement.classList).toContain(
+ 'md-pseudo-checkbox-anim-unchecked-checked');
+
+ testComponent.checked = false;
+ fixture.detectChanges();
+ expect(checkboxNativeElement.classList).not.toContain(
+ 'md-pseudo-checkbox-anim-unchecked-checked');
+ expect(checkboxNativeElement.classList).toContain(
+ 'md-pseudo-checkbox-anim-checked-unchecked');
+ });
+
+ it('should transition unchecked -> indeterminate -> unchecked', () => {
+ testComponent.indeterminate = true;
+ fixture.detectChanges();
+
+ expect(checkboxNativeElement.classList)
+ .toContain('md-pseudo-checkbox-anim-unchecked-indeterminate');
+
+ testComponent.indeterminate = false;
+ fixture.detectChanges();
+
+ expect(checkboxNativeElement.classList)
+ .not.toContain('md-pseudo-checkbox-anim-unchecked-indeterminate');
+ expect(checkboxNativeElement.classList)
+ .toContain('md-pseudo-checkbox-anim-indeterminate-unchecked');
+ });
+
+ it('should transition indeterminate -> checked', () => {
+ testComponent.indeterminate = true;
+ fixture.detectChanges();
+
+ testComponent.checked = true;
+ fixture.detectChanges();
+
+ expect(checkboxNativeElement.classList).not.toContain(
+ 'md-pseudo-checkbox-anim-unchecked-indeterminate');
+ expect(checkboxNativeElement.classList).toContain(
+ 'md-pseudo-checkbox-anim-indeterminate-checked');
+ });
+
+ it('should not apply transition classes when there is no state change', () => {
+ testComponent.checked = checkboxInstance.checked;
+ fixture.detectChanges();
+ expect(checkboxNativeElement).not.toMatch(/^md-pseudo-checkbox-anim/g);
+
+ testComponent.indeterminate = checkboxInstance.indeterminate;
+ expect(checkboxNativeElement).not.toMatch(/^md-pseudo-checkbox-anim/g);
+ });
+
+ it('should not initially have any transition classes', () => {
+ expect(checkboxNativeElement).not.toMatch(/^md-pseudo-checkbox-anim/g);
+ });
+ });
+
+ describe('color behaviour', () => {
+ it('should apply class based on color attribute', () => {
+ testComponent.color = 'primary';
+ fixture.detectChanges();
+ expect(checkboxDebugElement.nativeElement.classList).toContain('md-primary');
+
+ testComponent.color = 'accent';
+ fixture.detectChanges();
+ expect(checkboxDebugElement.nativeElement.classList).toContain('md-accent');
+ });
+
+ it('should should not clear previous defined classes', () => {
+ checkboxDebugElement.nativeElement.classList.add('custom-class');
+
+ testComponent.color = 'primary';
+ fixture.detectChanges();
+
+ expect(checkboxDebugElement.nativeElement.classList).toContain('md-primary');
+ expect(checkboxDebugElement.nativeElement.classList).toContain('custom-class');
+
+ testComponent.color = 'accent';
+ fixture.detectChanges();
+
+ expect(checkboxDebugElement.nativeElement.classList).not.toContain('md-primary');
+ expect(checkboxDebugElement.nativeElement.classList).toContain('md-accent');
+ expect(checkboxDebugElement.nativeElement.classList).toContain('custom-class');
+
+ });
+ });
+
+});
+
+
+@Component({
+ template: `
+
+ `
+})
+export class SimplePseudoCheckbox {
+ checked = false;
+ indeterminate = false;
+ disabled = false;
+ color = 'accent';
+}
diff --git a/src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.ts b/src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.ts
new file mode 100644
index 000000000000..5c95f7771437
--- /dev/null
+++ b/src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.ts
@@ -0,0 +1,156 @@
+import {
+ Component,
+ Input,
+ Renderer,
+ ElementRef,
+ ChangeDetectorRef,
+ ViewEncapsulation,
+} from '@angular/core';
+
+export type MdPseudoCheckboxState = 'unchecked' | 'checked' | 'indeterminate';
+
+/**
+ * Represents the different states that require custom transitions between them.
+ * @docs-private
+ */
+export 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
+}
+
+/**
+ * Represents a check box, without any of the underlying form control functionality.
+ * Intended to be used for composing other components.
+ * @docs-private
+ */
+@Component({
+ moduleId: module.id,
+ encapsulation: ViewEncapsulation.None,
+ selector: 'md-pseudo-checkbox',
+ styleUrls: ['pseudo-checkbox.css'],
+ templateUrl: 'pseudo-checkbox.html',
+ host: {
+ '[class.md-pseudo-checkbox-indeterminate]': 'indeterminate',
+ '[class.md-pseudo-checkbox-checked]': 'checked',
+ '[class.md-pseudo-checkbox-disabled]': 'disabled',
+ },
+})
+export class MdPseudoCheckbox {
+ constructor(
+ private _renderer: Renderer,
+ private _elementRef: ElementRef,
+ private _changeDetectorRef: ChangeDetectorRef) {
+
+ this.color = 'accent';
+ }
+
+ @Input() disabled: boolean = false;
+
+ @Input()
+ get checked() { return this._checked; }
+ set checked(checked: boolean) {
+ if (checked != this.checked) {
+ this._indeterminate = false;
+ this._checked = checked;
+ this._transitionCheckState(
+ this._checked ? TransitionCheckState.Checked : TransitionCheckState.Unchecked);
+ this._changeDetectorRef.markForCheck();
+ }
+ }
+
+ @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 color of the button. Can be `primary`, `accent`, or `warn`. */
+ @Input()
+ get color(): string { return this._color; }
+ set color(value: string) { this._updateColor(value); }
+
+ private _checked: boolean = false;
+
+ private _indeterminate: boolean = false;
+
+ private _currentCheckState: TransitionCheckState = TransitionCheckState.Init;
+
+ private _currentAnimationClass: string = '';
+
+ private _color: string;
+
+ 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:
+ // Handle edge case where user interacts with checkbox that does not have [(ngModel)] or
+ // [checked] bound to it.
+ if (newState === TransitionCheckState.Checked) {
+ animSuffix = 'unchecked-checked';
+ } else {
+ return '';
+ }
+ break;
+ 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-pseudo-checkbox-anim-${animSuffix}`;
+ }
+
+ private _updateColor(newColor: string) {
+ this._setElementColor(this._color, false);
+ this._setElementColor(newColor, true);
+ this._color = newColor;
+ }
+
+ private _setElementColor(color: string, isAdd: boolean) {
+ if (color != null && color != '') {
+ this._renderer.setElementClass(this._elementRef.nativeElement, `md-${color}`, isAdd);
+ }
+ }
+}