mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
committed by
Kara
parent
fb565c0117
commit
c9347537ae
@@ -18,7 +18,6 @@
|
||||
[attr.aria-label]="ariaLabel"
|
||||
[attr.aria-labelledby]="ariaLabelledby"
|
||||
(change)="_onInputChange($event)"
|
||||
(focus)="_onInputFocus()"
|
||||
(blur)="_onInputBlur()"
|
||||
(click)="_onInputClick($event)">
|
||||
|
||||
|
||||
+25
-27
@@ -6,9 +6,21 @@ import {MdRadioGroup, MdRadioButton, MdRadioChange, MdRadioModule} from './radio
|
||||
import {ViewportRuler} from '../core/overlay/position/viewport-ruler';
|
||||
import {FakeViewportRuler} from '../core/overlay/position/fake-viewport-ruler';
|
||||
import {dispatchFakeEvent} from '../core/testing/dispatch-events';
|
||||
import {FocusOriginMonitor, FocusOrigin} from '../core';
|
||||
import {RIPPLE_FADE_IN_DURATION, RIPPLE_FADE_OUT_DURATION} from '../core/ripple/ripple-renderer';
|
||||
import {Subject} from 'rxjs/Subject';
|
||||
|
||||
|
||||
describe('MdRadio', () => {
|
||||
let fakeFocusOriginMonitorStream = new Subject<FocusOrigin>();
|
||||
let fakeFocusOriginMonitor = {
|
||||
monitor: () => fakeFocusOriginMonitorStream.asObservable(),
|
||||
unmonitor: () => {},
|
||||
focusVia: (element: HTMLElement, renderer: any, origin: FocusOrigin) => {
|
||||
element.focus();
|
||||
fakeFocusOriginMonitorStream.next(origin);
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
@@ -21,6 +33,7 @@ describe('MdRadio', () => {
|
||||
],
|
||||
providers: [
|
||||
{provide: ViewportRuler, useClass: FakeViewportRuler},
|
||||
{provide: FocusOriginMonitor, useValue: fakeFocusOriginMonitor}
|
||||
]
|
||||
});
|
||||
|
||||
@@ -177,37 +190,22 @@ describe('MdRadio', () => {
|
||||
expect(changeSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
// TODO(jelbourn): test this in an e2e test with *real* focus, rather than faking
|
||||
// a focus / blur event.
|
||||
it('should focus individual radio buttons', () => {
|
||||
let nativeRadioInput = <HTMLElement> radioNativeElements[0].querySelector('input');
|
||||
it('should show a ripple when focusing via the keyboard', fakeAsync(() => {
|
||||
expect(radioNativeElements[0].querySelectorAll('.mat-ripple-element').length)
|
||||
.toBe(0, 'Expected no ripples on init.');
|
||||
|
||||
expect(nativeRadioInput.classList).not.toContain('mat-radio-focused');
|
||||
fakeFocusOriginMonitorStream.next('keyboard');
|
||||
tick(RIPPLE_FADE_IN_DURATION);
|
||||
|
||||
dispatchFakeEvent(nativeRadioInput, 'focus');
|
||||
fixture.detectChanges();
|
||||
expect(radioNativeElements[0].querySelectorAll('.mat-ripple-element').length)
|
||||
.toBe(1, 'Expected one ripple after keyboard focus.');
|
||||
|
||||
expect(radioNativeElements[0].classList).toContain('mat-radio-focused');
|
||||
dispatchFakeEvent(radioNativeElements[0].querySelector('input'), 'blur');
|
||||
tick(RIPPLE_FADE_OUT_DURATION);
|
||||
|
||||
dispatchFakeEvent(nativeRadioInput, 'blur');
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(radioNativeElements[0].classList).not.toContain('mat-radio-focused');
|
||||
});
|
||||
|
||||
it('should focus individual radio buttons', () => {
|
||||
let nativeRadioInput = <HTMLElement> radioNativeElements[0].querySelector('input');
|
||||
|
||||
radioInstances[0].focus();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(radioNativeElements[0].classList).toContain('mat-radio-focused');
|
||||
|
||||
dispatchFakeEvent(nativeRadioInput, 'blur');
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(radioNativeElements[0].classList).not.toContain('mat-radio-focused');
|
||||
});
|
||||
expect(radioNativeElements[0].querySelectorAll('.mat-ripple-element').length)
|
||||
.toBe(0, 'Expected no ripples on blur.');
|
||||
}));
|
||||
|
||||
it('should update the group and radios when updating the group value', () => {
|
||||
expect(groupInstance.value).toBeFalsy();
|
||||
|
||||
+48
-25
@@ -6,7 +6,6 @@ import {
|
||||
ElementRef,
|
||||
Renderer,
|
||||
EventEmitter,
|
||||
HostBinding,
|
||||
Input,
|
||||
OnInit,
|
||||
Optional,
|
||||
@@ -17,17 +16,23 @@ import {
|
||||
NgModule,
|
||||
ModuleWithProviders,
|
||||
ViewChild,
|
||||
OnDestroy,
|
||||
AfterViewInit,
|
||||
} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {NG_VALUE_ACCESSOR, ControlValueAccessor} from '@angular/forms';
|
||||
import {
|
||||
MdRippleModule,
|
||||
RippleRef,
|
||||
UniqueSelectionDispatcher,
|
||||
CompatibilityModule,
|
||||
UNIQUE_SELECTION_DISPATCHER_PROVIDER,
|
||||
MdRipple,
|
||||
FocusOriginMonitor,
|
||||
} from '../core';
|
||||
import {coerceBooleanProperty} from '../core/coercion/boolean-property';
|
||||
import {VIEWPORT_RULER_PROVIDER} from '../core/overlay/position/viewport-ruler';
|
||||
import {Subscription} from 'rxjs/Subscription';
|
||||
|
||||
|
||||
/**
|
||||
@@ -265,24 +270,21 @@ export class MdRadioGroup implements AfterContentInit, ControlValueAccessor {
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
host: {
|
||||
'[class.mat-radio-button]': 'true',
|
||||
'[class.mat-radio-checked]': 'checked',
|
||||
'[class.mat-radio-disabled]': 'disabled',
|
||||
'[attr.id]': 'id',
|
||||
}
|
||||
})
|
||||
export class MdRadioButton implements OnInit {
|
||||
|
||||
@HostBinding('class.mat-radio-focused')
|
||||
_isFocused: boolean;
|
||||
export class MdRadioButton implements OnInit, AfterViewInit, OnDestroy {
|
||||
|
||||
/** Whether this radio is checked. */
|
||||
private _checked: boolean = false;
|
||||
|
||||
/** The unique ID for the radio button. */
|
||||
@HostBinding('id')
|
||||
@Input()
|
||||
id: string = `md-radio-${_uniqueIdCounter++}`;
|
||||
@Input() id: string = `md-radio-${_uniqueIdCounter++}`;
|
||||
|
||||
/** Analog to HTML 'name' attribute used to group radios for unique selection. */
|
||||
@Input()
|
||||
name: string;
|
||||
@Input() name: string;
|
||||
|
||||
/** Used to set the 'aria-label' attribute on the underlying input element. */
|
||||
@Input('aria-label') ariaLabel: string;
|
||||
@@ -299,6 +301,15 @@ export class MdRadioButton implements OnInit {
|
||||
/** Whether the ripple effect on click should be disabled. */
|
||||
private _disableRipple: boolean;
|
||||
|
||||
/** The child ripple instance. */
|
||||
@ViewChild(MdRipple) _ripple: MdRipple;
|
||||
|
||||
/** Stream of focus event from the focus origin monitor. */
|
||||
private _focusOriginMonitorSubscription: Subscription;
|
||||
|
||||
/** Reference to the current focus ripple. */
|
||||
private _focusedRippleRef: RippleRef;
|
||||
|
||||
/** The parent radio group. May or may not be present. */
|
||||
radioGroup: MdRadioGroup;
|
||||
|
||||
@@ -321,6 +332,7 @@ export class MdRadioButton implements OnInit {
|
||||
constructor(@Optional() radioGroup: MdRadioGroup,
|
||||
private _elementRef: ElementRef,
|
||||
private _renderer: Renderer,
|
||||
private _focusOriginMonitor: FocusOriginMonitor,
|
||||
public radioDispatcher: UniqueSelectionDispatcher) {
|
||||
// Assertions. Ideally these should be stripped out by the compiler.
|
||||
// TODO(jelbourn): Assert that there's no name binding AND a parent radio group.
|
||||
@@ -340,7 +352,6 @@ export class MdRadioButton implements OnInit {
|
||||
}
|
||||
|
||||
/** Whether this radio button is checked. */
|
||||
@HostBinding('class.mat-radio-checked')
|
||||
@Input()
|
||||
get checked(): boolean {
|
||||
return this._checked;
|
||||
@@ -415,7 +426,6 @@ export class MdRadioButton implements OnInit {
|
||||
}
|
||||
|
||||
/** Whether the radio button is disabled. */
|
||||
@HostBinding('class.mat-radio-disabled')
|
||||
@Input()
|
||||
get disabled(): boolean {
|
||||
return this._disabled || (this.radioGroup != null && this.radioGroup.disabled);
|
||||
@@ -435,6 +445,25 @@ export class MdRadioButton implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this._focusOriginMonitorSubscription = this._focusOriginMonitor
|
||||
.monitor(this._inputElement.nativeElement, this._renderer, false)
|
||||
.subscribe(focusOrigin => {
|
||||
if (focusOrigin === 'keyboard' && !this._focusedRippleRef) {
|
||||
this._focusedRippleRef = this._ripple.launch(0, 0, { persistent: true, centered: true });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this._focusOriginMonitor.unmonitor(this._inputElement.nativeElement);
|
||||
|
||||
if (this._focusOriginMonitorSubscription) {
|
||||
this._focusOriginMonitorSubscription.unsubscribe();
|
||||
this._focusOriginMonitorSubscription = null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Dispatch change event with current value. */
|
||||
private _emitChangeEvent(): void {
|
||||
let event = new MdRadioChange();
|
||||
@@ -447,23 +476,16 @@ export class MdRadioButton implements OnInit {
|
||||
return this.disableRipple || this.disabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* We use a hidden native input field to handle changes to focus state via keyboard navigation,
|
||||
* with visual rendering done separately. The native element is kept in sync with the overall
|
||||
* state of the component.
|
||||
*/
|
||||
_onInputFocus() {
|
||||
this._isFocused = true;
|
||||
}
|
||||
|
||||
/** Focuses the radio button. */
|
||||
focus(): void {
|
||||
this._renderer.invokeElementMethod(this._inputElement.nativeElement, 'focus');
|
||||
this._onInputFocus();
|
||||
this._focusOriginMonitor.focusVia(this._inputElement.nativeElement, this._renderer, 'keyboard');
|
||||
}
|
||||
|
||||
_onInputBlur() {
|
||||
this._isFocused = false;
|
||||
if (this._focusedRippleRef) {
|
||||
this._focusedRippleRef.fadeOut();
|
||||
this._focusedRippleRef = null;
|
||||
}
|
||||
|
||||
if (this.radioGroup) {
|
||||
this.radioGroup._touch();
|
||||
@@ -503,13 +525,14 @@ export class MdRadioButton implements OnInit {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, MdRippleModule, CompatibilityModule],
|
||||
exports: [MdRadioGroup, MdRadioButton, CompatibilityModule],
|
||||
providers: [UNIQUE_SELECTION_DISPATCHER_PROVIDER, VIEWPORT_RULER_PROVIDER],
|
||||
providers: [UNIQUE_SELECTION_DISPATCHER_PROVIDER, VIEWPORT_RULER_PROVIDER, FocusOriginMonitor],
|
||||
declarations: [MdRadioGroup, MdRadioButton],
|
||||
})
|
||||
export class MdRadioModule {
|
||||
|
||||
Reference in New Issue
Block a user