Skip to content

Commit 62e2d0a

Browse files
committed
feat(): md-input
1 parent ead66f7 commit 62e2d0a

19 files changed

+560
-2
lines changed

ember-cli-build.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ module.exports = function(defaults) {
3333
componentCssTree,
3434
demoAppCssTree,
3535
demoCssTree,
36-
].concat(dartAppTree || []));
36+
].concat(dartAppTree || []), {overwrite: true});
3737
};
3838

3939
/** Gets the Dart tree - Transpile Dart files and format them afterward. */

src/components/input/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# mdInput
2+
3+

src/components/input/input.html

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<div class="md-wrapper" (click)="input.focus()">
2+
<label>
3+
<div [class.md-empty]="empty" [class.md-focused]="focused"
4+
[class.md-float]="floatingPlaceholder" [class.md-accent]="dividerColor == 'accent'">
5+
{{placeholder}}
6+
<span *ngIf="required">*</span>
7+
</div>
8+
9+
<input #input aria-target
10+
[disabled]="disabled" [required]="required" [attr.maxlength]="maxLength" [attr.type]="type"
11+
(focus)="onFocus()" (blur)="onBlur()"
12+
[(ngModel)]="value">
13+
</label>
14+
15+
<div class="md-underline" [class.md-disabled]="disabled">
16+
<span [class.md-focused]="focused" [class.md-accent]="dividerColor == 'accent'"></span>
17+
</div>
18+
<div *ngIf="_hintLabel() != ''" class="md-hint" [class.md-right]="_rightAlignHintLabel()">{{_hintLabel()}}</div>
19+
</div>

src/components/input/input.scss

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
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+
25+
input {
26+
font-size: 100%;
27+
border: none;
28+
outline: none;
29+
// By default, <input> has a padding.
30+
padding: 0;
31+
z-index: 1;
32+
33+
// The input should take 100% width of the component.
34+
width: 100%;
35+
}
36+
37+
label > div {
38+
position: absolute;
39+
visibility: hidden;
40+
font-size: 100%;
41+
pointer-events: none;
42+
color: $md-input-label-color;
43+
44+
width: 100%;
45+
46+
transform: translateY(0);
47+
transform-origin: bottom left;
48+
transition: transform $swift-ease-out-duration $swift-ease-out-timing-function,
49+
scale $swift-ease-out-duration $swift-ease-out-timing-function,
50+
color $swift-ease-out-duration $swift-ease-out-timing-function;
51+
52+
&.md-empty {
53+
visibility: visible;
54+
cursor: text;
55+
}
56+
57+
&.md-float:not(.md-empty), &.md-float.md-focused {
58+
visibility: visible;
59+
padding-bottom: 5px;
60+
transform: translateY(-100%) scale(0.75);
61+
62+
span {
63+
color: $md-input-required-label-color;
64+
}
65+
}
66+
67+
&.md-focused {
68+
color: $md-input-floating-label-color;
69+
70+
&.md-accent {
71+
color: $md-input-underline-color-accent;
72+
}
73+
}
74+
}
75+
76+
.md-underline {
77+
position: absolute;
78+
height: 1px;
79+
width: 100%;
80+
margin-top: 4px;
81+
border-top: 1px solid $md-input-underline-color;
82+
83+
&.md-disabled {
84+
border-top: 0;
85+
background-image: linear-gradient(to right, rgba(0,0,0,0.26) 0%, rgba(0,0,0,0.26) 33%, transparent 0%);
86+
background-position: 0;
87+
background-size: 4px 1px;
88+
background-repeat: repeat-x;
89+
}
90+
91+
> span {
92+
position: absolute;
93+
height: 2px;
94+
z-index: 1;
95+
background-color: $md-input-underline-focused-color;
96+
top: -1px;
97+
width: 100%;
98+
transform-origin: top;
99+
opacity: 0;
100+
transform: scaleY(0);
101+
transition: transform $swift-ease-out-duration $swift-ease-out-timing-function,
102+
opacity $swift-ease-out-duration $swift-ease-out-timing-function;
103+
104+
&.md-accent {
105+
background-color: $md-input-underline-color-accent;
106+
}
107+
108+
&.md-focused {
109+
opacity: 1;
110+
transform: scaleY(1);
111+
}
112+
}
113+
}
114+
115+
.md-hint {
116+
position: absolute;
117+
font-size: 75%;
118+
bottom: -0.5em;
119+
120+
&.md-right {
121+
right: 0;
122+
}
123+
}
124+
}
125+
126+
127+
:host-context([dir="rtl"]) {
128+
label {
129+
transform-origin: bottom right;
130+
}
131+
132+
.md-hint.md-right {
133+
right: auto;
134+
left: 0;
135+
}
136+
}

src/components/input/input.ts

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

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)