Skip to content

Commit a5fa97a

Browse files
committed
feat(): md-input
1 parent 40d3f0c commit a5fa97a

18 files changed

+633
-0
lines changed

abc

Whitespace-only changes.

src/components/input/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# mdInput
2+
3+
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).
4+
5+
6+
7+
## Type
8+
9+
At the time of writing this README, the `[type]` attribute is copied to the actual `<input>` element in the `<md-input>`. Therefore, the valid types and behavior of those are the ones supported by your browser.
10+
11+
## Prefix and Suffix
12+

src/components/input/input.html

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<div class="md-wrapper" (click)="input.focus()">
2+
<div class="md-flex">
3+
<prefix><ng-content select="[md-prefix]"></ng-content></prefix>
4+
5+
<label [attr.for]="'md-input-' + _inputId"
6+
[class.md-empty]="empty" [class.md-focused]="focused"
7+
[class.md-float]="floatingPlaceholder" [class.md-accent]="dividerColor == 'accent'">
8+
{{placeholder}}
9+
<span *ngIf="required">*</span>
10+
</label>
11+
12+
<input #input aria-target
13+
[class.md-end]="align == 'end'"
14+
[attr.id]="'md-input-' + _inputId"
15+
[disabled]="disabled" [required]="required"
16+
[attr.maxlength]="maxLength" [attr.type]="type"
17+
(focus)="onFocus()" (blur)="onBlur()"
18+
[(ngModel)]="value">
19+
20+
<suffix><ng-content select="[md-suffix]"></ng-content></suffix>
21+
22+
</div>
23+
24+
<div class="md-underline" [class.md-disabled]="disabled">
25+
<span [class.md-focused]="focused" [class.md-accent]="dividerColor == 'accent'"></span>
26+
</div>
27+
<div *ngIf="hintLabel != ''" class="md-hint">{{hintLabel}}</div>
28+
<div *ngIf="_characterCounterLabel() != ''" class="md-hint md-right">{{_characterCounterLabel()}}</div>
29+
</div>

src/components/input/input.scss

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
@import 'default-theme';
2+
@import 'mixins';
3+
@import 'variables';
4+
5+
6+
//
7+
$md-input-label-color: md-color($md-foreground, hint-text);
8+
$md-input-floating-label-color: md-color($md-primary);
9+
$md-input-required-label-color: md-color($md-accent);
10+
$md-input-underline-color: md-color($md-foreground, hint-text);
11+
$md-input-underline-color-accent: md-color($md-accent);
12+
$md-input-underline-disabled-color: md-color($md-foreground, hint-text);
13+
$md-input-underline-focused-color: md-color($md-primary);
14+
15+
16+
:host {
17+
display: inline-block;
18+
position: relative;
19+
font-family: $md-font-family;
20+
21+
.md-wrapper {
22+
margin: 16px 0;
23+
}
24+
.md-flex {
25+
display: inline-table;
26+
flex-flow: column;
27+
vertical-align: bottom;
28+
width: 100%;
29+
}
30+
.md-flex > * {
31+
display: table-cell;
32+
}
33+
34+
input {
35+
// Font needs to be inherited, because by default <input> has a system font.
36+
font: inherit;
37+
38+
// By default, <input> has a padding, border, outline and a default width.
39+
border: none;
40+
outline: none;
41+
padding: 0;
42+
width: 100%;
43+
44+
&.md-end {
45+
text-align: right;
46+
}
47+
}
48+
49+
label {
50+
position: absolute;
51+
visibility: hidden;
52+
font-size: 100%;
53+
pointer-events: none;
54+
color: $md-input-label-color;
55+
z-index: 1;
56+
57+
width: 100%;
58+
59+
transform: translateY(0);
60+
transform-origin: bottom left;
61+
transition: transform $swift-ease-out-duration $swift-ease-out-timing-function,
62+
scale $swift-ease-out-duration $swift-ease-out-timing-function,
63+
color $swift-ease-out-duration $swift-ease-out-timing-function;
64+
65+
&.md-empty {
66+
visibility: visible;
67+
cursor: text;
68+
}
69+
70+
&.md-float:not(.md-empty), &.md-float.md-focused {
71+
visibility: visible;
72+
padding-bottom: 5px;
73+
transform: translateY(-100%) scale(0.75);
74+
75+
span {
76+
color: $md-input-required-label-color;
77+
}
78+
}
79+
80+
&.md-focused {
81+
color: $md-input-floating-label-color;
82+
83+
&.md-accent {
84+
color: $md-input-underline-color-accent;
85+
}
86+
}
87+
}
88+
89+
.md-underline {
90+
position: absolute;
91+
height: 1px;
92+
width: 100%;
93+
margin-top: 4px;
94+
border-top: 1px solid $md-input-underline-color;
95+
96+
&.md-disabled {
97+
border-top: 0;
98+
background-image: linear-gradient(to right, rgba(0,0,0,0.26) 0%, rgba(0,0,0,0.26) 33%, transparent 0%);
99+
background-position: 0;
100+
background-size: 4px 1px;
101+
background-repeat: repeat-x;
102+
}
103+
104+
> span {
105+
position: absolute;
106+
height: 2px;
107+
z-index: 1;
108+
background-color: $md-input-underline-focused-color;
109+
top: -1px;
110+
width: 100%;
111+
transform-origin: top;
112+
opacity: 0;
113+
transform: scaleY(0);
114+
transition: transform $swift-ease-out-duration $swift-ease-out-timing-function,
115+
opacity $swift-ease-out-duration $swift-ease-out-timing-function;
116+
117+
&.md-accent {
118+
background-color: $md-input-underline-color-accent;
119+
}
120+
121+
&.md-focused {
122+
opacity: 1;
123+
transform: scaleY(1);
124+
}
125+
}
126+
}
127+
128+
.md-hint {
129+
position: absolute;
130+
font-size: 75%;
131+
bottom: -0.5em;
132+
133+
&.md-right {
134+
right: 0;
135+
}
136+
}
137+
}
138+
139+
140+
:host-context([dir="rtl"]) {
141+
label {
142+
transform-origin: bottom right;
143+
}
144+
145+
input.md-end {
146+
text-align: left;
147+
}
148+
149+
.md-hint.md-right {
150+
right: auto;
151+
left: 0;
152+
}
153+
}

src/components/input/input.ts

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import {
2+
forwardRef,
3+
Component,
4+
HostBinding,
5+
Input,
6+
Output,
7+
Provider,
8+
} from 'angular2/core';
9+
import {EventEmitter} from 'angular2/src/facade/async';
10+
import {CONST_EXPR, noop} from 'angular2/src/facade/lang';
11+
import {MdAriaTarget, MdAria} from '../../core/utilities/aria-directives';
12+
import {
13+
NG_VALUE_ACCESSOR,
14+
ControlValueAccessor
15+
} from 'angular2/src/common/forms/directives/control_value_accessor';
16+
import {BooleanFieldValue} from '../../core/annotations/field-value';
17+
import {OneOf} from '../../core/annotations/one-of';
18+
19+
20+
const MD_INPUT_CONTROL_VALUE_ACCESSOR = CONST_EXPR(new Provider(
21+
NG_VALUE_ACCESSOR, {
22+
useExisting: forwardRef(() => MdInput),
23+
multi: true
24+
}));
25+
26+
27+
let uniqueId = 0;
28+
29+
30+
/**
31+
* <md-input> component.
32+
*/
33+
@Component({
34+
selector: 'md-input',
35+
templateUrl: 'components/input/input.html',
36+
styleUrls: ['components/input/input.css'],
37+
providers: [MdAria, MD_INPUT_CONTROL_VALUE_ACCESSOR],
38+
directives: [MdAriaTarget],
39+
})
40+
export class MdInput implements ControlValueAccessor {
41+
private _empty: boolean = true;
42+
private _focused: boolean = false;
43+
private _onTouched: () => void = noop;
44+
private _onChangeFn: (_: any) => void = noop;
45+
private _value: any = '';
46+
private _inputId: number = uniqueId++;
47+
48+
get focused() { return this._focused; }
49+
get empty() { return this._empty; }
50+
51+
@Input() @BooleanFieldValue() characterCounter: boolean = false;
52+
@Input() @BooleanFieldValue() disabled: boolean = false;
53+
@Input() @BooleanFieldValue() required: boolean = false;
54+
@Input() @BooleanFieldValue() floatingPlaceholder: boolean = true;
55+
56+
@Input() @OneOf(['primary', 'accent']) dividerColor: string = 'primary';
57+
@Input() @OneOf(['start', 'end']) align: string = 'start';
58+
@Input() hintLabel: string = '';
59+
@Input() maxLength: number = -1;
60+
@Input() placeholder: string;
61+
@Input() type: string = 'text';
62+
63+
get value(): any { return this._value; };
64+
@Input() set value(v: any) {
65+
if (this._value !== v) {
66+
this._value = v;
67+
this._empty = v === undefined || v === null || (''+v == '');
68+
this._onChangeFn(v);
69+
}
70+
}
71+
72+
@Output('change') change = new EventEmitter(false);
73+
74+
@HostBinding('attr.align') private get _align(): any { return null; }
75+
76+
/** @override */
77+
writeValue(value: any) {
78+
if (this._value !== value) {
79+
this._value = value;
80+
this._empty = value === undefined || value === null || (''+value == '');
81+
}
82+
}
83+
84+
/** @override */
85+
registerOnChange(fn: any) {
86+
this._onChangeFn = fn;
87+
}
88+
89+
/** @override */
90+
registerOnTouched(fn: any) {
91+
this._onTouched = fn;
92+
}
93+
94+
onFocus() {
95+
this._focused = true;
96+
}
97+
onBlur() {
98+
this._focused = false;
99+
this._onTouched();
100+
}
101+
102+
private _characterCounterLabel(): string {
103+
if (this.characterCounter) {
104+
return String(this.value || '').length + (this.maxLength > 0 ? ' / ' + this.maxLength : '');
105+
} else {
106+
return '';
107+
}
108+
}
109+
}
110+
111+
export const MD_INPUT_DIRECTIVES: any[] = CONST_EXPR([
112+
MdInput,
113+
MdAria,
114+
]);

src/core/annotations/field-value.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/**
2+
* Annotation for a @BooleanFieldValue() property.
3+
*/
4+
class BooleanFieldValue {
5+
const BooleanFieldValue();
6+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import {BooleanFieldValue} from './field-value';
2+
3+
describe('BooleanFieldValue', () => {
4+
it('should work for null values', () => {
5+
let x = new BooleanFieldValueTest();
6+
7+
x.field = null;
8+
expect(x.field).toBe(false);
9+
10+
x.field = undefined;
11+
expect(x.field).toBe(false);
12+
});
13+
14+
it('should work for string values', () => {
15+
let x = new BooleanFieldValueTest();
16+
17+
(<any>x).field = 'hello';
18+
expect(x.field).toBe(true);
19+
20+
(<any>x).field = 'true';
21+
expect(x.field).toBe(true);
22+
23+
(<any>x).field = '';
24+
expect(x.field).toBe(true);
25+
26+
(<any>x).field = 'false';
27+
expect(x.field).toBe(false);
28+
});
29+
});
30+
31+
32+
class BooleanFieldValueTest {
33+
@BooleanFieldValue() field: boolean;
34+
}

src/core/annotations/field-value.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import {isPresent} from 'angular2/src/facade/lang';
2+
3+
4+
declare var Symbol: any;
5+
6+
7+
/**
8+
* Annotation Factory that allows HTML style boolean attributes. For example,
9+
* a field declared like this:
10+
11+
* @Directive({ selector: 'component' }) class MyComponent {
12+
* @Input() @BooleanFieldValueFactory() myField: boolean;
13+
* }
14+
*
15+
* You could set it up this way:
16+
* <component myField>
17+
* or:
18+
* <component myField="">
19+
*/
20+
function booleanFieldValueFactory() {
21+
return function booleanFieldValueMetadata(target: any, key: string): void {
22+
const defaultValue = target[key];
23+
24+
// Use a fallback if Symbol isn't available.
25+
const localKey = isPresent(Symbol) ? Symbol(key) : `__md_private_symbol_${key}`;
26+
target[localKey] = defaultValue;
27+
28+
Object.defineProperty(target, key, {
29+
get() { return this[localKey]; },
30+
set(value: boolean) {
31+
this[localKey] = isPresent(value) && value !== null && String(value) != 'false';
32+
}
33+
});
34+
};
35+
}
36+
37+
38+
export { booleanFieldValueFactory as BooleanFieldValue };

0 commit comments

Comments
 (0)