Skip to content

Commit

Permalink
fix(select): fix width setting under ngIf (angular#2065)
Browse files Browse the repository at this point in the history
  • Loading branch information
kara authored and mmalerba committed Dec 7, 2016
1 parent 27f9c99 commit 03e90db
Show file tree
Hide file tree
Showing 7 changed files with 126 additions and 23 deletions.
26 changes: 15 additions & 11 deletions src/demo-app/select/select-demo.html
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
<div style="height: 1000px">This div is for testing scrolled selects.</div>
<button md-button (click)="showSelect=!showSelect">SHOW SELECT</button>
<div class="demo-select">
<md-card>
<md-select placeholder="Food i would like to eat" [formControl]="foodControl">
<md-option *ngFor="let food of foods" [value]="food.value"> {{ food.viewValue }} </md-option>
</md-select>
<p> Value: {{ foodControl.value }} </p>
<p> Touched: {{ foodControl.touched }} </p>
<p> Dirty: {{ foodControl.dirty }} </p>
<p> Status: {{ foodControl.status }} </p>
<button md-button (click)="foodControl.setValue('pizza-1')">SET VALUE</button>
<button md-button (click)="toggleDisabled()">TOGGLE DISABLED</button>
</md-card>
<div *ngIf="showSelect">
<md-card>
<md-select placeholder="Food i would like to eat" [formControl]="foodControl">
<md-option *ngFor="let food of foods" [value]="food.value"> {{ food.viewValue }} </md-option>
</md-select>
<p> Value: {{ foodControl.value }} </p>
<p> Touched: {{ foodControl.touched }} </p>
<p> Dirty: {{ foodControl.dirty }} </p>
<p> Status: {{ foodControl.status }} </p>
<button md-button (click)="foodControl.setValue('pizza-1')">SET VALUE</button>
<button md-button (click)="toggleDisabled()">TOGGLE DISABLED</button>
</md-card>
</div>


<md-card>
<md-select placeholder="Drink" [(ngModel)]="currentDrink" [required]="isRequired" [disabled]="isDisabled"
Expand Down
1 change: 1 addition & 0 deletions src/demo-app/select/select-demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {FormControl} from '@angular/forms';
export class SelectDemo {
isRequired = false;
isDisabled = false;
showSelect = false;
currentDrink: string;
foodControl = new FormControl('pizza-1');

Expand Down
4 changes: 2 additions & 2 deletions src/lib/select/select-animations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,13 @@ export const transformPlaceholder: AnimationEntryMetadata = trigger('transformPl
export const transformPanel: AnimationEntryMetadata = trigger('transformPanel', [
state('showing', style({
opacity: 1,
width: 'calc(100% + 32px)',
minWidth: 'calc(100% + 32px)',
transform: `translate3d(0,0,0) scaleY(1)`
})),
transition('void => *', [
style({
opacity: 0,
width: '100%',
minWidth: '100%',
transform: `translate3d(0, 0, 0) scaleY(0)`
}),
animate(`150ms cubic-bezier(0.25, 0.8, 0.25, 1)`)
Expand Down
4 changes: 2 additions & 2 deletions src/lib/select/select.html
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
<div class="md-select-trigger" overlay-origin (click)="toggle()" #origin="overlayOrigin" #trigger>
<span class="md-select-placeholder" [class.md-floating-placeholder]="this.selected"
[@transformPlaceholder]="_placeholderState"> {{ placeholder }} </span>
[@transformPlaceholder]="_placeholderState" [style.width.px]="_selectedValueWidth"> {{ placeholder }} </span>
<span class="md-select-value" *ngIf="selected"> {{ selected?.viewValue }} </span>
<span class="md-select-arrow"></span>
</div>

<template connected-overlay [origin]="origin" [open]="panelOpen" hasBackdrop (backdropClick)="close()"
backdropClass="md-overlay-transparent-backdrop" [positions]="_positions" [width]="_getWidth()"
backdropClass="md-overlay-transparent-backdrop" [positions]="_positions" [minWidth]="_triggerWidth"
[offsetY]="_offsetY" [offsetX]="_offsetX" (attach)="_setScrollTop()">
<div class="md-select-panel" [@transformPanel]="'showing'" (@transformPanel.done)="_onPanelDone()"
(keydown)="_keyManager.onKeydown($event)" [style.transformOrigin]="_transformOrigin">
Expand Down
2 changes: 2 additions & 0 deletions src/lib/select/select.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
@import '../core/style/menu-common';
@import '../core/style/list-common';
@import '../core/style/form-common';
@import '../a11y/_a11y';

Expand Down Expand Up @@ -63,6 +64,7 @@ md-select {

.md-select-value {
position: absolute;
@include md-truncate-line();

// Firefox and some versions of IE incorrectly keep absolutely
// positioned children of flex containers in the flex flow when calculating
Expand Down
75 changes: 68 additions & 7 deletions src/lib/select/select.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ describe('MdSelect', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [MdSelectModule.forRoot(), ReactiveFormsModule, FormsModule],
declarations: [BasicSelect, NgModelSelect, ManySelects],
declarations: [BasicSelect, NgModelSelect, ManySelects, NgIfSelect],
providers: [
{provide: OverlayContainer, useFactory: () => {
overlayContainerElement = document.createElement('div');
Expand Down Expand Up @@ -96,14 +96,16 @@ describe('MdSelect', () => {
});
}));

it('should set the width of the overlay based on the trigger', () => {
it('should set the width of the overlay based on the trigger', async(() => {
trigger.style.width = '200px';
trigger.click();
fixture.detectChanges();

const pane = overlayContainerElement.children[0] as HTMLElement;
expect(pane.style.width).toBe('200px');
});
fixture.whenStable().then(() => {
trigger.click();
fixture.detectChanges();
const pane = overlayContainerElement.children[0] as HTMLElement;
expect(pane.style.minWidth).toBe('200px');
});
}));

});

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

});

describe('special cases', () => {

it('should handle nesting in an ngIf', async(() => {
const fixture = TestBed.createComponent(NgIfSelect);
fixture.detectChanges();

fixture.componentInstance.isShowing = true;
fixture.detectChanges();

const trigger = fixture.debugElement.query(By.css('.md-select-trigger')).nativeElement;
trigger.style.width = '300px';

fixture.whenStable().then(() => {
fixture.detectChanges();
const value = fixture.debugElement.query(By.css('.md-select-value'));
expect(value.nativeElement.textContent)
.toContain('Pizza', `Expected trigger to be populated by the control's initial value.`);

trigger.click();
fixture.detectChanges();

const pane = overlayContainerElement.children[0] as HTMLElement;
expect(pane.style.minWidth).toEqual('300px');

expect(fixture.componentInstance.select.panelOpen).toBe(true);
expect(overlayContainerElement.textContent).toContain('Steak');
expect(overlayContainerElement.textContent).toContain('Pizza');
expect(overlayContainerElement.textContent).toContain('Tacos');
});
}));

});

});

@Component({
Expand Down Expand Up @@ -1062,6 +1097,32 @@ class NgModelSelect {
})
class ManySelects {}

@Component({
selector: 'ng-if-select',
template: `
<div *ngIf="isShowing">
<md-select placeholder="Food I want to eat right now" [formControl]="control">
<md-option *ngFor="let food of foods" [value]="food.value">
{{ food.viewValue }}
</md-option>
</md-select>
</div>
`

})
class NgIfSelect {
isShowing = false;
foods: any[] = [
{ value: 'steak-0', viewValue: 'Steak' },
{ value: 'pizza-1', viewValue: 'Pizza' },
{ value: 'tacos-2', viewValue: 'Tacos'}
];
control = new FormControl('pizza-1');

@ViewChild(MdSelect) select: MdSelect;
}



/**
* TODO: Move this to core testing utility until Angular has event faking
Expand Down
37 changes: 36 additions & 1 deletion src/lib/select/select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,24 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr
/** The scroll position of the overlay panel, calculated to center the selected option. */
private _scrollTop = 0;

/** The placeholder displayed in the trigger of the select. */
private _placeholder: string;

/** The animation state of the placeholder. */
_placeholderState = '';

/**
* The width of the trigger. Must be saved to set the min width of the overlay panel
* and the width of the selected value.
*/
_triggerWidth: number;

/**
* The width of the selected option's value. Must be set programmatically
* to ensure its overflow is clipped, as it's absolutely positioned.
*/
_selectedValueWidth: number;

/** Manages keyboard events for options in the panel. */
_keyManager: ListKeyManager;

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

@Input() placeholder: string;
@Input()
get placeholder() {
return this._placeholder;
}

set placeholder(value: string) {
this._placeholder = value;

// Must wait to record the trigger width to ensure placeholder width is included.
Promise.resolve(null).then(() => this._triggerWidth = this._getWidth());
}

@Input()
get disabled() {
Expand Down Expand Up @@ -400,6 +425,7 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr
private _onSelect(option: MdOption): void {
this._selected = option;
this._updateOptions();
this._setValueWidth();
if (this.panelOpen) {
this.close();
}
Expand All @@ -414,6 +440,15 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr
});
}

/**
* Must set the width of the selected option's value programmatically
* because it is absolutely positioned and otherwise will not clip
* overflow. The selection arrow is 9px wide, add 4px of padding = 13
*/
private _setValueWidth() {
this._selectedValueWidth = this._triggerWidth - 13;
}

/** Focuses the selected item. If no option is selected, it will focus
* the first item instead.
*/
Expand Down

0 comments on commit 03e90db

Please sign in to comment.