Skip to content

Commit fb0cf8a

Browse files
devversionkara
authored andcommitted
feat(input): option to imperatively float placeholder (#2585)
Closes #2466
1 parent 4ed259a commit fb0cf8a

File tree

6 files changed

+117
-15
lines changed

6 files changed

+117
-15
lines changed

src/demo-app/input/input-container-demo.html

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,9 +180,16 @@ <h4>Textarea</h4>
180180
</md-input-container>
181181
</p>
182182
<p>
183-
<md-checkbox [(ngModel)]="floatingLabel"> Check to make floating label:</md-checkbox>
184-
<md-input-container [floatingPlaceholder]="floatingLabel">
185-
<input mdInput [placeholder]="floatingLabel ? 'Floating label' : 'Not floating label'">
183+
<md-button-toggle-group [(ngModel)]="floatingLabel">
184+
<md-button-toggle value="auto">Auto Float</md-button-toggle>
185+
<md-button-toggle value="always">Always Float</md-button-toggle>
186+
<md-button-toggle value="never">Never Float</md-button-toggle>
187+
</md-button-toggle-group>
188+
</p>
189+
190+
<p>
191+
<md-input-container [floatPlaceholder]="floatingLabel">
192+
<input mdInput placeholder="Placeholder">
186193
</md-input-container>
187194
</p>
188195

src/demo-app/input/input-container-demo.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ let max = 5;
1111
styleUrls: ['input-container-demo.css'],
1212
})
1313
export class InputContainerDemo {
14+
floatingLabel: string = 'auto';
1415
dividerColor: boolean;
1516
requiredField: boolean;
16-
floatingLabel: boolean;
1717
ctrlDisabled = false;
1818

1919
name: string;

src/lib/input/input-container.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77

88
<label class="md-input-placeholder"
99
[attr.for]="_mdInputChild.id"
10-
[class.md-empty]="_mdInputChild.empty"
10+
[class.md-empty]="_mdInputChild.empty && !_shouldAlwaysFloat"
1111
[class.md-focused]="_mdInputChild.focused"
12-
[class.md-float]="floatingPlaceholder"
12+
[class.md-float]="_canPlaceholderFloat"
1313
[class.md-accent]="dividerColor == 'accent'"
1414
[class.md-warn]="dividerColor == 'warn'"
1515
*ngIf="_hasPlaceholder()">

src/lib/input/input-container.spec.ts

Lines changed: 87 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ describe('MdInputContainer', function () {
4848
MdInputContainerWithStaticPlaceholder,
4949
MdInputContainerMissingMdInputTestController,
5050
MdInputContainerMultipleHintTestController,
51-
MdInputContainerMultipleHintMixedTestController
51+
MdInputContainerMultipleHintMixedTestController,
52+
MdInputContainerWithDynamicPlaceholder
5253
],
5354
});
5455

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

6263
let inputContainer = fixture.debugElement.query(By.directive(MdInputContainer))
6364
.componentInstance as MdInputContainer;
64-
expect(inputContainer.floatingPlaceholder).toBe(true,
65-
'Expected MdInputContainer to default to having floating placeholders turned on');
65+
expect(inputContainer.floatPlaceholder).toBe('auto',
66+
'Expected MdInputContainer to set floatingLabel to auto by default.');
6667
});
6768

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

478479
expect(ariaValue).toBe(`${hintLabel.getAttribute('id')} ${endLabel.getAttribute('id')}`);
479480
});
481+
482+
it('should float when floatPlaceholder is set to default and text is entered', () => {
483+
let fixture = TestBed.createComponent(MdInputContainerWithDynamicPlaceholder);
484+
fixture.detectChanges();
485+
486+
let inputEl = fixture.debugElement.query(By.css('input')).nativeElement;
487+
let labelEl = fixture.debugElement.query(By.css('label')).nativeElement;
488+
489+
expect(labelEl.classList).not.toContain('md-empty');
490+
expect(labelEl.classList).toContain('md-float');
491+
492+
fixture.componentInstance.shouldFloat = 'auto';
493+
fixture.detectChanges();
494+
495+
expect(labelEl.classList).toContain('md-empty');
496+
expect(labelEl.classList).toContain('md-float');
497+
498+
// Update the value of the input.
499+
inputEl.value = 'Text';
500+
501+
// Fake behavior of the `(input)` event which should trigger a change detection.
502+
fixture.detectChanges();
503+
504+
expect(labelEl.classList).not.toContain('md-empty');
505+
expect(labelEl.classList).toContain('md-float');
506+
});
507+
508+
it('should always float the placeholder when floatPlaceholder is set to true', () => {
509+
let fixture = TestBed.createComponent(MdInputContainerWithDynamicPlaceholder);
510+
fixture.detectChanges();
511+
512+
let inputEl = fixture.debugElement.query(By.css('input')).nativeElement;
513+
let labelEl = fixture.debugElement.query(By.css('label')).nativeElement;
514+
515+
expect(labelEl.classList).not.toContain('md-empty');
516+
expect(labelEl.classList).toContain('md-float');
517+
518+
fixture.detectChanges();
519+
520+
// Update the value of the input.
521+
inputEl.value = 'Text';
522+
523+
// Fake behavior of the `(input)` event which should trigger a change detection.
524+
fixture.detectChanges();
525+
526+
expect(labelEl.classList).not.toContain('md-empty');
527+
expect(labelEl.classList).toContain('md-float');
528+
});
529+
530+
531+
it('should never float the placeholder when floatPlaceholder is set to false', () => {
532+
let fixture = TestBed.createComponent(MdInputContainerWithDynamicPlaceholder);
533+
534+
fixture.componentInstance.shouldFloat = 'never';
535+
fixture.detectChanges();
536+
537+
let inputEl = fixture.debugElement.query(By.css('input')).nativeElement;
538+
let labelEl = fixture.debugElement.query(By.css('label')).nativeElement;
539+
540+
expect(labelEl.classList).toContain('md-empty');
541+
expect(labelEl.classList).not.toContain('md-float');
542+
543+
// Update the value of the input.
544+
inputEl.value = 'Text';
545+
546+
// Fake behavior of the `(input)` event which should trigger a change detection.
547+
fixture.detectChanges();
548+
549+
expect(labelEl.classList).not.toContain('md-empty');
550+
expect(labelEl.classList).not.toContain('md-float');
551+
});
552+
480553
});
481554

482555
@Component({
@@ -668,13 +741,23 @@ class MdInputContainerWithValueBinding {
668741

669742
@Component({
670743
template: `
671-
<md-input-container [floatingPlaceholder]="false">
744+
<md-input-container floatPlaceholder="never">
672745
<input mdInput placeholder="Label">
673746
</md-input-container>
674747
`
675748
})
676749
class MdInputContainerWithStaticPlaceholder {}
677750

751+
@Component({
752+
template: `
753+
<md-input-container [floatPlaceholder]="shouldFloat">
754+
<input mdInput placeholder="Label">
755+
</md-input-container>`
756+
})
757+
class MdInputContainerWithDynamicPlaceholder {
758+
shouldFloat: string = 'always';
759+
}
760+
678761
@Component({
679762
template: `
680763
<md-input-container>

src/lib/input/input-container.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ const MD_INPUT_INVALID_TYPES = [
3838
'submit'
3939
];
4040

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

4244
let nextUniqueId = 0;
4345

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

258+
/** Whether the floating label should always float or not. */
259+
get _shouldAlwaysFloat() { return this._floatPlaceholder === 'always'; };
260+
261+
/** Whether the placeholder can float or not. */
262+
get _canPlaceholderFloat() { return this._floatPlaceholder !== 'never'; }
263+
256264
/** Text for the input hint. */
257265
@Input()
258266
get hintLabel() { return this._hintLabel; }
@@ -265,11 +273,13 @@ export class MdInputContainer implements AfterContentInit {
265273
// Unique id for the hint label.
266274
_hintLabelId: string = `md-input-hint-${nextUniqueId++}`;
267275

268-
/** Text or the floating placeholder. */
276+
/** Whether the placeholder should always float, never float or float as the user types. */
269277
@Input()
270-
get floatingPlaceholder(): boolean { return this._floatingPlaceholder; }
271-
set floatingPlaceholder(value) { this._floatingPlaceholder = coerceBooleanProperty(value); }
272-
private _floatingPlaceholder: boolean = true;
278+
get floatPlaceholder() { return this._floatPlaceholder; }
279+
set floatPlaceholder(value: FloatPlaceholderType) {
280+
this._floatPlaceholder = value || 'auto';
281+
}
282+
private _floatPlaceholder: FloatPlaceholderType = 'auto';
273283

274284
@ContentChild(MdInputDirective) _mdInputChild: MdInputDirective;
275285

src/lib/input/input.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,11 @@ be used with `md-input-container`:
3838
A placeholder is an indicative text displayed in the input zone when the input does not contain
3939
text. When text is present, the indicative text will float above this input zone.
4040

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

44+
When setting `floatPlaceholder` to `always` the floating label will always show above the input.
45+
4446
A placeholder for the input can be specified in one of two ways: either using the `placeholder`
4547
attribute on the `input` or `textarea`, or using an `md-placeholder` element in the
4648
`md-input-container`. Using both will raise an error.

0 commit comments

Comments
 (0)