From 19f9974344a510073c797739772275294b3af1fd Mon Sep 17 00:00:00 2001 From: Paul Gschwendtner Date: Thu, 19 May 2016 22:17:41 +0200 Subject: [PATCH] update(slide-toggle): add focus indicator Fixes #471. --- src/components/slide-toggle/slide-toggle.html | 7 ++- src/components/slide-toggle/slide-toggle.scss | 47 +++++++++++++------ .../slide-toggle/slide-toggle.spec.ts | 20 ++++++++ src/components/slide-toggle/slide-toggle.ts | 32 ++++++++++++- src/core/style/_mixins.scss | 32 ++++++++----- 5 files changed, 109 insertions(+), 29 deletions(-) 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