feat(): md-input

This commit is contained in:
Hans Larsen
2016-03-13 12:47:26 -07:00
parent 4f78c8c578
commit 74d9bc0bfa
14 changed files with 1104 additions and 0 deletions
+42
View File
@@ -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 `<input>` element in the `<md-input>`.
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
<md-input type="number" placeholder="amount">
<span md-prefix>$</span>
<span md-suffix>.00</span>
</md-input>
```
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 `<md-hint>` directive in the `<md-input>`, 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
+47
View File
@@ -0,0 +1,47 @@
<div class="md-input-wrapper" (click)="input.focus()">
<div class="md-input-table">
<div class="md-input-prefix"><ng-content select="[md-prefix]"></ng-content></div>
<label class="md-input-placeholder"
[attr.for]="id"
[class.md-empty]="empty"
[class.md-focused]="focused"
[class.md-float]="floatingPlaceholder"
[class.md-accent]="dividerColor == 'accent'"
*ngIf="hasPlaceholder()">
<ng-content select="md-placeholder"></ng-content>
{{placeholder}}
<span class="md-placeholder-required" *ngIf="required">*</span>
</label>
<input #input
aria-target
class="md-input-element"
[class.md-end]="align == 'end'"
[attr.aria-label]="ariaLabel"
[attr.aria-labelledby]="ariaLabelledBy"
[attr.aria-disabled]="ariaDisabled"
[attr.aria-required]="ariaRequired"
[attr.aria-invalid]="ariaInvalid"
[id]="id"
[disabled]="disabled"
[required]="required"
[attr.maxlength]="maxLength"
[type]="type"
(focus)="onFocus()"
(blur)="onBlur()"
[(ngModel)]="value">
<div class="md-input-suffix"><ng-content select="[md-suffix]"></ng-content></div>
</div>
<div class="md-input-underline"
[class.md-disabled]="disabled">
<span class="md-input-ripple"
[class.md-focused]="focused"
[class.md-accent]="dividerColor == 'accent'"></span>
</div>
<div *ngIf="hintLabel != ''" class="md-hint">{{hintLabel}}</div>
<ng-content select="md-hint"></ng-content>
</div>
+184
View File
@@ -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 <input> has a system font.
font: inherit;
// By default, <input> 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;
}
}
}
+355
View File
@@ -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 <input> 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 <md-hint>.
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: `
<md-input required placeholder="hello">
</md-input>
`,
directives: [MD_INPUT_DIRECTIVES]
})
class MdInputPlaceholderRequiredTestComponent {
}
@Component({
selector: 'test-input-controller',
template: `
<md-input>
<md-placeholder>{{placeholder}}</md-placeholder>
</md-input>
`,
directives: [MD_INPUT_DIRECTIVES]
})
class MdInputPlaceholderElementTestComponent {
placeholder: string = 'Default Placeholder';
}
@Component({
selector: 'test-input-controller',
template: `
<md-input [placeholder]="placeholder">
</md-input>
`,
directives: [MD_INPUT_DIRECTIVES]
})
class MdInputPlaceholderAttrTestComponent {
placeholder: string = '';
}
@Component({
selector: 'test-input-controller',
template: `
<md-input>
<md-hint>{{label}}</md-hint>
</md-input>
`,
directives: [MD_INPUT_DIRECTIVES]
})
class MdInputHintLabel2TestController {
label: string = '';
}
@Component({
selector: 'test-input-controller',
template: `
<md-input [hintLabel]="label">
</md-input>
`,
directives: [MD_INPUT_DIRECTIVES]
})
class MdInputHintLabelTestController {
label: string = '';
}
@Component({
selector: 'test-input-controller',
template: `
<md-input type="file">
</md-input>
`,
directives: [MD_INPUT_DIRECTIVES]
})
class MdInputInvalidTypeTestController {
}
@Component({
selector: 'test-input-controller',
template: `
<md-input placeholder="Hello">
<md-placeholder>World</md-placeholder>
</md-input>
`,
directives: [MD_INPUT_DIRECTIVES]
})
class MdInputInvalidPlaceholderTestController {
}
@Component({
selector: 'test-input-controller',
template: `
<md-input hintLabel="Hello">
<md-hint>World</md-hint>
</md-input>
`,
directives: [MD_INPUT_DIRECTIVES]
})
class MdInputInvalidHint2TestController {
}
@Component({
selector: 'test-input-controller',
template: `
<md-input>
<md-hint>Hello</md-hint>
<md-hint>World</md-hint>
</md-input>
`,
directives: [MD_INPUT_DIRECTIVES]
})
class MdInputInvalidHintTestController {
}
@Component({
selector: 'test-input-controller',
template: `
<md-input [(ngModel)]="model">
</md-input>
`,
directives: [MdInput]
})
class MdInputBaseTestController {
model: any = '';
}
@Component({
selector: 'test-input-controller',
template: `
<md-input [aria-label]="ariaLabel" [aria-disabled]="ariaDisabled">
</md-input>
`,
directives: [MdInput]
})
class MdInputAriaTestController {
ariaLabel: string = 'label';
ariaDisabled: boolean = true;
}
+241
View File
@@ -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 <input> 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<MdHint>;
/** 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 <md-placeholder> are mutually exclusive.
* - type attribute is not one of the forbidden types (see constant at the top).
* - Maximum one of each `<md-hint>` 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,
]);
+6
View File
@@ -0,0 +1,6 @@
/**
* Annotation for a @BooleanFieldValue() property.
*/
class BooleanFieldValue {
const BooleanFieldValue();
}
@@ -0,0 +1 @@
+34
View File
@@ -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();
(<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;
}
+38
View File
@@ -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:
* <component myField>
* or:
* <component myField="">
*/
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 };
+1
View File
@@ -8,6 +8,7 @@
<li><a [routerLink]="['PortalDemo']">Portal demo</a></li>
<li><a [routerLink]="['OverlayDemo']">Overlay demo</a></li>
<li><a [routerLink]="['CheckboxDemo']">Checkbox demo</a></li>
<li><a [routerLink]="['InputDemo']">Input demo</a></li>
<li><a [routerLink]="['ToolbarDemo']">Toolbar demo</a></li>
<li><a [routerLink]="['RadioDemo']">Radio demo</a></li>
<li><a [routerLink]="['ListDemo']">List demo</a></li>
+3
View File
@@ -12,6 +12,8 @@ import {PortalDemo} from './portal/portal-demo';
import {ToolbarDemo} from './toolbar/toolbar-demo';
import {OverlayDemo} from './overlay/overlay-demo';
import {ListDemo} from './list/list-demo';
import {InputDemo} from './input/input-demo';
@Component({
selector: 'home',
@@ -37,6 +39,7 @@ export class Home {}
new Route({path: '/portal', name: 'PortalDemo', component: PortalDemo}),
new Route({path: '/overlay', name: 'OverlayDemo', component: OverlayDemo}),
new Route({path: '/checkbox', name: 'CheckboxDemo', component: CheckboxDemo}),
new Route({path: '/input', name: 'InputDemo', component: InputDemo}),
new Route({path: '/toolbar', name: 'ToolbarDemo', component: ToolbarDemo}),
new Route({path: '/list', name: 'ListDemo', component: ListDemo})
])
+99
View File
@@ -0,0 +1,99 @@
<md-card class="demo-card">
<md-card-title>
Hello <md-input [(ngModel)]="name" type="string" placeholder="First name"></md-input>,
how are you?
</md-card-title>
<md-card-content>
<p>
<md-input disabled placeholder="Disabled field" value="Value"></md-input>
<md-input required placeholder="Required field"></md-input>
</p>
<p>
<md-input placeholder="100% width placeholder" style="width: 100%"></md-input>
</p>
<p>
<md-input placeholder="Character count (100 max)" maxLength="100" style="width: 100%"
#input>
<md-hint align="end">{{input.characterCount}} / 100</md-hint>
</md-input>
</p>
<p>
<md-input placeholder="Show Hint Label" style="width: 100%"
hintLabel="Hint label"></md-input>
</p>
<p>
<md-input>
<md-placeholder>
I <i class="material-icons demo-icons">favorite</i> <b>bold</b> placeholder
</md-placeholder>
<md-hint>
I also <i class="material-icons demo-icons">home</i> <i>italic</i> hint labels
</md-hint>
</md-input>
</p>
<p>
<md-input placeholder="Show Hint Label With Character Count" style="width: 100%"
hintLabel="Hint label" characterCounter></md-input>
</p>
<p>
<md-checkbox [(ngModel)]="dividerColor">Check to change the divider color:</md-checkbox>
<md-input [dividerColor]="dividerColor ? 'primary' : 'accent'"
[placeholder]="dividerColor ? 'Primary color' : 'Accent color'"></md-input>
</p>
<p>
<md-checkbox [(ngModel)]="requiredField"> Check to make required:</md-checkbox>
<md-input [required]="requiredField"
[placeholder]="requiredField ? 'Required field' : 'Not required field'"></md-input>
</p>
<p>
<md-checkbox [(ngModel)]="floatingLabel"> Check to make floating label:</md-checkbox>
<md-input [floatingPlaceholder]="floatingLabel"
[placeholder]="floatingLabel ? 'Floating label' : 'Not floating label'"></md-input>
</p>
<p>
<md-input placeholder="Prefixed" value="example">
<div md-prefix>Example:&nbsp;</div>
</md-input>
<md-input placeholder="Suffixed" value="123" align="end">
<span md-suffix>.00 €</span>
</md-input>
<br/>
Both:
<md-input #email placeholder="Email Address" value="angular-core" align="end">
<span md-prefix><i [class.primary]="email.focused" class="material-icons demo-icons demo-transform">email</i>&nbsp;</span>
<span md-suffix class="demo-transform" [class.primary]="email.focused">&nbsp;@gmail.com</span>
</md-input>
</p>
<p>
Empty: <md-input></md-input>
<label>Label: <md-input></md-input></label>
</p>
</md-card-content>
</md-card>
<md-card class="demo-card">
<table width="100%">
<thead>
<td>Table</td>
<td colspan="3">
<button (click)="addABunch(5)">Add 5</button>
<button (click)="addABunch(10)">Add 10</button>
<button (click)="addABunch(100)">Add 100</button>
<button (click)="addABunch(1000)">Add 1000</button>
</td>
</thead>
<tr *ngFor="#item of items; #i = index">
<td>{{i+1}}</td>
<td>
<md-input type="number" placeholder="value" aria-label="hello" [(ngModel)]="items[i].value"></md-input>
</td>
<td>
<input type="number" [(ngModel)]="items[i].value">
</td>
<td>{{item.value}}</td>
</tr>
</table>
</md-card>
+19
View File
@@ -0,0 +1,19 @@
@import 'default-theme';
@import 'variables';
.demo-icons {
font-size: 100%;
vertical-align: top;
}
.demo-transform {
transition: color $swift-ease-out-duration $swift-ease-out-timing-function;
}
.demo-primary {
color: md-color($md-primary);
}
.demo-card {
margin: 16px;
}
+34
View File
@@ -0,0 +1,34 @@
import {Component} from 'angular2/core';
import {MD_INPUT_DIRECTIVES} from '../../components/input/input';
import {MdButton} from '../../components/button/button';
import {MdCard} from '../../components/card/card';
import {MdCheckbox} from '../../components/checkbox/checkbox';
let max = 5;
@Component({
selector: 'input-demo',
templateUrl: 'demo-app/input/input-demo.html',
styleUrls: ['demo-app/input/input-demo.css'],
directives: [MdCard, MdCheckbox, MdButton, MD_INPUT_DIRECTIVES]
})
export class InputDemo {
dividerColor: boolean;
requiredField: boolean;
floatingLabel: boolean;
name: string;
items: any[] = [
{ value: 10 },
{ value: 20 },
{ value: 30 },
{ value: 40 },
{ value: 50 },
];
addABunch(n: number) {
for (let x = 0; x < n; x++) {
this.items.push({ value: ++max });
}
}
}