diff --git a/src/components/slide-toggle/slide-toggle.html b/src/components/slide-toggle/slide-toggle.html
index 5e22fef49ac9..6a3af979532f 100644
--- a/src/components/slide-toggle/slide-toggle.html
+++ b/src/components/slide-toggle/slide-toggle.html
@@ -2,7 +2,9 @@
diff --git a/src/components/slide-toggle/slide-toggle.scss b/src/components/slide-toggle/slide-toggle.scss
index 9e0123ba863b..1ff06307555d 100644
--- a/src/components/slide-toggle/slide-toggle.scss
+++ b/src/components/slide-toggle/slide-toggle.scss
@@ -12,12 +12,28 @@ $md-slide-toggle-thumb-size: 20px !default;
$md-slide-toggle-margin: 16px !default;
@mixin md-switch-checked($palette) {
- .md-slide-toggle-thumb {
- background-color: md-color($palette);
+ &.md-checked {
+ .md-slide-toggle-thumb {
+ background-color: md-color($palette);
+ }
+
+ .md-slide-toggle-bar {
+ background-color: md-color($palette, 0.5);
+ }
}
+}
+
+@mixin md-switch-ripple($palette) {
+ // Temporary ripple effect for the thumb of the slide-toggle.
+ // Bind to the parent selector and specify the current palette.
+ @include md-temporary-ink-ripple(slide-toggle, true, $palette);
- .md-slide-toggle-bar {
- background-color: md-color($palette, 0.5);
+ &.md-slide-toggle-focused {
+ &:not(.md-checked) .md-ink-ripple {
+ // When the slide-toggle is not checked and it shows its focus indicator, it should use a 12% opacity
+ // of black in light themes and 12% of white in dark themes.
+ background-color: md-color($md-foreground, divider);
+ }
}
}
@@ -34,21 +50,24 @@ $md-slide-toggle-margin: 16px !default;
outline: none;
&.md-checked {
- @include md-switch-checked($md-accent);
-
- &.md-primary {
- @include md-switch-checked($md-primary);
- }
-
- &.md-warn {
- @include md-switch-checked($md-warn);
- }
-
.md-slide-toggle-thumb-container {
transform: translate3d(100%, 0, 0);
}
}
+ @include md-switch-checked($md-accent);
+ @include md-switch-ripple($md-accent);
+
+ &.md-primary {
+ @include md-switch-checked($md-primary);
+ @include md-switch-ripple($md-primary);
+ }
+
+ &.md-warn {
+ @include md-switch-checked($md-warn);
+ @include md-switch-ripple($md-warn);
+ }
+
&.md-disabled {
.md-slide-toggle-label, .md-slide-toggle-container {
diff --git a/src/components/slide-toggle/slide-toggle.spec.ts b/src/components/slide-toggle/slide-toggle.spec.ts
index 4f86c2ab9b29..15cca9a781d5 100644
--- a/src/components/slide-toggle/slide-toggle.spec.ts
+++ b/src/components/slide-toggle/slide-toggle.spec.ts
@@ -235,10 +235,30 @@ describe('MdSlideToggle', () => {
expect(slideToggleElement.classList).toContain('md-checked');
});
+ it('should correctly set the slide-toggle to checked on focus', () => {
+ expect(slideToggleElement.classList).not.toContain('md-slide-toggle-focused');
+
+ dispatchFocusChangeEvent('focus', inputElement);
+ fixture.detectChanges();
+
+ expect(slideToggleElement.classList).toContain('md-slide-toggle-focused');
+ });
+
});
});
+/**
+ * Dispatches a focus change event from an element.
+ * @param eventName Name of the event, either 'focus' or 'blur'.
+ * @param element The element from which the event will be dispatched.
+ */
+function dispatchFocusChangeEvent(eventName: string, element: HTMLElement): void {
+ let event = document.createEvent('Event');
+ event.initEvent(eventName, true, true);
+ element.dispatchEvent(event);
+}
+
@Component({
selector: 'slide-toggle-test-app',
template: `
diff --git a/src/components/slide-toggle/slide-toggle.ts b/src/components/slide-toggle/slide-toggle.ts
index cd0d8bf02210..74b448f0b8b9 100644
--- a/src/components/slide-toggle/slide-toggle.ts
+++ b/src/components/slide-toggle/slide-toggle.ts
@@ -30,7 +30,10 @@ let nextId = 0;
host: {
'[class.md-checked]': 'checked',
'[class.md-disabled]': 'disabled',
- '(click)': 'onTouched()'
+ // This md-slide-toggle prefix will change, once the temporary ripple is removed.
+ '[class.md-slide-toggle-focused]': '_hasFocus',
+ '(click)': 'onTouched()',
+ '(mousedown)': 'setMousedown()'
},
templateUrl: 'slide-toggle.html',
styleUrls: ['slide-toggle.css'],
@@ -46,6 +49,8 @@ export class MdSlideToggle implements ControlValueAccessor {
private _uniqueId = `md-slide-toggle-${++nextId}`;
private _checked: boolean = false;
private _color: string;
+ private _hasFocus: boolean = false;
+ private _isMousedown: boolean = false;
@Input() @BooleanFieldValue() disabled: boolean = false;
@Input() name: string = null;
@@ -76,6 +81,31 @@ export class MdSlideToggle implements ControlValueAccessor {
}
}
+ /** @internal */
+ setMousedown() {
+ // We only *show* the focus style when focus has come to the button via the keyboard.
+ // The Material Design spec is silent on this topic, and without doing this, the
+ // button continues to look :active after clicking.
+ // @see http://marcysutton.com/button-focus-hell/
+ this._isMousedown = true;
+ setTimeout(() => this._isMousedown = false, 100);
+ }
+
+ /** @internal */
+ onInputFocus() {
+ // Only show the focus / ripple indicator when the focus was not triggered by a mouse
+ // interaction on the component.
+ if (!this._isMousedown) {
+ this._hasFocus = true;
+ }
+ }
+
+ /** @internal */
+ onInputBlur() {
+ this._hasFocus = false;
+ this.onTouched();
+ }
+
/**
* Implemented as part of ControlValueAccessor.
* @internal
diff --git a/src/core/style/_mixins.scss b/src/core/style/_mixins.scss
index 46ebdd6546ef..6df7cee07341 100644
--- a/src/core/style/_mixins.scss
+++ b/src/core/style/_mixins.scss
@@ -37,15 +37,22 @@
display: table;
}
}
-@mixin md-temporary-ink-ripple($component) {
+
+/**
+ * A mixin, which generates temporary ink ripple on a given component.
+ * When $bindToParent is set to true, it will check for the focused class on the same selector as you included
+ * that mixin.
+ * It is also possible to specify the color palette of the temporary ripple. By default it uses the
+ * accent palette for its background.
+ */
+@mixin md-temporary-ink-ripple($component, $bindToParent: false, $palette: $md-accent) {
// TODO(mtlin): Replace when ink ripple component is implemented.
// A placeholder ink ripple, shown when keyboard focused.
.md-ink-ripple {
- background-color: md-color($md-accent);
border-radius: 50%;
+ opacity: 0;
height: 48px;
left: 50%;
- opacity: 0;
overflow: hidden;
pointer-events: none;
position: absolute;
@@ -53,16 +60,17 @@
transform: translate(-50%,-50%);
transition: opacity ease 0.28s, background-color ease 0.28s;
width: 48px;
+ }
- // Fade in when radio focused.
- .md-#{$component}-focused & {
- opacity: 0.1;
- }
+ // Fade in when radio focused.
+ #{if($bindToParent, '&', '')}.md-#{$component}-focused .md-ink-ripple {
+ opacity: 1;
+ background-color: md-color($palette, 0.26);
+ }
- // TODO(mtlin): This corresponds to disabled focus state, but it's unclear how to enter into
- // this state.
- .md-#{$component}-disabled & {
- background: #000;
- }
+ // TODO(mtlin): This corresponds to disabled focus state, but it's unclear how to enter into
+ // this state.
+ #{if($bindToParent, '&', '')}.md-#{$component}-disabled .md-ink-ripple {
+ background-color: #000;
}
}
\ No newline at end of file