chore: remove BooleanFieldValue (#1290)

This commit is contained in:
Jeremy Elbourn
2016-10-26 11:40:43 -07:00
committed by GitHub
parent 9eaf7e4122
commit c63b9f4c5f
14 changed files with 467 additions and 439 deletions
+1
View File
@@ -33,3 +33,4 @@
/libpeerconnection.log
npm-debug.log
testem.log
/.chrome
+3 -8
View File
@@ -15,13 +15,9 @@ import {
forwardRef,
AfterViewInit
} from '@angular/core';
import {
NG_VALUE_ACCESSOR,
ControlValueAccessor,
FormsModule,
} from '@angular/forms';
import {NG_VALUE_ACCESSOR, ControlValueAccessor, FormsModule} from '@angular/forms';
import {Observable} from 'rxjs/Observable';
import {BooleanFieldValue, MdUniqueSelectionDispatcher} from '../core';
import {MdUniqueSelectionDispatcher, coerceBooleanProperty} from '../core';
export type ToggleType = 'checkbox' | 'radio';
@@ -102,13 +98,12 @@ export class MdButtonToggleGroup implements AfterViewInit, ControlValueAccessor
}
@Input()
@BooleanFieldValue()
get disabled(): boolean {
return this._disabled;
}
set disabled(value) {
this._disabled = (value != null && value !== false) ? true : null;
this._disabled = coerceBooleanProperty(value);
}
@Input()
+6 -2
View File
@@ -10,7 +10,7 @@ import {
ModuleWithProviders,
} from '@angular/core';
import {CommonModule} from '@angular/common';
import {BooleanFieldValue, MdRippleModule} from '../core';
import {MdRippleModule, coerceBooleanProperty} from '../core';
// TODO(jelbourn): Make the `isMouseDown` stuff done with one global listener.
// TODO(kara): Convert attribute selectors to classes when attr maps become available
@@ -41,7 +41,11 @@ export class MdButton {
_isMouseDown: boolean = false;
/** Whether the ripple effect on click should be disabled. */
@Input() @BooleanFieldValue() disableRipple: boolean = false;
private _disableRipple: boolean = false;
@Input()
get disableRipple() { return this._disableRipple; }
set disableRipple(v) { this._disableRipple = coerceBooleanProperty(v); }
constructor(private _elementRef: ElementRef, private _renderer: Renderer) { }
+7 -2
View File
@@ -12,7 +12,8 @@ import {
ModuleWithProviders,
} from '@angular/core';
import {NG_VALUE_ACCESSOR, ControlValueAccessor} from '@angular/forms';
import {BooleanFieldValue} from '../core';
import {coerceBooleanProperty} from '../core/coersion/boolean-property';
/**
* Monotonically increasing integer used to auto-generate unique ids for checkbox components.
@@ -93,8 +94,12 @@ export class MdCheckbox implements ControlValueAccessor {
return `input-${this.id}`;
}
private _required: boolean;
/** Whether the checkbox is required or not. */
@Input() @BooleanFieldValue() required: boolean = false;
@Input()
get required(): boolean { return this._required; }
set required(value) { this._required = coerceBooleanProperty(value); }
/** Whether or not the checkbox should come before or after the label. */
@Input() align: 'start' | 'end' = 'start';
@@ -1,34 +0,0 @@
import {BooleanFieldValue} from './field-value';
describe('BooleanFieldValue', () => {
it('should work for null values', () => {
let x = new BooleanFieldValueTest();
x.field = null;
expect(x.field).toBe(false);
x.field = undefined;
expect(x.field).toBe(false);
});
it('should work for string values', () => {
let x = new BooleanFieldValueTest();
(<any>x).field = 'hello';
expect(x.field).toBe(true);
(<any>x).field = 'true';
expect(x.field).toBe(true);
(<any>x).field = '';
expect(x.field).toBe(true);
(<any>x).field = 'false';
expect(x.field).toBe(false);
});
});
class BooleanFieldValueTest {
@BooleanFieldValue() field: boolean;
}
-31
View File
@@ -1,31 +0,0 @@
/**
* Annotation Factory that allows HTML style boolean attributes. For example,
* a field declared like this:
* @Directive({ selector: 'component' }) class MyComponent {
* @Input() @BooleanFieldValueFactory() myField: boolean;
* }
*
* You could set it up this way:
* <component myField>
* or:
* <component myField="">
* @deprecated
*/
function booleanFieldValueFactory() {
return function booleanFieldValueMetadata(target: any, key: string): void {
const defaultValue = target[key];
const localKey = `__md_private_symbol_${key}`;
target[localKey] = defaultValue;
Object.defineProperty(target, key, {
get() { return (<any>this)[localKey]; },
set(value: boolean) {
(<any>this)[localKey] = value != null && `${value}` !== 'false';
}
});
};
}
export { booleanFieldValueFactory as BooleanFieldValue };
@@ -0,0 +1,48 @@
import {coerceBooleanProperty} from './boolean-property';
describe('coerceBooleanProperty', () => {
it('should coerce undefined to false', () => {
expect(coerceBooleanProperty(undefined)).toBe(false);
});
it('should coerce null to false', () => {
expect(coerceBooleanProperty(null)).toBe(false);
});
it('should coerce the empty string to true', () => {
expect(coerceBooleanProperty('')).toBe(true);
});
it('should coerce zero to true', () => {
expect(coerceBooleanProperty(0)).toBe(true);
});
it('should coerce the string "false" to false', () => {
expect(coerceBooleanProperty('false')).toBe(false);
});
it('should coerce the boolean false to false', () => {
expect(coerceBooleanProperty(false)).toBe(false);
});
it('should coerce the boolean true to true', () => {
expect(coerceBooleanProperty(true)).toBe(true);
});
it('should coerce the string "true" to true', () => {
expect(coerceBooleanProperty('true')).toBe(true);
});
it('should coerce an arbitrary string to true', () => {
expect(coerceBooleanProperty('pink')).toBe(true);
});
it('should coerce an object to true', () => {
expect(coerceBooleanProperty({})).toBe(true);
});
it('should coerce an array to true', () => {
expect(coerceBooleanProperty([])).toBe(true);
});
});
@@ -0,0 +1,4 @@
/** Coerces a data-bound value (typically a string) to a boolean. */
export function coerceBooleanProperty(value: any): boolean {
return value != null && `${value}` !== 'false';
}
+3 -3
View File
@@ -70,9 +70,6 @@ export {applyCssTransform} from './style/apply-transform';
// Error
export {MdError} from './errors/error';
// Annotations.
export {BooleanFieldValue} from './annotations/field-value';
// Misc
export {ComponentType} from './overlay/generic-component-type';
@@ -84,6 +81,9 @@ export * from './compatibility/style-compatibility';
// Animation
export * from './animation/animation';
// Coersion
export {coerceBooleanProperty} from './coersion/boolean-property';
@NgModule({
imports: [MdLineModule, RtlModule, MdRippleModule, PortalModule, OverlayModule, A11yModule],
+50 -15
View File
@@ -18,13 +18,9 @@ import {
ModuleWithProviders,
ViewEncapsulation,
} from '@angular/core';
import {
NG_VALUE_ACCESSOR,
ControlValueAccessor,
FormsModule,
} from '@angular/forms';
import {NG_VALUE_ACCESSOR, ControlValueAccessor, FormsModule} from '@angular/forms';
import {CommonModule} from '@angular/common';
import {BooleanFieldValue, MdError} from '../core';
import {MdError, coerceBooleanProperty} from '../core';
import {Observable} from 'rxjs/Observable';
@@ -118,9 +114,22 @@ export class MdInput implements ControlValueAccessor, AfterContentInit, OnChange
*/
@Input('aria-label') ariaLabel: string;
@Input('aria-labelledby') ariaLabelledBy: string;
@Input('aria-disabled') @BooleanFieldValue() ariaDisabled: boolean;
@Input('aria-required') @BooleanFieldValue() ariaRequired: boolean;
@Input('aria-invalid') @BooleanFieldValue() ariaInvalid: boolean;
private _ariaDisabled: boolean;
private _ariaRequired: boolean;
private _ariaInvalid: boolean;
@Input('aria-disabled')
get ariaDisabled(): boolean { return this._ariaDisabled; }
set ariaDisabled(value) { this._ariaDisabled = coerceBooleanProperty(value); }
@Input('aria-required')
get ariaRequired(): boolean { return this._ariaRequired; }
set ariaRequired(value) { this._ariaRequired = coerceBooleanProperty(value); }
@Input('aria-invalid')
get ariaInvalid(): boolean { return this._ariaInvalid; }
set ariaInvalid(value) { this._ariaInvalid = coerceBooleanProperty(value); }
/**
* Content directives.
@@ -141,14 +150,11 @@ export class MdInput implements ControlValueAccessor, AfterContentInit, OnChange
*/
@Input() align: 'start' | 'end' = 'start';
@Input() dividerColor: 'primary' | 'accent' | 'warn' = 'primary';
@Input() @BooleanFieldValue() floatingPlaceholder: boolean = true;
@Input() hintLabel: string = '';
@Input() autocomplete: string;
@Input() autocorrect: string;
@Input() autocapitalize: string;
@Input() @BooleanFieldValue() autofocus: boolean = false;
@Input() @BooleanFieldValue() disabled: boolean = false;
@Input() id: string = `md-input-${nextUniqueId++}`;
@Input() list: string = null;
@Input() max: string | number = null;
@@ -156,14 +162,43 @@ export class MdInput implements ControlValueAccessor, AfterContentInit, OnChange
@Input() min: string | number = null;
@Input() minlength: number = null;
@Input() placeholder: string = null;
@Input() @BooleanFieldValue() readonly: boolean = false;
@Input() @BooleanFieldValue() required: boolean = false;
@Input() @BooleanFieldValue() spellcheck: boolean = false;
@Input() step: number = null;
@Input() tabindex: number = null;
@Input() type: string = 'text';
@Input() name: string = null;
private _floatingPlaceholder: boolean = false;
private _autofocus: boolean = false;
private _disabled: boolean = false;
private _readonly: boolean = false;
private _required: boolean = false;
private _spellcheck: boolean = false;
@Input()
get floatingPlaceholder(): boolean { return this._floatingPlaceholder; }
set floatingPlaceholder(value) { this._floatingPlaceholder = coerceBooleanProperty(value); }
@Input()
get autofocus(): boolean { return this._autofocus; }
set autofocus(value) { this._autofocus = coerceBooleanProperty(value); }
@Input()
get disabled(): boolean { return this._disabled; }
set disabled(value) { this._disabled = coerceBooleanProperty(value); }
@Input()
get readonly(): boolean { return this._readonly; }
set readonly(value) { this._readonly = coerceBooleanProperty(value); }
@Input()
get required(): boolean { return this._required; }
set required(value) { this._required = coerceBooleanProperty(value); }
@Input()
get spellcheck(): boolean { return this._spellcheck; }
set spellcheck(value) { this._spellcheck = coerceBooleanProperty(value); }
private _blurEmitter: EventEmitter<FocusEvent> = new EventEmitter<FocusEvent>();
private _focusEmitter: EventEmitter<FocusEvent> = new EventEmitter<FocusEvent>();
+3 -4
View File
@@ -16,7 +16,8 @@ import {
ViewEncapsulation,
} from '@angular/core';
import {CommonModule} from '@angular/common';
import {Dir, MdError} from '../core';
import {Dir, MdError, coerceBooleanProperty} from '../core';
/** Exception thrown when two MdSidenav are matching the same side. */
export class MdDuplicatedSidenavError extends MdError {
@@ -79,9 +80,7 @@ export class MdSidenav {
@Input()
get opened(): boolean { return this._opened; }
set opened(v: boolean) {
// TODO(jelbourn): this coercion goes away when BooleanFieldValue is removed.
let booleanValue = v != null && `${v}` !== 'false';
this.toggle(booleanValue);
this.toggle(coerceBooleanProperty(v));
}
+329 -326
View File
@@ -1,326 +1,329 @@
import {
Component,
ElementRef,
Renderer,
forwardRef,
ChangeDetectionStrategy,
Input,
Output,
EventEmitter,
AfterContentInit,
NgModule,
ModuleWithProviders,
ViewEncapsulation,
} from '@angular/core';
import {HAMMER_GESTURE_CONFIG} from '@angular/platform-browser';
import {
FormsModule,
ControlValueAccessor,
NG_VALUE_ACCESSOR
} from '@angular/forms';
import {BooleanFieldValue, applyCssTransform} from '../core';
import {Observable} from 'rxjs/Observable';
import {MdGestureConfig} from '../core';
export const MD_SLIDE_TOGGLE_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => MdSlideToggle),
multi: true
};
// A simple change event emitted by the MdSlideToggle component.
export class MdSlideToggleChange {
source: MdSlideToggle;
checked: boolean;
}
// Increasing integer for generating unique ids for slide-toggle components.
let nextId = 0;
@Component({
moduleId: module.id,
selector: 'md-slide-toggle',
host: {
'[class.md-checked]': 'checked',
'[class.md-disabled]': 'disabled',
// This md-slide-toggle prefix will change, once the temporary ripple is removed.
'[class.md-slide-toggle-focused]': '_hasFocus',
'(mousedown)': '_setMousedown()'
},
templateUrl: 'slide-toggle.html',
styleUrls: ['slide-toggle.css'],
providers: [MD_SLIDE_TOGGLE_VALUE_ACCESSOR],
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class MdSlideToggle implements AfterContentInit, ControlValueAccessor {
private onChange = (_: any) => {};
private onTouched = () => {};
// A unique id for the slide-toggle. By default the id is auto-generated.
private _uniqueId = `md-slide-toggle-${++nextId}`;
private _checked: boolean = false;
private _color: string;
private _isMousedown: boolean = false;
private _slideRenderer: SlideToggleRenderer = null;
// Needs to be public to support AOT compilation (as host binding).
_hasFocus: boolean = false;
@Input() @BooleanFieldValue() disabled: boolean = false;
@Input() @BooleanFieldValue() required: boolean = false;
@Input() name: string = null;
@Input() id: string = this._uniqueId;
@Input() tabIndex: number = 0;
@Input() ariaLabel: string = null;
@Input() ariaLabelledby: string = null;
private _change: EventEmitter<MdSlideToggleChange> = new EventEmitter<MdSlideToggleChange>();
@Output() change: Observable<MdSlideToggleChange> = this._change.asObservable();
// Returns the unique id for the visual hidden input.
getInputId = () => `${this.id || this._uniqueId}-input`;
constructor(private _elementRef: ElementRef, private _renderer: Renderer) {}
/** TODO: internal */
ngAfterContentInit() {
this._slideRenderer = new SlideToggleRenderer(this._elementRef);
}
/**
* The onChangeEvent method will be also called on click.
* This is because everything for the slide-toggle is wrapped inside of a label,
* which triggers a onChange event on click.
*/
_onChangeEvent(event: Event) {
// We always have to stop propagation on the change event.
// Otherwise the change event, from the input element, will bubble up and
// emit its event object to the component's `change` output.
event.stopPropagation();
// Once a drag is currently in progress, we do not want to toggle the slide-toggle on a click.
if (!this.disabled && !this._slideRenderer.isDragging()) {
this.toggle();
// Emit our custom change event if the native input emitted one.
// It is important to only emit it, if the native input triggered one, because
// we don't want to trigger a change event, when the `checked` variable changes for example.
this._emitChangeEvent();
}
}
_onInputClick(event: Event) {
this.onTouched();
// We have to stop propagation for click events on the visual hidden input element.
// By default, when a user clicks on a label element, a generated click event will be
// dispatched on the associated input element. Since we are using a label element as our
// root container, the click event on the `slide-toggle` will be executed twice.
// The real click event will bubble up, and the generated click event also tries to bubble up.
// This will lead to multiple click events.
// Preventing bubbling for the second event will solve that issue.
event.stopPropagation();
}
_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);
}
_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;
}
}
_onInputBlur() {
this._hasFocus = false;
this.onTouched();
}
/**
* Implemented as part of ControlValueAccessor.
* TODO: internal
*/
writeValue(value: any): void {
this.checked = value;
}
/**
* Implemented as part of ControlValueAccessor.
* TODO: internal
*/
registerOnChange(fn: any): void {
this.onChange = fn;
}
/**
* Implemented as part of ControlValueAccessor.
* TODO: internal
*/
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
@Input()
get checked() {
return !!this._checked;
}
set checked(value) {
if (this.checked !== !!value) {
this._checked = value;
this.onChange(this._checked);
}
}
@Input()
get color(): string {
return this._color;
}
set color(value: string) {
this._updateColor(value);
}
toggle() {
this.checked = !this.checked;
}
private _updateColor(newColor: string) {
this._setElementColor(this._color, false);
this._setElementColor(newColor, true);
this._color = newColor;
}
private _setElementColor(color: string, isAdd: boolean) {
if (color != null && color != '') {
this._renderer.setElementClass(this._elementRef.nativeElement, `md-${color}`, isAdd);
}
}
/** Emits the change event to the `change` output EventEmitter */
private _emitChangeEvent() {
let event = new MdSlideToggleChange();
event.source = this;
event.checked = this.checked;
this._change.emit(event);
}
/** TODO: internal */
_onDragStart() {
if (!this.disabled) {
this._slideRenderer.startThumbDrag(this.checked);
}
}
/** TODO: internal */
_onDrag(event: HammerInput) {
if (this._slideRenderer.isDragging()) {
this._slideRenderer.updateThumbPosition(event.deltaX);
}
}
/** TODO: internal */
_onDragEnd() {
if (!this._slideRenderer.isDragging()) {
return;
}
// Notice that we have to stop outside of the current event handler,
// because otherwise the click event will be fired and will reset the new checked variable.
setTimeout(() => {
this.checked = this._slideRenderer.stopThumbDrag();
this._emitChangeEvent();
}, 0);
}
}
/**
* Renderer for the Slide Toggle component, which separates DOM modification in its own class
*/
class SlideToggleRenderer {
private _thumbEl: HTMLElement;
private _thumbBarEl: HTMLElement;
private _thumbBarWidth: number;
private _checked: boolean;
private _percentage: number;
constructor(private _elementRef: ElementRef) {
this._thumbEl = _elementRef.nativeElement.querySelector('.md-slide-toggle-thumb-container');
this._thumbBarEl = _elementRef.nativeElement.querySelector('.md-slide-toggle-bar');
}
/** Whether the slide-toggle is currently dragging. */
isDragging(): boolean {
return !!this._thumbBarWidth;
}
/** Initializes the drag of the slide-toggle. */
startThumbDrag(checked: boolean) {
if (!this.isDragging()) {
this._thumbBarWidth = this._thumbBarEl.clientWidth - this._thumbEl.clientWidth;
this._checked = checked;
this._thumbEl.classList.add('md-dragging');
}
}
/** Stops the current drag and returns the new checked value. */
stopThumbDrag(): boolean {
if (this.isDragging()) {
this._thumbBarWidth = null;
this._thumbEl.classList.remove('md-dragging');
applyCssTransform(this._thumbEl, '');
return this._percentage > 50;
}
}
/** Updates the thumb containers position from the specified distance. */
updateThumbPosition(distance: number) {
this._percentage = this._getThumbPercentage(distance);
applyCssTransform(this._thumbEl, `translate3d(${this._percentage}%, 0, 0)`);
}
/** Retrieves the percentage of thumb from the moved distance. */
private _getThumbPercentage(distance: number) {
let percentage = (distance / this._thumbBarWidth) * 100;
// When the toggle was initially checked, then we have to start the drag at the end.
if (this._checked) {
percentage += 100;
}
return Math.max(0, Math.min(percentage, 100));
}
}
@NgModule({
imports: [FormsModule],
exports: [MdSlideToggle],
declarations: [MdSlideToggle],
})
export class MdSlideToggleModule {
static forRoot(): ModuleWithProviders {
return {
ngModule: MdSlideToggleModule,
providers: [{provide: HAMMER_GESTURE_CONFIG, useClass: MdGestureConfig}]
};
}
}
import {
Component,
ElementRef,
Renderer,
forwardRef,
ChangeDetectionStrategy,
Input,
Output,
EventEmitter,
AfterContentInit,
NgModule,
ModuleWithProviders,
ViewEncapsulation,
} from '@angular/core';
import {HAMMER_GESTURE_CONFIG} from '@angular/platform-browser';
import {FormsModule, ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
import {applyCssTransform, coerceBooleanProperty, MdGestureConfig} from '../core';
import {Observable} from 'rxjs/Observable';
export const MD_SLIDE_TOGGLE_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => MdSlideToggle),
multi: true
};
// A simple change event emitted by the MdSlideToggle component.
export class MdSlideToggleChange {
source: MdSlideToggle;
checked: boolean;
}
// Increasing integer for generating unique ids for slide-toggle components.
let nextId = 0;
@Component({
moduleId: module.id,
selector: 'md-slide-toggle',
host: {
'[class.md-checked]': 'checked',
'[class.md-disabled]': 'disabled',
// This md-slide-toggle prefix will change, once the temporary ripple is removed.
'[class.md-slide-toggle-focused]': '_hasFocus',
'(mousedown)': '_setMousedown()'
},
templateUrl: 'slide-toggle.html',
styleUrls: ['slide-toggle.css'],
providers: [MD_SLIDE_TOGGLE_VALUE_ACCESSOR],
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class MdSlideToggle implements AfterContentInit, ControlValueAccessor {
private onChange = (_: any) => {};
private onTouched = () => {};
// A unique id for the slide-toggle. By default the id is auto-generated.
private _uniqueId = `md-slide-toggle-${++nextId}`;
private _checked: boolean = false;
private _color: string;
private _isMousedown: boolean = false;
private _slideRenderer: SlideToggleRenderer = null;
private _disabled: boolean = false;
private _required: boolean = false;
// Needs to be public to support AOT compilation (as host binding).
_hasFocus: boolean = false;
@Input() name: string = null;
@Input() id: string = this._uniqueId;
@Input() tabIndex: number = 0;
@Input() ariaLabel: string = null;
@Input() ariaLabelledby: string = null;
@Input()
get disabled(): boolean { return this._disabled; }
set disabled(value) { this._disabled = coerceBooleanProperty(value); }
@Input()
get required(): boolean { return this._required; }
set required(value) { this._required = coerceBooleanProperty(value); }
private _change: EventEmitter<MdSlideToggleChange> = new EventEmitter<MdSlideToggleChange>();
@Output() change: Observable<MdSlideToggleChange> = this._change.asObservable();
// Returns the unique id for the visual hidden input.
getInputId = () => `${this.id || this._uniqueId}-input`;
constructor(private _elementRef: ElementRef, private _renderer: Renderer) {}
/** TODO: internal */
ngAfterContentInit() {
this._slideRenderer = new SlideToggleRenderer(this._elementRef);
}
/**
* The onChangeEvent method will be also called on click.
* This is because everything for the slide-toggle is wrapped inside of a label,
* which triggers a onChange event on click.
*/
_onChangeEvent(event: Event) {
// We always have to stop propagation on the change event.
// Otherwise the change event, from the input element, will bubble up and
// emit its event object to the component's `change` output.
event.stopPropagation();
// Once a drag is currently in progress, we do not want to toggle the slide-toggle on a click.
if (!this.disabled && !this._slideRenderer.isDragging()) {
this.toggle();
// Emit our custom change event if the native input emitted one.
// It is important to only emit it, if the native input triggered one, because
// we don't want to trigger a change event, when the `checked` variable changes for example.
this._emitChangeEvent();
}
}
_onInputClick(event: Event) {
this.onTouched();
// We have to stop propagation for click events on the visual hidden input element.
// By default, when a user clicks on a label element, a generated click event will be
// dispatched on the associated input element. Since we are using a label element as our
// root container, the click event on the `slide-toggle` will be executed twice.
// The real click event will bubble up, and the generated click event also tries to bubble up.
// This will lead to multiple click events.
// Preventing bubbling for the second event will solve that issue.
event.stopPropagation();
}
_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);
}
_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;
}
}
_onInputBlur() {
this._hasFocus = false;
this.onTouched();
}
/**
* Implemented as part of ControlValueAccessor.
* TODO: internal
*/
writeValue(value: any): void {
this.checked = value;
}
/**
* Implemented as part of ControlValueAccessor.
* TODO: internal
*/
registerOnChange(fn: any): void {
this.onChange = fn;
}
/**
* Implemented as part of ControlValueAccessor.
* TODO: internal
*/
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
@Input()
get checked() {
return !!this._checked;
}
set checked(value) {
if (this.checked !== !!value) {
this._checked = value;
this.onChange(this._checked);
}
}
@Input()
get color(): string {
return this._color;
}
set color(value: string) {
this._updateColor(value);
}
toggle() {
this.checked = !this.checked;
}
private _updateColor(newColor: string) {
this._setElementColor(this._color, false);
this._setElementColor(newColor, true);
this._color = newColor;
}
private _setElementColor(color: string, isAdd: boolean) {
if (color != null && color != '') {
this._renderer.setElementClass(this._elementRef.nativeElement, `md-${color}`, isAdd);
}
}
/** Emits the change event to the `change` output EventEmitter */
private _emitChangeEvent() {
let event = new MdSlideToggleChange();
event.source = this;
event.checked = this.checked;
this._change.emit(event);
}
/** TODO: internal */
_onDragStart() {
if (!this.disabled) {
this._slideRenderer.startThumbDrag(this.checked);
}
}
/** TODO: internal */
_onDrag(event: HammerInput) {
if (this._slideRenderer.isDragging()) {
this._slideRenderer.updateThumbPosition(event.deltaX);
}
}
/** TODO: internal */
_onDragEnd() {
if (!this._slideRenderer.isDragging()) {
return;
}
// Notice that we have to stop outside of the current event handler,
// because otherwise the click event will be fired and will reset the new checked variable.
setTimeout(() => {
this.checked = this._slideRenderer.stopThumbDrag();
this._emitChangeEvent();
}, 0);
}
}
/**
* Renderer for the Slide Toggle component, which separates DOM modification in its own class
*/
class SlideToggleRenderer {
private _thumbEl: HTMLElement;
private _thumbBarEl: HTMLElement;
private _thumbBarWidth: number;
private _checked: boolean;
private _percentage: number;
constructor(private _elementRef: ElementRef) {
this._thumbEl = _elementRef.nativeElement.querySelector('.md-slide-toggle-thumb-container');
this._thumbBarEl = _elementRef.nativeElement.querySelector('.md-slide-toggle-bar');
}
/** Whether the slide-toggle is currently dragging. */
isDragging(): boolean {
return !!this._thumbBarWidth;
}
/** Initializes the drag of the slide-toggle. */
startThumbDrag(checked: boolean) {
if (!this.isDragging()) {
this._thumbBarWidth = this._thumbBarEl.clientWidth - this._thumbEl.clientWidth;
this._checked = checked;
this._thumbEl.classList.add('md-dragging');
}
}
/** Stops the current drag and returns the new checked value. */
stopThumbDrag(): boolean {
if (this.isDragging()) {
this._thumbBarWidth = null;
this._thumbEl.classList.remove('md-dragging');
applyCssTransform(this._thumbEl, '');
return this._percentage > 50;
}
}
/** Updates the thumb containers position from the specified distance. */
updateThumbPosition(distance: number) {
this._percentage = this._getThumbPercentage(distance);
applyCssTransform(this._thumbEl, `translate3d(${this._percentage}%, 0, 0)`);
}
/** Retrieves the percentage of thumb from the moved distance. */
private _getThumbPercentage(distance: number) {
let percentage = (distance / this._thumbBarWidth) * 100;
// When the toggle was initially checked, then we have to start the drag at the end.
if (this._checked) {
percentage += 100;
}
return Math.max(0, Math.min(percentage, 100));
}
}
@NgModule({
imports: [FormsModule],
exports: [MdSlideToggle],
declarations: [MdSlideToggle],
})
export class MdSlideToggleModule {
static forRoot(): ModuleWithProviders {
return {
ngModule: MdSlideToggleModule,
providers: [{provide: HAMMER_GESTURE_CONFIG, useClass: MdGestureConfig}]
};
}
}
+10 -10
View File
@@ -9,13 +9,9 @@ import {
AfterContentInit,
forwardRef,
} from '@angular/core';
import {
NG_VALUE_ACCESSOR,
ControlValueAccessor,
FormsModule,
} from '@angular/forms';
import {NG_VALUE_ACCESSOR, ControlValueAccessor, FormsModule} from '@angular/forms';
import {HAMMER_GESTURE_CONFIG} from '@angular/platform-browser';
import {BooleanFieldValue, MdGestureConfig, applyCssTransform} from '../core';
import {MdGestureConfig, applyCssTransform, coerceBooleanProperty} from '../core';
import {Input as HammerInput} from 'hammerjs';
/**
@@ -58,16 +54,20 @@ export class MdSlider implements AfterContentInit, ControlValueAccessor {
/** The dimensions of the slider. */
private _sliderDimensions: ClientRect = null;
private _disabled: boolean = false;
@Input()
@BooleanFieldValue()
@HostBinding('class.md-slider-disabled')
@HostBinding('attr.aria-disabled')
disabled: boolean = false;
get disabled(): boolean { return this._disabled; }
set disabled(value) { this._disabled = coerceBooleanProperty(value); }
/** Whether or not to show the thumb label. */
private _thumbLabel: boolean = false;
@Input('thumb-label')
@BooleanFieldValue()
thumbLabel: boolean = false;
get thumbLabel(): boolean { return this._thumbLabel; }
set thumbLabel(value) { this._thumbLabel = coerceBooleanProperty(value); }
/** The miniumum value that the slider can have. */
private _min: number = 0;
+3 -4
View File
@@ -13,14 +13,14 @@ import {
ContentChildren
} from '@angular/core';
import {CommonModule} from '@angular/common';
import {PortalModule} from '../core';
import {PortalModule, RIGHT_ARROW, LEFT_ARROW, ENTER, coerceBooleanProperty} from '../core';
import {MdTabLabel} from './tab-label';
import {MdTabContent} from './tab-content';
import {MdTabLabelWrapper} from './tab-label-wrapper';
import {MdInkBar} from './ink-bar';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import {RIGHT_ARROW, LEFT_ARROW, ENTER} from '../core';
/** Used to generate unique ID's for each tab component */
let nextId = 0;
@@ -38,11 +38,10 @@ export class MdTab {
@ContentChild(MdTabLabel) label: MdTabLabel;
@ContentChild(MdTabContent) content: MdTabContent;
// TODO: Replace this when BooleanFieldValue is removed.
private _disabled = false;
@Input('disabled')
set disabled(value: boolean) {
this._disabled = (value != null && `${value}` !== 'false');
this._disabled = coerceBooleanProperty(value);
}
get disabled(): boolean {
return this._disabled;