Skip to content

update(slide-toggle): add focus indicator #475

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions src/components/slide-toggle/slide-toggle.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
<div class="md-slide-toggle-container">
<div class="md-slide-toggle-bar"></div>
<div class="md-slide-toggle-thumb-container">
<div class="md-slide-toggle-thumb"></div>
<div class="md-slide-toggle-thumb">
<div class="md-ink-ripple"></div>
</div>
</div>

<input #input class="md-slide-toggle-checkbox" type="checkbox"
Expand All @@ -13,7 +15,8 @@
[attr.name]="name"
[attr.aria-label]="ariaLabel"
[attr.aria-labelledby]="ariaLabelledby"
(blur)="onTouched()"
(blur)="onInputBlur()"
(focus)="onInputFocus()"
(change)="onChangeEvent()">
</div>
<span class="md-slide-toggle-content">
Expand Down
47 changes: 33 additions & 14 deletions src/components/slide-toggle/slide-toggle.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}

Expand All @@ -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);
Copy link
Member Author

@devversion devversion May 19, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI: I know, this could be grouped up, into one mixin. But since this is quite temporary and more clear, I decided to do it this way.

}

&.md-warn {
@include md-switch-checked($md-warn);
@include md-switch-ripple($md-warn);
}

&.md-disabled {

.md-slide-toggle-label, .md-slide-toggle-container {
Expand Down
20 changes: 20 additions & 0 deletions src/components/slide-toggle/slide-toggle.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: `
Expand Down
32 changes: 31 additions & 1 deletion src/components/slide-toggle/slide-toggle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
Expand All @@ -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;
Expand Down Expand Up @@ -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
Expand Down
32 changes: 20 additions & 12 deletions src/core/style/_mixins.scss
Original file line number Diff line number Diff line change
Expand Up @@ -37,32 +37,40 @@
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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you expand the comment to explain what bindToParent is used for?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything updated now 👍

// 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;
top: 50%;
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;
}
}