Skip to content

Commit 667a4e4

Browse files
willshowellmmalerba
authored andcommitted
fix(autocomplete): attach overlay to a more accurate input element (#6282)
* fix(autocomplete): attach overlay to a more accurate input element * Fix issue with transient prefix/suffix * Remove unneeded position change subscription
1 parent 48d3a0c commit 667a4e4

File tree

7 files changed

+22
-69
lines changed

7 files changed

+22
-69
lines changed

src/demo-app/autocomplete/autocomplete-demo.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,4 @@
5353
<md-option *ngFor="let state of tdStates" [value]="state.name">
5454
<span>{{ state.name }}</span>
5555
</md-option>
56-
</md-autocomplete>
56+
</md-autocomplete>

src/lib/autocomplete/autocomplete-trigger.ts

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,6 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
119119
private _portal: TemplatePortal;
120120
private _panelOpen: boolean = false;
121121

122-
/** The subscription to positioning changes in the autocomplete panel. */
123-
private _panelPositionSubscription: Subscription;
124-
125122
/** Strategy that is used to position the panel. */
126123
private _positionStrategy: ConnectedPositionStrategy;
127124

@@ -160,10 +157,6 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
160157
@Optional() @Inject(DOCUMENT) private _document: any) {}
161158

162159
ngOnDestroy() {
163-
if (this._panelPositionSubscription) {
164-
this._panelPositionSubscription.unsubscribe();
165-
}
166-
167160
this._destroyPanel();
168161
}
169162

@@ -467,28 +460,21 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
467460

468461
private _getOverlayPosition(): PositionStrategy {
469462
this._positionStrategy = this._overlay.position().connectedTo(
470-
this._element,
463+
this._getConnectedElement(),
471464
{originX: 'start', originY: 'bottom'}, {overlayX: 'start', overlayY: 'top'})
472465
.withFallbackPosition(
473466
{originX: 'start', originY: 'top'}, {overlayX: 'start', overlayY: 'bottom'}
474467
);
475-
this._subscribeToPositionChanges(this._positionStrategy);
476468
return this._positionStrategy;
477469
}
478470

479-
/**
480-
* This method subscribes to position changes in the autocomplete panel, so the panel's
481-
* y-offset can be adjusted to match the new position.
482-
*/
483-
private _subscribeToPositionChanges(strategy: ConnectedPositionStrategy) {
484-
this._panelPositionSubscription = strategy.onPositionChange.subscribe(change => {
485-
this.autocomplete.positionY = change.connectionPair.originY === 'top' ? 'above' : 'below';
486-
});
471+
private _getConnectedElement(): ElementRef {
472+
return this._inputContainer ? this._inputContainer._connectionContainerRef : this._element;
487473
}
488474

489475
/** Returns the width of the input element, so the panel width can match it. */
490476
private _getHostWidth(): number {
491-
return this._element.nativeElement.getBoundingClientRect().width;
477+
return this._getConnectedElement().nativeElement.getBoundingClientRect().width;
492478
}
493479

494480
/** Reset active item to -1 so arrow events will activate the correct options.*/

src/lib/autocomplete/autocomplete.scss

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,6 @@
66
*/
77
$mat-autocomplete-panel-max-height: 256px !default;
88

9-
/**
10-
* When in "below" position, the panel needs a slight
11-
* y-offset to ensure the input underline displays.
12-
*/
13-
$mat-autocomplete-panel-below-offset: 6px !default;
14-
15-
/**
16-
* When in "above" position, the panel needs a larger
17-
* y-offset to ensure the label has room to display.
18-
*/
19-
$mat-autocomplete-panel-above-offset: -24px !default;
20-
219
.mat-autocomplete-panel {
2210
@include mat-menu-base(8);
2311
visibility: hidden;
@@ -26,14 +14,6 @@ $mat-autocomplete-panel-above-offset: -24px !default;
2614
max-height: $mat-autocomplete-panel-max-height;
2715
position: relative;
2816

29-
&.mat-autocomplete-panel-below {
30-
top: $mat-autocomplete-panel-below-offset;
31-
}
32-
33-
&.mat-autocomplete-panel-above {
34-
top: $mat-autocomplete-panel-above-offset;
35-
}
36-
3717
&.mat-autocomplete-visible {
3818
visibility: visible;
3919
}

src/lib/autocomplete/autocomplete.spec.ts

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1055,27 +1055,26 @@ describe('MdAutocomplete', () => {
10551055
describe('Fallback positions', () => {
10561056
let fixture: ComponentFixture<SimpleAutocomplete>;
10571057
let input: HTMLInputElement;
1058+
let inputReference: HTMLInputElement;
10581059

10591060
beforeEach(() => {
10601061
fixture = TestBed.createComponent(SimpleAutocomplete);
10611062
fixture.detectChanges();
10621063

10631064
input = fixture.debugElement.query(By.css('input')).nativeElement;
1065+
inputReference = fixture.debugElement.query(By.css('.mat-input-flex')).nativeElement;
10641066
});
10651067

10661068
it('should use below positioning by default', () => {
10671069
fixture.componentInstance.trigger.openPanel();
10681070
fixture.detectChanges();
10691071

1070-
const inputBottom = input.getBoundingClientRect().bottom;
1072+
const inputBottom = inputReference.getBoundingClientRect().bottom;
10711073
const panel = overlayContainerElement.querySelector('.mat-autocomplete-panel')!;
10721074
const panelTop = panel.getBoundingClientRect().top;
10731075

1074-
// Panel is offset by 6px in styles so that the underline has room to display.
1075-
expect(Math.floor(inputBottom + 6))
1076+
expect(Math.floor(inputBottom))
10761077
.toEqual(Math.floor(panelTop), `Expected panel top to match input bottom by default.`);
1077-
expect(fixture.componentInstance.trigger.autocomplete.positionY)
1078-
.toEqual('below', `Expected autocomplete positionY to default to below.`);
10791078
});
10801079

10811080
it('should reposition the panel on scroll', () => {
@@ -1091,39 +1090,36 @@ describe('MdAutocomplete', () => {
10911090
scrolledSubject.next();
10921091
fixture.detectChanges();
10931092

1094-
const inputBottom = input.getBoundingClientRect().bottom;
1093+
const inputBottom = inputReference.getBoundingClientRect().bottom;
10951094
const panel = overlayContainerElement.querySelector('.mat-autocomplete-panel')!;
10961095
const panelTop = panel.getBoundingClientRect().top;
10971096

1098-
expect(Math.floor(inputBottom + 6)).toEqual(Math.floor(panelTop),
1097+
expect(Math.floor(inputBottom)).toEqual(Math.floor(panelTop),
10991098
'Expected panel top to match input bottom after scrolling.');
11001099

11011100
document.body.removeChild(spacer);
11021101
});
11031102

11041103
it('should fall back to above position if panel cannot fit below', () => {
11051104
// Push the autocomplete trigger down so it won't have room to open "below"
1106-
input.style.top = '600px';
1107-
input.style.position = 'relative';
1105+
inputReference.style.top = '600px';
1106+
inputReference.style.position = 'relative';
11081107

11091108
fixture.componentInstance.trigger.openPanel();
11101109
fixture.detectChanges();
11111110

1112-
const inputTop = input.getBoundingClientRect().top;
1111+
const inputTop = inputReference.getBoundingClientRect().top;
11131112
const panel = overlayContainerElement.querySelector('.mat-autocomplete-panel')!;
11141113
const panelBottom = panel.getBoundingClientRect().bottom;
11151114

1116-
// Panel is offset by 24px in styles so that the label has room to display.
1117-
expect(Math.floor(inputTop - 24))
1115+
expect(Math.floor(inputTop))
11181116
.toEqual(Math.floor(panelBottom), `Expected panel to fall back to above position.`);
1119-
expect(fixture.componentInstance.trigger.autocomplete.positionY)
1120-
.toEqual('above', `Expected autocomplete positionY to be "above" if panel won't fit.`);
11211117
});
11221118

11231119
it('should align panel properly when filtering in "above" position', async(() => {
11241120
// Push the autocomplete trigger down so it won't have room to open "below"
1125-
input.style.top = '600px';
1126-
input.style.position = 'relative';
1121+
inputReference.style.top = '600px';
1122+
inputReference.style.position = 'relative';
11271123

11281124
fixture.componentInstance.trigger.openPanel();
11291125
fixture.detectChanges();
@@ -1132,15 +1128,12 @@ describe('MdAutocomplete', () => {
11321128
typeInElement('f', input);
11331129
fixture.detectChanges();
11341130

1135-
const inputTop = input.getBoundingClientRect().top;
1131+
const inputTop = inputReference.getBoundingClientRect().top;
11361132
const panel = overlayContainerElement.querySelector('.mat-autocomplete-panel')!;
11371133
const panelBottom = panel.getBoundingClientRect().bottom;
11381134

1139-
// Panel is offset by 24px in styles so that the label has room to display.
1140-
expect(Math.floor(inputTop - 24))
1135+
expect(Math.floor(inputTop))
11411136
.toEqual(Math.floor(panelBottom), `Expected panel to stay aligned after filtering.`);
1142-
expect(fixture.componentInstance.trigger.autocomplete.positionY)
1143-
.toEqual('above', `Expected autocomplete positionY to be "above" if panel won't fit.`);
11441137
});
11451138
}));
11461139

src/lib/autocomplete/autocomplete.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,6 @@ import {ActiveDescendantKeyManager} from '../core/a11y/activedescendant-key-mana
2828
*/
2929
let _uniqueAutocompleteIdCounter = 0;
3030

31-
export type AutocompletePositionY = 'above' | 'below';
32-
3331
@Component({
3432
moduleId: module.id,
3533
selector: 'md-autocomplete, mat-autocomplete',
@@ -47,9 +45,6 @@ export class MdAutocomplete implements AfterContentInit {
4745
/** Manages active item in option list based on key events. */
4846
_keyManager: ActiveDescendantKeyManager;
4947

50-
/** Whether the autocomplete panel displays above or below its trigger. */
51-
positionY: AutocompletePositionY = 'below';
52-
5348
/** Whether the autocomplete panel should be visible, depending on option length. */
5449
showPanel = false;
5550

@@ -97,11 +92,9 @@ export class MdAutocomplete implements AfterContentInit {
9792
});
9893
}
9994

100-
/** Sets a class on the panel based on its position (used to set y-offset). */
95+
/** Sets a class on the panel based on whether it is visible. */
10196
_getClassList() {
10297
return {
103-
'mat-autocomplete-panel-below': this.positionY === 'below',
104-
'mat-autocomplete-panel-above': this.positionY === 'above',
10598
'mat-autocomplete-visible': this.showPanel,
10699
'mat-autocomplete-hidden': !this.showPanel
107100
};

src/lib/input/input-container.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<div class="mat-input-wrapper">
2-
<div class="mat-input-flex">
2+
<div class="mat-input-flex" #connectionContainer>
33
<div class="mat-input-prefix" *ngIf="_prefixChildren.length">
44
<ng-content select="[mdPrefix], [matPrefix]"></ng-content>
55
</div>

src/lib/input/input-container.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,7 @@ export class MdInputContainer implements AfterViewInit, AfterContentInit, AfterC
462462

463463
/** Reference to the input's underline element. */
464464
@ViewChild('underline') underlineRef: ElementRef;
465+
@ViewChild('connectionContainer') _connectionContainerRef: ElementRef;
465466
@ContentChild(MdInputDirective) _mdInputChild: MdInputDirective;
466467
@ContentChild(MdPlaceholder) _placeholderChild: MdPlaceholder;
467468
@ContentChildren(MdErrorDirective) _errorChildren: QueryList<MdErrorDirective>;

0 commit comments

Comments
 (0)