fix(select): fix width setting under ngIf (#2065)

This commit is contained in:
Kara
2016-12-06 19:15:49 -08:00
committed by mmalerba
parent 27f9c99ad5
commit 03e90dbcbc
7 changed files with 126 additions and 23 deletions
+15 -11
View File
@@ -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"
+1
View File
@@ -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');
+2 -2
View File
@@ -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)`)
+2 -2
View File
@@ -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">
+2
View File
@@ -1,4 +1,5 @@
@import '../core/style/menu-common';
@import '../core/style/list-common';
@import '../core/style/form-common';
@import '../a11y/_a11y';
@@ -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
+68 -7
View File
@@ -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');
@@ -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');
});
}));
});
@@ -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({
@@ -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
+36 -1
View File
@@ -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;
@@ -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() {
@@ -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();
}
@@ -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.
*/