Skip to content

Commit 7af84b8

Browse files
committed
fix(select): fix width setting under ngIf
1 parent 751fd4c commit 7af84b8

File tree

6 files changed

+125
-23
lines changed

6 files changed

+125
-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/lib/select/select-animations.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,13 @@ export const transformPlaceholder: AnimationEntryMetadata = trigger('transformPl
4444
export const transformPanel: AnimationEntryMetadata = trigger('transformPanel', [
4545
state('showing', style({
4646
opacity: 1,
47-
width: 'calc(100% + 32px)',
47+
minWidth: 'calc(100% + 32px)',
4848
transform: `translate3d(0,0,0) scaleY(1)`
4949
})),
5050
transition('void => *', [
5151
style({
5252
opacity: 0,
53-
width: '100%',
53+
minWidth: '100%',
5454
transform: `translate3d(0, 0, 0) scaleY(0)`
5555
}),
5656
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,11 +1,11 @@
11
<div class="md-select-trigger" overlay-origin (click)="toggle()" #origin="overlayOrigin" #trigger>
22
<span class="md-select-placeholder" [@transformPlaceholder]="_getPlaceholderState()"> {{ placeholder }} </span>
3-
<span class="md-select-value" *ngIf="selected"> {{ selected?.viewValue }} </span>
3+
<span class="md-select-value" *ngIf="selected" [style.width.px]="_selectedValueWidth"> {{ selected?.viewValue }} </span>
44
<span class="md-select-arrow"></span>
55
</div>
66

77
<template connected-overlay [origin]="origin" [open]="panelOpen" hasBackdrop (backdropClick)="close()"
8-
backdropClass="md-overlay-transparent-backdrop" [positions]="_positions" [width]="_getWidth()"
8+
backdropClass="md-overlay-transparent-backdrop" [positions]="_positions" [minWidth]="_getWidth()"
99
[offsetY]="_offsetY" [offsetX]="_offsetX" (attach)="_setScrollTop()">
1010
<div class="md-select-panel" [@transformPanel]="'showing'" (@transformPanel.done)="_onPanelDone()"
1111
(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

45
$md-select-trigger-height: 30px !default;
@@ -47,6 +48,7 @@ md-select {
4748

4849
.md-select-value {
4950
position: absolute;
51+
@include md-truncate-line();
5052

5153
// Firefox and some versions of IE incorrectly keep absolutely
5254
// 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

@@ -968,6 +970,39 @@ describe('MdSelect', () => {
968970

969971
});
970972

973+
describe('special cases', () => {
974+
975+
it('should handle nesting in an ngIf', async(() => {
976+
const fixture = TestBed.createComponent(NgIfSelect);
977+
fixture.detectChanges();
978+
979+
fixture.componentInstance.isShowing = true;
980+
fixture.detectChanges();
981+
982+
const trigger = fixture.debugElement.query(By.css('.md-select-trigger')).nativeElement;
983+
trigger.style.width = '300px';
984+
985+
fixture.whenStable().then(() => {
986+
fixture.detectChanges();
987+
const value = fixture.debugElement.query(By.css('.md-select-value'));
988+
expect(value.nativeElement.textContent)
989+
.toContain('Pizza', `Expected trigger to be populated by the control's initial value.`);
990+
991+
trigger.click();
992+
fixture.detectChanges();
993+
994+
const pane = overlayContainerElement.children[0] as HTMLElement;
995+
expect(pane.style.minWidth).toEqual('300px');
996+
997+
expect(fixture.componentInstance.select.panelOpen).toBe(true);
998+
expect(overlayContainerElement.textContent).toContain('Steak');
999+
expect(overlayContainerElement.textContent).toContain('Pizza');
1000+
expect(overlayContainerElement.textContent).toContain('Tacos');
1001+
});
1002+
}));
1003+
1004+
});
1005+
9711006
});
9721007

9731008
@Component({
@@ -1033,6 +1068,32 @@ class NgModelSelect {
10331068
})
10341069
class ManySelects {}
10351070

1071+
@Component({
1072+
selector: 'ng-if-select',
1073+
template: `
1074+
<div *ngIf="isShowing">
1075+
<md-select placeholder="Food I want to eat right now" [formControl]="control">
1076+
<md-option *ngFor="let food of foods" [value]="food.value">
1077+
{{ food.viewValue }}
1078+
</md-option>
1079+
</md-select>
1080+
</div>
1081+
`
1082+
1083+
})
1084+
class NgIfSelect {
1085+
isShowing = false;
1086+
foods: any[] = [
1087+
{ value: 'steak-0', viewValue: 'Steak' },
1088+
{ value: 'pizza-1', viewValue: 'Pizza' },
1089+
{ value: 'tacos-2', viewValue: 'Tacos'}
1090+
];
1091+
control = new FormControl('pizza-1');
1092+
1093+
@ViewChild(MdSelect) select: MdSelect;
1094+
}
1095+
1096+
10361097

10371098
/**
10381099
* 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,6 +114,21 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr
114114

115115
/** The scroll position of the overlay panel, calculated to center the selected option. */
116116
private _scrollTop = 0;
117+
118+
/** The placeholder displayed in the trigger of the select. */
119+
private _placeholder: string;
120+
121+
/**
122+
* The width of the trigger. Must be saved to set the min width of the overlay panel
123+
* and the width of the selected value.
124+
*/
125+
_triggerWidth: number;
126+
127+
/**
128+
* The width of the selected option's value. Must be set programmatically
129+
* to ensure its overflow is clipped, as it's absolutely positioned.
130+
*/
131+
_selectedValueWidth: number;
117132

118133
/** Manages keyboard events for options in the panel. */
119134
_keyManager: ListKeyManager;
@@ -169,7 +184,17 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr
169184
@ViewChild(ConnectedOverlayDirective) overlayDir: ConnectedOverlayDirective;
170185
@ContentChildren(MdOption) options: QueryList<MdOption>;
171186

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

174199
@Input()
175200
get disabled() {
@@ -403,6 +428,7 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr
403428
private _onSelect(option: MdOption): void {
404429
this._selected = option;
405430
this._updateOptions();
431+
this._setValueWidth();
406432
this.close();
407433
}
408434

@@ -415,6 +441,15 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr
415441
});
416442
}
417443

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

0 commit comments

Comments
 (0)