diff --git a/src/components/input/README.md b/src/components/input/README.md
new file mode 100644
index 000000000000..a3faa9067bc4
--- /dev/null
+++ b/src/components/input/README.md
@@ -0,0 +1,42 @@
+# mdInput
+
+Inputs are the basic input component of Material 2. The spec can be found [here](https://www.google.com/design/spec/components/text-fields.html).
+
+### Screenshots
+
+
+
+## Type
+
+At the time of writing this README, the `[type]` attribute is copied to the actual `` element in the ``.
+
+The valid `type` attribute values are any supported by your browser, with the exception of `file`, `checkbox` and `radio`. File inputs aren't supported for now, while check boxes and radio buttons have their own components.
+
+## Prefix and Suffix
+
+You can include HTML before, and after the input tag, as prefix or suffix. It will be underlined as per the Material specification, and clicking it will focus the input.
+
+To add a prefix, use the `md-prefix` attribute on the element. Similarly, to add a suffix, use the `md-suffix` attribute. For example, in a template:
+
+```html
+
+ $
+ .00
+
+```
+
+Will result in this:
+
+!!!! INSERT SCREENSHOT HERE.
+
+
+## Hint Labels
+
+Hint labels are the labels that shows the underline. You can have up to two hint labels; one on the `start` of the line (left in an LTR language, right in RTL), or one on the `end`.
+
+You specify a hint-label in one of two ways; either using the `hintLabel` attribute, or using an `` directive in the ``, which takes an `align` attribute containing the side. The attribute version is assumed to be at the `start`.
+
+Specifying a side twice will result in an exception during initialization.
+
+## Divider Color
+
diff --git a/src/components/input/input.html b/src/components/input/input.html
new file mode 100644
index 000000000000..82d460776bef
--- /dev/null
+++ b/src/components/input/input.html
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{hintLabel}}
+
+
diff --git a/src/components/input/input.scss b/src/components/input/input.scss
new file mode 100644
index 000000000000..433e0883de18
--- /dev/null
+++ b/src/components/input/input.scss
@@ -0,0 +1,184 @@
+@import 'default-theme';
+@import 'mixins';
+@import 'variables';
+
+
+// Placeholder colors. Required is used for the `*` star shown in the placeholder.
+$md-input-placeholder-color: md-color($md-foreground, hint-text);
+$md-input-floating-placeholder-color: md-color($md-primary);
+$md-input-required-placeholder-color: md-color($md-accent);
+
+// Underline colors.
+$md-input-underline-color: md-color($md-foreground, hint-text);
+$md-input-underline-color-accent: md-color($md-accent);
+$md-input-underline-disabled-color: md-color($md-foreground, hint-text);
+$md-input-underline-focused-color: md-color($md-primary);
+
+// Gradient for showing the dashed line when the input is disabled.
+$md-input-underline-disabled-background-image: linear-gradient(to right,
+ rgba(0,0,0,0.26) 0%, rgba(0,0,0,0.26) 33%, transparent 0%);
+
+:host {
+ display: inline-block;
+ position: relative;
+ font-family: $md-font-family;
+
+ // Global wrapper. We need to apply margin to the element for spacing, but
+ // cannot apply it to the host element directly.
+ .md-input-wrapper {
+ margin: 16px 0;
+ }
+
+ // We use a table layout to baseline align the prefix and suffix classes.
+ // The underline is outside of it so it can cover all of the elements under
+ // this table.
+ // Flex does not respect the baseline. What we really want is akin to a table
+ // as want an inline-block where elements don't wrap.
+ .md-input-table {
+ display: inline-table;
+ flex-flow: column;
+ vertical-align: bottom;
+ width: 100%;
+
+ & > * {
+ display: table-cell;
+ }
+ }
+
+ // The Input element proper.
+ .md-input-element {
+ // Font needs to be inherited, because by default has a system font.
+ font: inherit;
+
+ // By default, has a padding, border, outline and a default width.
+ border: none;
+ outline: none;
+ padding: 0;
+ width: 100%;
+
+ &.md-end {
+ text-align: right;
+ }
+ }
+
+ // The placeholder label. This is invisible unless it is. The logic to show it is
+ // basically `empty || (float && (!empty || focused))`. Float is dependent on the
+ // `floatingPlaceholder` input.
+ .md-input-placeholder {
+ position: absolute;
+ visibility: hidden;
+ font-size: 100%;
+ pointer-events: none; // We shouldn't catch mouse events (let them through).
+ color: $md-input-placeholder-color;
+ z-index: 1;
+
+ width: 100%;
+
+ transform: translateY(0);
+ transform-origin: bottom left;
+ transition: transform $swift-ease-out-duration $swift-ease-out-timing-function,
+ scale $swift-ease-out-duration $swift-ease-out-timing-function,
+ color $swift-ease-out-duration $swift-ease-out-timing-function;
+
+ &.md-empty {
+ visibility: visible;
+ cursor: text;
+ }
+
+ // Show the placeholder above the input when it's not empty, or focused.
+ &.md-float:not(.md-empty), &.md-float.md-focused {
+ visibility: visible;
+ padding-bottom: 5px;
+ transform: translateY(-100%) scale(0.75);
+
+ .md-placeholder-required {
+ color: $md-input-required-placeholder-color;
+ }
+ }
+
+ // :focus is applied to the input, but we apply md-focused to the other elements
+ // that need to listen to it.
+ &.md-focused {
+ color: $md-input-floating-placeholder-color;
+
+ &.md-accent {
+ color: $md-input-underline-color-accent;
+ }
+ }
+ }
+
+ // The underline is what's shown under the input, its prefix and its suffix.
+ // The ripple is the blue animation coming on top of it.
+ .md-input-underline {
+ position: absolute;
+ height: 1px;
+ width: 100%;
+ margin-top: 4px;
+ border-top: 1px solid $md-input-underline-color;
+
+ &.md-disabled {
+ border-top: 0;
+ background-image: $md-input-underline-disabled-background-image;
+ background-position: 0;
+ background-size: 4px 1px;
+ background-repeat: repeat-x;
+ }
+
+ .md-input-ripple {
+ position: absolute;
+ height: 2px;
+ z-index: 1;
+ background-color: $md-input-underline-focused-color;
+ top: -1px;
+ width: 100%;
+ transform-origin: top;
+ opacity: 0;
+ transform: scaleY(0);
+ transition: transform $swift-ease-out-duration $swift-ease-out-timing-function,
+ opacity $swift-ease-out-duration $swift-ease-out-timing-function;
+
+ &.md-accent {
+ background-color: $md-input-underline-color-accent;
+ }
+
+ &.md-focused {
+ opacity: 1;
+ transform: scaleY(1);
+ }
+ }
+ }
+
+ // The hint is shown below the underline. There can be more than one; one at the start
+ // and one at the end.
+ .md-hint {
+ position: absolute;
+ font-size: 75%;
+ bottom: -0.5em;
+
+ &.md-right {
+ right: 0;
+ }
+ }
+}
+
+
+// RTL support.
+:host-context([dir="rtl"]) {
+ .md-input-placeholder {
+ transform-origin: bottom right;
+ }
+
+ .md-input-element.md-end {
+ text-align: left;
+ }
+
+ .md-hint {
+ right: 0;
+ left: auto;
+
+ &.md-right {
+ right: auto;
+ left: 0;
+ }
+ }
+}
diff --git a/src/components/input/input.spec.ts b/src/components/input/input.spec.ts
new file mode 100644
index 000000000000..1cbd7268eeca
--- /dev/null
+++ b/src/components/input/input.spec.ts
@@ -0,0 +1,355 @@
+import {
+ fakeAsync,
+ inject,
+ ComponentFixture,
+ TestComponentBuilder,
+ injectAsync,
+ tick,
+} from 'angular2/testing';
+import {Component} from 'angular2/core';
+import {By} from 'angular2/platform/browser';
+import {
+ it,
+ expect,
+ beforeEach,
+} from '../../core/facade/testing';
+import {
+ MdInput,
+ MdInputDuplicatedHintException,
+ MD_INPUT_DIRECTIVES,
+ MdInputPlaceholderConflictException
+} from './input';
+
+
+export function main() {
+ describe('MdInput', function () {
+ var builder: TestComponentBuilder;
+
+ beforeEach(inject([TestComponentBuilder], function (tcb: TestComponentBuilder) {
+ builder = tcb;
+ }));
+
+ it('creates a native element', injectAsync([], () => {
+ return builder.createAsync(MdInputBaseTestController)
+ .then((fixture) => {
+ fixture.detectChanges();
+ expect(fixture.debugElement.query(By.css('input'))).toBeTruthy();
+ });
+ }));
+
+ it('support ngModel', injectAsync([], () => {
+ return builder.createAsync(MdInputBaseTestController)
+ .then((fixture) => {
+ fixture.detectChanges();
+ fakeAsync(() => {
+ let instance = fixture.componentInstance;
+ let component = fixture.debugElement.query(By.directive(MdInput)).componentInstance;
+ let el: HTMLInputElement = fixture.debugElement.query(By.css('input')).nativeElement;
+
+ instance.model = 'hello';
+ fixture.detectChanges();
+ tick();
+ expect(el.value).toEqual('hello');
+
+ component.value = 'world';
+ fixture.detectChanges();
+ tick();
+ expect(el.value).toEqual('world');
+ })();
+ });
+ }));
+
+ it('counts characters', injectAsync([], () => {
+ return builder.createAsync(MdInputBaseTestController)
+ .then((fixture) => {
+ let instance = fixture.componentInstance;
+ fixture.detectChanges();
+ let inputInstance = fixture.debugElement.query(By.directive(MdInput)).componentInstance;
+ expect(inputInstance.characterCount).toEqual(0);
+
+ instance.model = 'hello';
+ fixture.detectChanges();
+ expect(inputInstance.characterCount).toEqual(5);
+ });
+ }));
+
+ it('copies aria attributes to the inner input', injectAsync([], () => {
+ return builder.createAsync(MdInputAriaTestController)
+ .then((fixture) => {
+ let instance = fixture.componentInstance;
+ fixture.detectChanges();
+ let el: HTMLInputElement = fixture.debugElement.query(By.css('input')).nativeElement;
+ expect(el.getAttribute('aria-label')).toEqual('label');
+ instance.ariaLabel = 'label 2';
+ fixture.detectChanges();
+ expect(el.getAttribute('aria-label')).toEqual('label 2');
+
+ expect(el.getAttribute('aria-disabled')).toBeTruthy();
+ });
+ }));
+
+ it('validates there\'s only one hint label per side', injectAsync([], () => {
+ return builder.createAsync(MdInputInvalidHintTestController)
+ .then((fixture: ComponentFixture) => {
+ fakeAsync(() => {
+ expect(() => fixture.detectChanges())
+ .toThrow(new MdInputDuplicatedHintException('start'));
+ })();
+ });
+ }));
+
+ it(`validates there's only one hint label per side (attribute)`, injectAsync([], () => {
+ return builder.createAsync(MdInputInvalidHint2TestController)
+ .then((fixture: ComponentFixture) => {
+ fakeAsync(() => {
+ expect(() => fixture.detectChanges())
+ .toThrow(new MdInputDuplicatedHintException('start'));
+ })();
+ });
+ }));
+
+ it('validates there\'s only one placeholder', injectAsync([], () => {
+ return builder.createAsync(MdInputInvalidPlaceholderTestController)
+ .then((fixture: ComponentFixture) => {
+ fakeAsync(() => {
+ expect(() => fixture.detectChanges())
+ .toThrow(new MdInputPlaceholderConflictException());
+ })();
+ });
+ }));
+
+ it('validates the type', injectAsync([], () => {
+ return builder.createAsync(MdInputInvalidTypeTestController)
+ .then((fixture: ComponentFixture) => {
+ fakeAsync(() => {
+ // Technically this throws during the OnChanges detection phase,
+ // so the error is really a ChangeDetectionError and it becomes
+ // hard to build a full exception to compare with.
+ // We just check for any exception in this case.
+ expect(() => fixture.detectChanges())
+ .toThrow(/* new MdInputUnsupportedTypeException('file') */);
+ })();
+ });
+ }));
+
+ it('supports hint labels attribute', injectAsync([], () => {
+ return builder.createAsync(MdInputHintLabelTestController)
+ .then((fixture: ComponentFixture) => {
+ fakeAsync(() => {
+ fixture.detectChanges();
+
+ // If the hint label is empty, expect no label.
+ expect(fixture.debugElement.query(By.css('.md-hint'))).toBeNull();
+
+ fixture.componentInstance.label = 'label';
+ fixture.detectChanges();
+ expect(fixture.debugElement.query(By.css('.md-hint'))).not.toBeNull();
+ })();
+ });
+ }));
+
+ it('supports hint labels elements', injectAsync([], () => {
+ return builder.createAsync(MdInputHintLabel2TestController)
+ .then((fixture: ComponentFixture) => {
+ fakeAsync(() => {
+ fixture.detectChanges();
+
+ // In this case, we should have an empty .
+ let el = fixture.debugElement.query(By.css('md-hint')).nativeElement;
+ expect(el.textContent).toBeFalsy();
+
+ fixture.componentInstance.label = 'label';
+ fixture.detectChanges();
+ el = fixture.debugElement.query(By.css('md-hint')).nativeElement;
+ expect(el.textContent).toBe('label');
+ })();
+ });
+ }));
+
+ it('supports placeholder attribute', injectAsync([], () => {
+ return builder.createAsync(MdInputPlaceholderAttrTestComponent)
+ .then((fixture: ComponentFixture) => {
+ fakeAsync(() => {
+ fixture.detectChanges();
+
+ let el = fixture.debugElement.query(By.css('label'));
+ expect(el).toBeNull();
+
+ fixture.componentInstance.placeholder = 'Other placeholder';
+ fixture.detectChanges();
+ el = fixture.debugElement.query(By.css('label'));
+ expect(el).not.toBeNull();
+ expect(el.nativeElement.textContent).toMatch('Other placeholder');
+ expect(el.nativeElement.textContent).not.toMatch(/\*/g);
+ })();
+ });
+ }));
+
+ it('supports placeholder element', injectAsync([], () => {
+ return builder.createAsync(MdInputPlaceholderElementTestComponent)
+ .then((fixture: ComponentFixture) => {
+ fakeAsync(() => {
+ fixture.detectChanges();
+
+ let el = fixture.debugElement.query(By.css('label'));
+ expect(el).not.toBeNull();
+ expect(el.nativeElement.textContent).toMatch('Default Placeholder');
+
+ fixture.componentInstance.placeholder = 'Other placeholder';
+ fixture.detectChanges();
+ el = fixture.debugElement.query(By.css('label'));
+ expect(el).not.toBeNull();
+ expect(el.nativeElement.textContent).toMatch('Other placeholder');
+ expect(el.nativeElement.textContent).not.toMatch(/\*/g);
+ })();
+ });
+ }));
+
+ it('supports placeholder required star', injectAsync([], () => {
+ return builder.createAsync(MdInputPlaceholderRequiredTestComponent)
+ .then((fixture: ComponentFixture) => {
+ fakeAsync(() => {
+ fixture.detectChanges();
+
+ let el = fixture.debugElement.query(By.css('label'));
+ expect(el).not.toBeNull();
+ expect(el.nativeElement.textContent).toMatch(/hello\s+\*/g);
+ })();
+ });
+ }));
+ });
+}
+
+@Component({
+ selector: 'test-input-controller',
+ template: `
+
+
+ `,
+ directives: [MD_INPUT_DIRECTIVES]
+})
+class MdInputPlaceholderRequiredTestComponent {
+}
+
+@Component({
+ selector: 'test-input-controller',
+ template: `
+
+ {{placeholder}}
+
+ `,
+ directives: [MD_INPUT_DIRECTIVES]
+})
+class MdInputPlaceholderElementTestComponent {
+ placeholder: string = 'Default Placeholder';
+}
+
+@Component({
+ selector: 'test-input-controller',
+ template: `
+
+
+ `,
+ directives: [MD_INPUT_DIRECTIVES]
+})
+class MdInputPlaceholderAttrTestComponent {
+ placeholder: string = '';
+}
+
+@Component({
+ selector: 'test-input-controller',
+ template: `
+
+ {{label}}
+
+ `,
+ directives: [MD_INPUT_DIRECTIVES]
+})
+class MdInputHintLabel2TestController {
+ label: string = '';
+}
+
+@Component({
+ selector: 'test-input-controller',
+ template: `
+
+
+ `,
+ directives: [MD_INPUT_DIRECTIVES]
+})
+class MdInputHintLabelTestController {
+ label: string = '';
+}
+
+@Component({
+ selector: 'test-input-controller',
+ template: `
+
+
+ `,
+ directives: [MD_INPUT_DIRECTIVES]
+})
+class MdInputInvalidTypeTestController {
+}
+
+@Component({
+ selector: 'test-input-controller',
+ template: `
+
+ World
+
+ `,
+ directives: [MD_INPUT_DIRECTIVES]
+})
+class MdInputInvalidPlaceholderTestController {
+}
+
+@Component({
+ selector: 'test-input-controller',
+ template: `
+
+ World
+
+ `,
+ directives: [MD_INPUT_DIRECTIVES]
+})
+class MdInputInvalidHint2TestController {
+}
+
+@Component({
+ selector: 'test-input-controller',
+ template: `
+
+ Hello
+ World
+
+ `,
+ directives: [MD_INPUT_DIRECTIVES]
+})
+class MdInputInvalidHintTestController {
+}
+
+@Component({
+ selector: 'test-input-controller',
+ template: `
+
+
+ `,
+ directives: [MdInput]
+})
+class MdInputBaseTestController {
+ model: any = '';
+}
+
+@Component({
+ selector: 'test-input-controller',
+ template: `
+
+
+ `,
+ directives: [MdInput]
+})
+class MdInputAriaTestController {
+ ariaLabel: string = 'label';
+ ariaDisabled: boolean = true;
+}
diff --git a/src/components/input/input.ts b/src/components/input/input.ts
new file mode 100644
index 000000000000..025999e46c57
--- /dev/null
+++ b/src/components/input/input.ts
@@ -0,0 +1,241 @@
+import {
+ forwardRef,
+ Component,
+ HostBinding,
+ Input,
+ Provider,
+ Directive,
+ AfterContentInit,
+ ContentChild,
+ SimpleChange,
+ ContentChildren,
+ QueryList,
+ OnChanges,
+} from 'angular2/core';
+import {CONST_EXPR, noop} from 'angular2/src/facade/lang';
+import {
+ NG_VALUE_ACCESSOR,
+ ControlValueAccessor
+} from 'angular2/src/common/forms/directives/control_value_accessor';
+import {BaseException} from 'angular2/src/facade/exceptions';
+import {BooleanFieldValue} from '../../core/annotations/field-value';
+import {OneOf} from '../../core/annotations/one-of';
+
+
+const MD_INPUT_CONTROL_VALUE_ACCESSOR = CONST_EXPR(new Provider(
+ NG_VALUE_ACCESSOR, {
+ useExisting: forwardRef(() => MdInput),
+ multi: true
+ }));
+
+// Invalid input type. Using one of these will throw an MdInputUnsupportedTypeException.
+const MD_INPUT_INVALID_INPUT_TYPE = CONST_EXPR([
+ 'file',
+ 'radio',
+ 'checkbox',
+]);
+
+
+let nextUniqueId = 0;
+
+
+export class MdInputPlaceholderConflictException extends BaseException {
+ constructor() {
+ super('Placeholder attribute and child element were both specified.');
+ }
+}
+
+export class MdInputUnsupportedTypeException extends BaseException {
+ constructor(type: string) {
+ super(`Input type "${type}" isn't supported by md-input.`);
+ }
+}
+
+export class MdInputDuplicatedHintException extends BaseException {
+ constructor(align: string) {
+ super(`A hint was already declared for 'align="${align}"'.`);
+ }
+}
+
+
+
+/**
+ * The placeholder directive. The content can declare this to implement more
+ * complex placeholders.
+ */
+@Directive({
+ selector: 'md-placeholder'
+})
+export class MdPlaceholder {}
+
+
+/**
+ * The hint directive, used to tag content as hint labels (going under the input).
+ */
+@Directive({
+ selector: 'md-hint',
+ host: {
+ '[class.md-right]': 'align == "end"',
+ '[class.md-hint]': 'true'
+ }
+})
+export class MdHint {
+ // Whether to align the hint label at the start or end of the line.
+ @Input() @OneOf(['start', 'end']) align: string;
+}
+
+
+/**
+ * Component that represents a text input. It encapsulates the HTMLElement and
+ * improve on its behaviour, along with styling it according to the Material Design.
+ */
+@Component({
+ selector: 'md-input',
+ templateUrl: 'components/input/input.html',
+ styleUrls: ['components/input/input.css'],
+ providers: [MD_INPUT_CONTROL_VALUE_ACCESSOR],
+})
+export class MdInput implements ControlValueAccessor, AfterContentInit, OnChanges {
+ private _focused: boolean = false;
+ private _value: any = '';
+
+ /** Callback registered via registerOnTouched (ControlValueAccessor) */
+ private _onTouchedCallback: () => void = noop;
+ /** Callback registered via registerOnChange (ControlValueAccessor) */
+ private _onChangeCallback: (_: any) => void = noop;
+
+ /**
+ * Aria related inputs.
+ */
+ @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;
+
+ /**
+ * Content directives.
+ */
+ @ContentChild(MdPlaceholder) private _placeholderChild: MdPlaceholder;
+ @ContentChildren(MdHint) private _hintChildren: QueryList;
+
+ /** Readonly properties. */
+ get focused() { return this._focused; }
+ get empty() { return this._value == null || this._value === ''; }
+ get characterCount(): number {
+ return this.empty ? 0 : ('' + this._value).length;
+ }
+
+ /**
+ * Bindings.
+ */
+ @Input() @OneOf(['start', 'end']) align: string = 'start';
+ @Input() @BooleanFieldValue() disabled: boolean = false;
+ @Input() @OneOf(['primary', 'accent']) dividerColor: string = 'primary';
+ @Input() @BooleanFieldValue() floatingPlaceholder: boolean = true;
+ @Input() hintLabel: string = '';
+ @Input() id: string = `md-input-${nextUniqueId++}`;
+ @Input() maxLength: number = -1;
+ @Input() placeholder: string;
+ @Input() @BooleanFieldValue() required: boolean = false;
+ @Input() type: string = 'text';
+
+ get value(): any { return this._value; };
+ @Input() set value(v: any) {
+ if (v !== this._value) {
+ this._value = v;
+ this._onChangeCallback(v);
+ }
+ }
+
+ // This is to remove the `align` property of the `md-input` itself. Otherwise HTML5
+ // might place it as RTL when we don't want to. We still want to use `align` as an
+ // Input though, so we use HostBinding.
+ @HostBinding('attr.align') private get _align(): any { return null; }
+
+ /** @internal */
+ onFocus() {
+ this._focused = true;
+ }
+ /** @internal */
+ onBlur() {
+ this._focused = false;
+ this._onTouchedCallback();
+ }
+
+ /** @internal */
+ hasPlaceholder(): boolean {
+ return !!this.placeholder || this._placeholderChild != null;
+ }
+
+ /** Implemented as part of ControlValueAccessor. */
+ writeValue(value: any) {
+ this._value = value;
+ }
+
+ /** Implemented as part of ControlValueAccessor. */
+ registerOnChange(fn: any) {
+ this._onChangeCallback = fn;
+ }
+
+ /** Implemented as part of ControlValueAccessor. */
+ registerOnTouched(fn: any) {
+ this._onTouchedCallback = fn;
+ }
+
+ ngAfterContentInit() {
+ this._validateConstraints();
+
+ // Trigger validation when the hint children change.
+ this._hintChildren.changes.subscribe(() => {
+ this._validateConstraints();
+ });
+ }
+
+ ngOnChanges(changes: {[key: string]: SimpleChange}) {
+ this._validateConstraints();
+ }
+
+ /**
+ * Ensure that all constraints defined by the API are validated, or throw errors otherwise.
+ * Constraints for now:
+ * - placeholder attribute and are mutually exclusive.
+ * - type attribute is not one of the forbidden types (see constant at the top).
+ * - Maximum one of each `` alignment specified, with the attribute being
+ * considered as align="start".
+ * @private
+ */
+ private _validateConstraints() {
+ if (this.placeholder != '' && this.placeholder != null && this._placeholderChild != null) {
+ throw new MdInputPlaceholderConflictException();
+ }
+ if (MD_INPUT_INVALID_INPUT_TYPE.indexOf(this.type) != -1) {
+ throw new MdInputUnsupportedTypeException(this.type);
+ }
+
+ if (this._hintChildren) {
+ // Validate the hint labels.
+ let startHint: MdHint = null;
+ let endHint: MdHint = null;
+ this._hintChildren.forEach((hint: MdHint) => {
+ if (hint.align == 'start') {
+ if (startHint || this.hintLabel) {
+ throw new MdInputDuplicatedHintException('start');
+ }
+ startHint = hint;
+ } else if (hint.align == 'end') {
+ if (endHint) {
+ throw new MdInputDuplicatedHintException('end');
+ }
+ endHint = hint;
+ }
+ });
+ }
+ }
+}
+
+export const MD_INPUT_DIRECTIVES: any[] = CONST_EXPR([
+ MdPlaceholder,
+ MdInput,
+ MdHint,
+]);
diff --git a/src/core/annotations/field-value.dart b/src/core/annotations/field-value.dart
new file mode 100644
index 000000000000..56329b4dfc8e
--- /dev/null
+++ b/src/core/annotations/field-value.dart
@@ -0,0 +1,6 @@
+/**
+ * Annotation for a @BooleanFieldValue() property.
+ */
+class BooleanFieldValue {
+ const BooleanFieldValue();
+}
diff --git a/src/core/annotations/field-value.spec.dart b/src/core/annotations/field-value.spec.dart
new file mode 100644
index 000000000000..8b137891791f
--- /dev/null
+++ b/src/core/annotations/field-value.spec.dart
@@ -0,0 +1 @@
+
diff --git a/src/core/annotations/field-value.spec.ts b/src/core/annotations/field-value.spec.ts
new file mode 100644
index 000000000000..55bcc5d25c0a
--- /dev/null
+++ b/src/core/annotations/field-value.spec.ts
@@ -0,0 +1,34 @@
+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();
+
+ (x).field = 'hello';
+ expect(x.field).toBe(true);
+
+ (x).field = 'true';
+ expect(x.field).toBe(true);
+
+ (x).field = '';
+ expect(x.field).toBe(true);
+
+ (x).field = 'false';
+ expect(x.field).toBe(false);
+ });
+});
+
+
+class BooleanFieldValueTest {
+ @BooleanFieldValue() field: boolean;
+}
diff --git a/src/core/annotations/field-value.ts b/src/core/annotations/field-value.ts
new file mode 100644
index 000000000000..f587ed3cfa83
--- /dev/null
+++ b/src/core/annotations/field-value.ts
@@ -0,0 +1,38 @@
+import {isPresent} from 'angular2/src/facade/lang';
+
+
+declare var Symbol: any;
+
+
+/**
+ * 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:
+ *
+ * or:
+ *
+ */
+function booleanFieldValueFactory() {
+ return function booleanFieldValueMetadata(target: any, key: string): void {
+ const defaultValue = target[key];
+
+ // Use a fallback if Symbol isn't available.
+ const localKey = isPresent(Symbol) ? Symbol(key) : `__md_private_symbol_${key}`;
+ target[localKey] = defaultValue;
+
+ Object.defineProperty(target, key, {
+ get() { return this[localKey]; },
+ set(value: boolean) {
+ this[localKey] = isPresent(value) && value !== null && String(value) != 'false';
+ }
+ });
+ };
+}
+
+
+export { booleanFieldValueFactory as BooleanFieldValue };
diff --git a/src/demo-app/demo-app.html b/src/demo-app/demo-app.html
index 6abc9c03925d..9ce3d2de0ea1 100644
--- a/src/demo-app/demo-app.html
+++ b/src/demo-app/demo-app.html
@@ -8,6 +8,7 @@