mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
fix(select): fix width setting under ngIf (#2065)
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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)`)
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user