Skip to content

Commit

Permalink
feat(input): option to imperatively float placeholder (#2585)
Browse files Browse the repository at this point in the history
Closes #2466
  • Loading branch information
devversion authored and kara committed Feb 3, 2017
1 parent 4ed259a commit fb0cf8a
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 15 deletions.
13 changes: 10 additions & 3 deletions src/demo-app/input/input-container-demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -180,9 +180,16 @@ <h4>Textarea</h4>
</md-input-container>
</p>
<p>
<md-checkbox [(ngModel)]="floatingLabel"> Check to make floating label:</md-checkbox>
<md-input-container [floatingPlaceholder]="floatingLabel">
<input mdInput [placeholder]="floatingLabel ? 'Floating label' : 'Not floating label'">
<md-button-toggle-group [(ngModel)]="floatingLabel">
<md-button-toggle value="auto">Auto Float</md-button-toggle>
<md-button-toggle value="always">Always Float</md-button-toggle>
<md-button-toggle value="never">Never Float</md-button-toggle>
</md-button-toggle-group>
</p>

<p>
<md-input-container [floatPlaceholder]="floatingLabel">
<input mdInput placeholder="Placeholder">
</md-input-container>
</p>

Expand Down
2 changes: 1 addition & 1 deletion src/demo-app/input/input-container-demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ let max = 5;
styleUrls: ['input-container-demo.css'],
})
export class InputContainerDemo {
floatingLabel: string = 'auto';
dividerColor: boolean;
requiredField: boolean;
floatingLabel: boolean;
ctrlDisabled = false;

name: string;
Expand Down
4 changes: 2 additions & 2 deletions src/lib/input/input-container.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@

<label class="md-input-placeholder"
[attr.for]="_mdInputChild.id"
[class.md-empty]="_mdInputChild.empty"
[class.md-empty]="_mdInputChild.empty && !_shouldAlwaysFloat"
[class.md-focused]="_mdInputChild.focused"
[class.md-float]="floatingPlaceholder"
[class.md-float]="_canPlaceholderFloat"
[class.md-accent]="dividerColor == 'accent'"
[class.md-warn]="dividerColor == 'warn'"
*ngIf="_hasPlaceholder()">
Expand Down
91 changes: 87 additions & 4 deletions src/lib/input/input-container.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ describe('MdInputContainer', function () {
MdInputContainerWithStaticPlaceholder,
MdInputContainerMissingMdInputTestController,
MdInputContainerMultipleHintTestController,
MdInputContainerMultipleHintMixedTestController
MdInputContainerMultipleHintMixedTestController,
MdInputContainerWithDynamicPlaceholder
],
});

Expand All @@ -61,8 +62,8 @@ describe('MdInputContainer', function () {

let inputContainer = fixture.debugElement.query(By.directive(MdInputContainer))
.componentInstance as MdInputContainer;
expect(inputContainer.floatingPlaceholder).toBe(true,
'Expected MdInputContainer to default to having floating placeholders turned on');
expect(inputContainer.floatPlaceholder).toBe('auto',
'Expected MdInputContainer to set floatingLabel to auto by default.');
});

it('should not be treated as empty if type is date',
Expand Down Expand Up @@ -477,6 +478,78 @@ describe('MdInputContainer', function () {

expect(ariaValue).toBe(`${hintLabel.getAttribute('id')} ${endLabel.getAttribute('id')}`);
});

it('should float when floatPlaceholder is set to default and text is entered', () => {
let fixture = TestBed.createComponent(MdInputContainerWithDynamicPlaceholder);
fixture.detectChanges();

let inputEl = fixture.debugElement.query(By.css('input')).nativeElement;
let labelEl = fixture.debugElement.query(By.css('label')).nativeElement;

expect(labelEl.classList).not.toContain('md-empty');
expect(labelEl.classList).toContain('md-float');

fixture.componentInstance.shouldFloat = 'auto';
fixture.detectChanges();

expect(labelEl.classList).toContain('md-empty');
expect(labelEl.classList).toContain('md-float');

// Update the value of the input.
inputEl.value = 'Text';

// Fake behavior of the `(input)` event which should trigger a change detection.
fixture.detectChanges();

expect(labelEl.classList).not.toContain('md-empty');
expect(labelEl.classList).toContain('md-float');
});

it('should always float the placeholder when floatPlaceholder is set to true', () => {
let fixture = TestBed.createComponent(MdInputContainerWithDynamicPlaceholder);
fixture.detectChanges();

let inputEl = fixture.debugElement.query(By.css('input')).nativeElement;
let labelEl = fixture.debugElement.query(By.css('label')).nativeElement;

expect(labelEl.classList).not.toContain('md-empty');
expect(labelEl.classList).toContain('md-float');

fixture.detectChanges();

// Update the value of the input.
inputEl.value = 'Text';

// Fake behavior of the `(input)` event which should trigger a change detection.
fixture.detectChanges();

expect(labelEl.classList).not.toContain('md-empty');
expect(labelEl.classList).toContain('md-float');
});


it('should never float the placeholder when floatPlaceholder is set to false', () => {
let fixture = TestBed.createComponent(MdInputContainerWithDynamicPlaceholder);

fixture.componentInstance.shouldFloat = 'never';
fixture.detectChanges();

let inputEl = fixture.debugElement.query(By.css('input')).nativeElement;
let labelEl = fixture.debugElement.query(By.css('label')).nativeElement;

expect(labelEl.classList).toContain('md-empty');
expect(labelEl.classList).not.toContain('md-float');

// Update the value of the input.
inputEl.value = 'Text';

// Fake behavior of the `(input)` event which should trigger a change detection.
fixture.detectChanges();

expect(labelEl.classList).not.toContain('md-empty');
expect(labelEl.classList).not.toContain('md-float');
});

});

@Component({
Expand Down Expand Up @@ -668,13 +741,23 @@ class MdInputContainerWithValueBinding {

@Component({
template: `
<md-input-container [floatingPlaceholder]="false">
<md-input-container floatPlaceholder="never">
<input mdInput placeholder="Label">
</md-input-container>
`
})
class MdInputContainerWithStaticPlaceholder {}

@Component({
template: `
<md-input-container [floatPlaceholder]="shouldFloat">
<input mdInput placeholder="Label">
</md-input-container>`
})
class MdInputContainerWithDynamicPlaceholder {
shouldFloat: string = 'always';
}

@Component({
template: `
<md-input-container>
Expand Down
18 changes: 14 additions & 4 deletions src/lib/input/input-container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ const MD_INPUT_INVALID_TYPES = [
'submit'
];

/** Type for the available floatPlaceholder values. */
export type FloatPlaceholderType = 'always' | 'never' | 'auto';

let nextUniqueId = 0;

Expand Down Expand Up @@ -253,6 +255,12 @@ export class MdInputContainer implements AfterContentInit {
/** Color of the input divider, based on the theme. */
@Input() dividerColor: 'primary' | 'accent' | 'warn' = 'primary';

/** Whether the floating label should always float or not. */
get _shouldAlwaysFloat() { return this._floatPlaceholder === 'always'; };

/** Whether the placeholder can float or not. */
get _canPlaceholderFloat() { return this._floatPlaceholder !== 'never'; }

/** Text for the input hint. */
@Input()
get hintLabel() { return this._hintLabel; }
Expand All @@ -265,11 +273,13 @@ export class MdInputContainer implements AfterContentInit {
// Unique id for the hint label.
_hintLabelId: string = `md-input-hint-${nextUniqueId++}`;

/** Text or the floating placeholder. */
/** Whether the placeholder should always float, never float or float as the user types. */
@Input()
get floatingPlaceholder(): boolean { return this._floatingPlaceholder; }
set floatingPlaceholder(value) { this._floatingPlaceholder = coerceBooleanProperty(value); }
private _floatingPlaceholder: boolean = true;
get floatPlaceholder() { return this._floatPlaceholder; }
set floatPlaceholder(value: FloatPlaceholderType) {
this._floatPlaceholder = value || 'auto';
}
private _floatPlaceholder: FloatPlaceholderType = 'auto';

@ContentChild(MdInputDirective) _mdInputChild: MdInputDirective;

Expand Down
4 changes: 3 additions & 1 deletion src/lib/input/input.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,11 @@ be used with `md-input-container`:
A placeholder is an indicative text displayed in the input zone when the input does not contain
text. When text is present, the indicative text will float above this input zone.

The `floatingPlaceholder` attribute of `md-input-container` can be set to `false` to hide the
The `floatPlaceholder` attribute of `md-input-container` can be set to `never` to hide the
indicative text instead when text is present in the input.

When setting `floatPlaceholder` to `always` the floating label will always show above the input.

A placeholder for the input can be specified in one of two ways: either using the `placeholder`
attribute on the `input` or `textarea`, or using an `md-placeholder` element in the
`md-input-container`. Using both will raise an error.
Expand Down

0 comments on commit fb0cf8a

Please sign in to comment.