Skip to content

Commit 03e90db

Browse files
karammalerba
authored andcommitted
fix(select): fix width setting under ngIf (#2065)
1 parent 27f9c99 commit 03e90db

File tree

7 files changed

+126
-23
lines changed

7 files changed

+126
-23
lines changed

src/demo-app/select/select-demo.html

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
<div style="height: 1000px">This div is for testing scrolled selects.</div>
2+
<button md-button (click)="showSelect=!showSelect">SHOW SELECT</button>
23
<div class="demo-select">
3-
<md-card>
4-
<md-select placeholder="Food i would like to eat" [formControl]="foodControl">
5-
<md-option *ngFor="let food of foods" [value]="food.value"> {{ food.viewValue }} </md-option>
6-
</md-select>
7-
<p> Value: {{ foodControl.value }} </p>
8-
<p> Touched: {{ foodControl.touched }} </p>
9-
<p> Dirty: {{ foodControl.dirty }} </p>
10-
<p> Status: {{ foodControl.status }} </p>
11-
<button md-button (click)="foodControl.setValue('pizza-1')">SET VALUE</button>
12-
<button md-button (click)="toggleDisabled()">TOGGLE DISABLED</button>
13-
</md-card>
4+
<div *ngIf="showSelect">
5+
<md-card>
6+
<md-select placeholder="Food i would like to eat" [formControl]="foodControl">
7+
<md-option *ngFor="let food of foods" [value]="food.value"> {{ food.viewValue }} </md-option>
8+
</md-select>
9+
<p> Value: {{ foodControl.value }} </p>
10+
<p> Touched: {{ foodControl.touched }} </p>
11+
<p> Dirty: {{ foodControl.dirty }} </p>
12+
<p> Status: {{ foodControl.status }} </p>
13+
<button md-button (click)="foodControl.setValue('pizza-1')">SET VALUE</button>
14+
<button md-button (click)="toggleDisabled()">TOGGLE DISABLED</button>
15+
</md-card>
16+
</div>
17+
1418

1519
<md-card>
1620
<md-select placeholder="Drink" [(ngModel)]="currentDrink" [required]="isRequired" [disabled]="isDisabled"

src/demo-app/select/select-demo.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {FormControl} from '@angular/forms';
1010
export class SelectDemo {
1111
isRequired = false;
1212
isDisabled = false;
13+
showSelect = false;
1314
currentDrink: string;
1415
foodControl = new FormControl('pizza-1');
1516

src/lib/select/select-animations.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,13 @@ export const transformPlaceholder: AnimationEntryMetadata = trigger('transformPl
4545
export const transformPanel: AnimationEntryMetadata = trigger('transformPanel', [
4646
state('showing', style({
4747
opacity: 1,
48-
width: 'calc(100% + 32px)',
48+
minWidth: 'calc(100% + 32px)',
4949
transform: `translate3d(0,0,0) scaleY(1)`
5050
})),
5151
transition('void => *', [
5252
style({
5353
opacity: 0,
54-
width: '100%',
54+
minWidth: '100%',
5555
transform: `translate3d(0, 0, 0) scaleY(0)`
5656
}),
5757
animate(`150ms cubic-bezier(0.25, 0.8, 0.25, 1)`)

src/lib/select/select.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
<div class="md-select-trigger" overlay-origin (click)="toggle()" #origin="overlayOrigin" #trigger>
22
<span class="md-select-placeholder" [class.md-floating-placeholder]="this.selected"
3-
[@transformPlaceholder]="_placeholderState"> {{ placeholder }} </span>
3+
[@transformPlaceholder]="_placeholderState" [style.width.px]="_selectedValueWidth"> {{ placeholder }} </span>
44
<span class="md-select-value" *ngIf="selected"> {{ selected?.viewValue }} </span>
55
<span class="md-select-arrow"></span>
66
</div>
77

88
<template connected-overlay [origin]="origin" [open]="panelOpen" hasBackdrop (backdropClick)="close()"
9-
backdropClass="md-overlay-transparent-backdrop" [positions]="_positions" [width]="_getWidth()"
9+
backdropClass="md-overlay-transparent-backdrop" [positions]="_positions" [minWidth]="_triggerWidth"
1010
[offsetY]="_offsetY" [offsetX]="_offsetX" (attach)="_setScrollTop()">
1111
<div class="md-select-panel" [@transformPanel]="'showing'" (@transformPanel.done)="_onPanelDone()"
1212
(keydown)="_keyManager.onKeydown($event)" [style.transformOrigin]="_transformOrigin">

src/lib/select/select.scss

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
@import '../core/style/menu-common';
2+
@import '../core/style/list-common';
23
@import '../core/style/form-common';
34
@import '../a11y/_a11y';
45

@@ -63,6 +64,7 @@ md-select {
6364

6465
.md-select-value {
6566
position: absolute;
67+
@include md-truncate-line();
6668

6769
// Firefox and some versions of IE incorrectly keep absolutely
6870
// positioned children of flex containers in the flex flow when calculating

src/lib/select/select.spec.ts

Lines changed: 68 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ describe('MdSelect', () => {
1616
beforeEach(async(() => {
1717
TestBed.configureTestingModule({
1818
imports: [MdSelectModule.forRoot(), ReactiveFormsModule, FormsModule],
19-
declarations: [BasicSelect, NgModelSelect, ManySelects],
19+
declarations: [BasicSelect, NgModelSelect, ManySelects, NgIfSelect],
2020
providers: [
2121
{provide: OverlayContainer, useFactory: () => {
2222
overlayContainerElement = document.createElement('div');
@@ -96,14 +96,16 @@ describe('MdSelect', () => {
9696
});
9797
}));
9898

99-
it('should set the width of the overlay based on the trigger', () => {
99+
it('should set the width of the overlay based on the trigger', async(() => {
100100
trigger.style.width = '200px';
101-
trigger.click();
102-
fixture.detectChanges();
103101

104-
const pane = overlayContainerElement.children[0] as HTMLElement;
105-
expect(pane.style.width).toBe('200px');
106-
});
102+
fixture.whenStable().then(() => {
103+
trigger.click();
104+
fixture.detectChanges();
105+
const pane = overlayContainerElement.children[0] as HTMLElement;
106+
expect(pane.style.minWidth).toBe('200px');
107+
});
108+
}));
107109

108110
});
109111

@@ -997,6 +999,39 @@ describe('MdSelect', () => {
997999

9981000
});
9991001

1002+
describe('special cases', () => {
1003+
1004+
it('should handle nesting in an ngIf', async(() => {
1005+
const fixture = TestBed.createComponent(NgIfSelect);
1006+
fixture.detectChanges();
1007+
1008+
fixture.componentInstance.isShowing = true;
1009+
fixture.detectChanges();
1010+
1011+
const trigger = fixture.debugElement.query(By.css('.md-select-trigger')).nativeElement;
1012+
trigger.style.width = '300px';
1013+
1014+
fixture.whenStable().then(() => {
1015+
fixture.detectChanges();
1016+
const value = fixture.debugElement.query(By.css('.md-select-value'));
1017+
expect(value.nativeElement.textContent)
1018+
.toContain('Pizza', `Expected trigger to be populated by the control's initial value.`);
1019+
1020+
trigger.click();
1021+
fixture.detectChanges();
1022+
1023+
const pane = overlayContainerElement.children[0] as HTMLElement;
1024+
expect(pane.style.minWidth).toEqual('300px');
1025+
1026+
expect(fixture.componentInstance.select.panelOpen).toBe(true);
1027+
expect(overlayContainerElement.textContent).toContain('Steak');
1028+
expect(overlayContainerElement.textContent).toContain('Pizza');
1029+
expect(overlayContainerElement.textContent).toContain('Tacos');
1030+
});
1031+
}));
1032+
1033+
});
1034+
10001035
});
10011036

10021037
@Component({
@@ -1062,6 +1097,32 @@ class NgModelSelect {
10621097
})
10631098
class ManySelects {}
10641099

1100+
@Component({
1101+
selector: 'ng-if-select',
1102+
template: `
1103+
<div *ngIf="isShowing">
1104+
<md-select placeholder="Food I want to eat right now" [formControl]="control">
1105+
<md-option *ngFor="let food of foods" [value]="food.value">
1106+
{{ food.viewValue }}
1107+
</md-option>
1108+
</md-select>
1109+
</div>
1110+
`
1111+
1112+
})
1113+
class NgIfSelect {
1114+
isShowing = false;
1115+
foods: any[] = [
1116+
{ value: 'steak-0', viewValue: 'Steak' },
1117+
{ value: 'pizza-1', viewValue: 'Pizza' },
1118+
{ value: 'tacos-2', viewValue: 'Tacos'}
1119+
];
1120+
control = new FormControl('pizza-1');
1121+
1122+
@ViewChild(MdSelect) select: MdSelect;
1123+
}
1124+
1125+
10651126

10661127
/**
10671128
* TODO: Move this to core testing utility until Angular has event faking

src/lib/select/select.ts

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,9 +114,24 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr
114114
/** The scroll position of the overlay panel, calculated to center the selected option. */
115115
private _scrollTop = 0;
116116

117+
/** The placeholder displayed in the trigger of the select. */
118+
private _placeholder: string;
119+
117120
/** The animation state of the placeholder. */
118121
_placeholderState = '';
119122

123+
/**
124+
* The width of the trigger. Must be saved to set the min width of the overlay panel
125+
* and the width of the selected value.
126+
*/
127+
_triggerWidth: number;
128+
129+
/**
130+
* The width of the selected option's value. Must be set programmatically
131+
* to ensure its overflow is clipped, as it's absolutely positioned.
132+
*/
133+
_selectedValueWidth: number;
134+
120135
/** Manages keyboard events for options in the panel. */
121136
_keyManager: ListKeyManager;
122137

@@ -171,7 +186,17 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr
171186
@ViewChild(ConnectedOverlayDirective) overlayDir: ConnectedOverlayDirective;
172187
@ContentChildren(MdOption) options: QueryList<MdOption>;
173188

174-
@Input() placeholder: string;
189+
@Input()
190+
get placeholder() {
191+
return this._placeholder;
192+
}
193+
194+
set placeholder(value: string) {
195+
this._placeholder = value;
196+
197+
// Must wait to record the trigger width to ensure placeholder width is included.
198+
Promise.resolve(null).then(() => this._triggerWidth = this._getWidth());
199+
}
175200

176201
@Input()
177202
get disabled() {
@@ -400,6 +425,7 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr
400425
private _onSelect(option: MdOption): void {
401426
this._selected = option;
402427
this._updateOptions();
428+
this._setValueWidth();
403429
if (this.panelOpen) {
404430
this.close();
405431
}
@@ -414,6 +440,15 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr
414440
});
415441
}
416442

443+
/**
444+
* Must set the width of the selected option's value programmatically
445+
* because it is absolutely positioned and otherwise will not clip
446+
* overflow. The selection arrow is 9px wide, add 4px of padding = 13
447+
*/
448+
private _setValueWidth() {
449+
this._selectedValueWidth = this._triggerWidth - 13;
450+
}
451+
417452
/** Focuses the selected item. If no option is selected, it will focus
418453
* the first item instead.
419454
*/

0 commit comments

Comments
 (0)