Skip to content

Commit 4b7b205

Browse files
committed
Add focus to checkbox, radio, button-toggle and slide-toggle
1 parent cf1b4b9 commit 4b7b205

File tree

10 files changed

+90
-2
lines changed

10 files changed

+90
-2
lines changed

src/demo-app/slide-toggle/slide-toggle-demo.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,4 @@
2626

2727
</form>
2828

29-
</div>
29+
</div>

src/lib/button-toggle/button-toggle.spec.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,16 @@ describe('MdButtonToggle', () => {
500500
expect(changeSpy).toHaveBeenCalledTimes(2);
501501
}));
502502

503+
it('should focus on underlying input element when focus() is called', () => {
504+
let nativeRadioInput = buttonToggleDebugElement.query(By.css('input')).nativeElement;
505+
expect(document.activeElement).not.toBe(nativeRadioInput);
506+
507+
buttonToggleInstance.focus();
508+
fixture.detectChanges();
509+
510+
expect(document.activeElement).toBe(nativeRadioInput);
511+
});
512+
503513
});
504514
});
505515

src/lib/button-toggle/button-toggle.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ import {
44
Component,
55
ContentChildren,
66
Directive,
7+
ElementRef,
78
EventEmitter,
89
HostBinding,
910
Input,
1011
OnInit,
1112
Optional,
1213
Output,
1314
QueryList,
15+
ViewChild,
1416
ViewEncapsulation,
1517
forwardRef,
1618
AfterViewInit
@@ -266,6 +268,8 @@ export class MdButtonToggle implements OnInit {
266268
return this._change.asObservable();
267269
}
268270

271+
@ViewChild('input') _inputElement: ElementRef;
272+
269273
constructor(@Optional() toggleGroup: MdButtonToggleGroup,
270274
@Optional() toggleGroupMultiple: MdButtonToggleGroupMultiple,
271275
public buttonToggleDispatcher: MdUniqueSelectionDispatcher) {
@@ -395,6 +399,10 @@ export class MdButtonToggle implements OnInit {
395399
// Preventing bubbling for the second event will solve that issue.
396400
event.stopPropagation();
397401
}
402+
403+
focus() {
404+
this._inputElement.nativeElement.focus();
405+
}
398406
}
399407

400408

src/lib/checkbox/checkbox.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<label class="md-checkbox-layout">
22
<div class="md-checkbox-inner-container">
3-
<input class="md-checkbox-input md-visually-hidden" type="checkbox"
3+
<input #input
4+
class="md-checkbox-input md-visually-hidden" type="checkbox"
45
[id]="inputId"
56
[required]="required"
67
[checked]="checked"

src/lib/checkbox/checkbox.spec.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,15 @@ describe('MdCheckbox', () => {
278278
expect(inputElement.required).toBe(false);
279279
});
280280

281+
it('should focus on underlying input element when focus() is called', () => {
282+
expect(document.activeElement).not.toBe(inputElement);
283+
284+
checkboxInstance.focus();
285+
fixture.detectChanges();
286+
287+
expect(document.activeElement).toBe(inputElement);
288+
});
289+
281290
describe('color behaviour', () => {
282291
it('should apply class based on color attribute', () => {
283292
testComponent.checkboxColor = 'primary';

src/lib/checkbox/checkbox.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
forwardRef,
1111
NgModule,
1212
ModuleWithProviders,
13+
ViewChild,
1314
} from '@angular/core';
1415
import {CommonModule} from '@angular/common';
1516
import {NG_VALUE_ACCESSOR, ControlValueAccessor} from '@angular/forms';
@@ -134,6 +135,9 @@ export class MdCheckbox implements ControlValueAccessor {
134135
/** Event emitted when the checkbox's `checked` value changes. */
135136
@Output() change: EventEmitter<MdCheckboxChange> = new EventEmitter<MdCheckboxChange>();
136137

138+
/** The native `<input type=checkbox> element */
139+
@ViewChild('input') _inputElement: ElementRef;
140+
137141
/** Called when the checkbox is blurred. Needed to properly implement ControlValueAccessor. */
138142
onTouched: () => any = () => {};
139143

@@ -314,6 +318,11 @@ export class MdCheckbox implements ControlValueAccessor {
314318
}
315319
}
316320

321+
focus() {
322+
this._inputElement.nativeElement.focus();
323+
this._onInputFocus();
324+
}
325+
317326
_onInputClick(event: Event) {
318327
// We have to stop propagation for click events on the visual hidden input element.
319328
// By default, when a user clicks on a label element, a generated click event will be

src/lib/radio/radio.spec.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,20 @@ describe('MdRadio', () => {
188188
expect(radioNativeElements[0].classList).not.toContain('md-radio-focused');
189189
});
190190

191+
it('should focus individual radio buttons', () => {
192+
let nativeRadioInput = <HTMLElement> radioNativeElements[0].querySelector('input');
193+
194+
radioInstances[0].focus();
195+
fixture.detectChanges();
196+
197+
expect(radioNativeElements[0].classList).toContain('md-radio-focused');
198+
199+
dispatchEvent('blur', nativeRadioInput);
200+
fixture.detectChanges();
201+
202+
expect(radioNativeElements[0].classList).not.toContain('md-radio-focused');
203+
});
204+
191205
it('should update the group and radios when updating the group value', () => {
192206
expect(groupInstance.value).toBeFalsy();
193207

@@ -519,6 +533,16 @@ describe('MdRadio', () => {
519533

520534
expect(fruitRadioNativeInputs[0].getAttribute('aria-labelledby')).toBe('uvw');
521535
});
536+
537+
it('should focus on underlying input element when focus() is called', () => {
538+
for (let i = 0; i < fruitRadioInstances.length; i++) {
539+
expect(document.activeElement).not.toBe(fruitRadioNativeInputs[i]);
540+
fruitRadioInstances[i].focus();
541+
fixture.detectChanges();
542+
543+
expect(document.activeElement).toBe(fruitRadioNativeInputs[i]);
544+
}
545+
});
522546
});
523547
});
524548

src/lib/radio/radio.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
forwardRef,
1616
NgModule,
1717
ModuleWithProviders,
18+
ViewChild,
1819
} from '@angular/core';
1920
import {CommonModule} from '@angular/common';
2021
import {
@@ -281,6 +282,9 @@ export class MdRadioButton implements OnInit {
281282
@Output()
282283
change: EventEmitter<MdRadioChange> = new EventEmitter<MdRadioChange>();
283284

285+
/** The native `<input type=radio> element */
286+
@ViewChild('input') _inputElement: ElementRef;
287+
284288
constructor(@Optional() radioGroup: MdRadioGroup,
285289
private _elementRef: ElementRef,
286290
public radioDispatcher: MdUniqueSelectionDispatcher) {
@@ -400,6 +404,11 @@ export class MdRadioButton implements OnInit {
400404
this._isFocused = true;
401405
}
402406

407+
focus() {
408+
this._inputElement.nativeElement.focus();
409+
this._onInputFocus();
410+
}
411+
403412
/** TODO: internal */
404413
_onInputBlur() {
405414
this._isFocused = false;

src/lib/slide-toggle/slide-toggle.spec.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,16 @@ describe('MdSlideToggle', () => {
336336
expect(inputElement.required).toBe(false);
337337
});
338338

339+
it('should focus on underlying input element when focus() is called', () => {
340+
expect(slideToggleElement.classList).not.toContain('md-slide-toggle-focused');
341+
expect(document.activeElement).not.toBe(inputElement);
342+
343+
slideToggle.focus();
344+
fixture.detectChanges();
345+
346+
expect(document.activeElement).toBe(inputElement);
347+
expect(slideToggleElement.classList).toContain('md-slide-toggle-focused');
348+
});
339349
});
340350

341351
describe('custom template', () => {

src/lib/slide-toggle/slide-toggle.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
AfterContentInit,
1111
NgModule,
1212
ModuleWithProviders,
13+
ViewChild,
1314
ViewEncapsulation,
1415
} from '@angular/core';
1516
import {HAMMER_GESTURE_CONFIG} from '@angular/platform-browser';
@@ -86,6 +87,8 @@ export class MdSlideToggle implements AfterContentInit, ControlValueAccessor {
8687
// Returns the unique id for the visual hidden input.
8788
getInputId = () => `${this.id || this._uniqueId}-input`;
8889

90+
@ViewChild('input') _inputElement: ElementRef;
91+
8992
constructor(private _elementRef: ElementRef, private _renderer: Renderer) {}
9093

9194
/** TODO: internal */
@@ -174,6 +177,11 @@ export class MdSlideToggle implements AfterContentInit, ControlValueAccessor {
174177
this.onTouched = fn;
175178
}
176179

180+
focus() {
181+
this._inputElement.nativeElement.focus();
182+
this._onInputFocus();
183+
}
184+
177185
@Input()
178186
get checked() {
179187
return !!this._checked;

0 commit comments

Comments
 (0)