Skip to content

Commit

Permalink
feat(autocomplete): add input for adding classes to the panel
Browse files Browse the repository at this point in the history
implemented simalar to select's panel class feature. fixes angular#4196.
  • Loading branch information
davidreher committed Aug 24, 2017
1 parent 3b46360 commit d0d7fda
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 128 deletions.
9 changes: 8 additions & 1 deletion src/lib/autocomplete/autocomplete.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
<ng-template>
<div class="mat-autocomplete-panel" role="listbox" [id]="id" [ngClass]="_getClassList()" #panel>
<div
class="mat-autocomplete-panel"
role="listbox"
[id]="id"
[ngClass]="panelClass"
[class.mat-autocomplete-visible]="showPanel"
[class.mat-autocomplete-hidden]="!showPanel"
#panel>
<ng-content></ng-content>
</div>
</ng-template>
16 changes: 15 additions & 1 deletion src/lib/autocomplete/autocomplete.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,19 @@ describe('MdAutocomplete', () => {
});
}));

it('should be able to set extra classes on the panel', () => {
fixture.componentInstance.trigger.openPanel();
fixture.whenStable().then(() => {
fixture.detectChanges();

const panel =
overlayContainerElement.querySelector('.mat-autocomplete-panel') as HTMLElement;

expect(panel.classList).toContain('custom-one');
expect(panel.classList).toContain('custom-two');
});
});

it('should keep the label floating until the panel closes', async(() => {
fixture.componentInstance.trigger.openPanel();
expect(fixture.componentInstance.formField.floatPlaceholder)
Expand Down Expand Up @@ -1607,7 +1620,7 @@ describe('MdAutocomplete', () => {
<input mdInput placeholder="State" [mdAutocomplete]="auto" [formControl]="stateCtrl">
</md-form-field>
<md-autocomplete #auto="mdAutocomplete" [displayWith]="displayFn">
<md-autocomplete #auto="mdAutocomplete" [displayWith]="displayFn" [panelClass]="panelClass">
<md-option *ngFor="let state of filteredStates" [value]="state">
<span> {{ state.code }}: {{ state.name }} </span>
</md-option>
Expand All @@ -1620,6 +1633,7 @@ class SimpleAutocomplete implements OnDestroy {
valueSub: Subscription;
placeholder = 'auto';
width: number;
panelClass = ['custom-one', 'custom-two'];

@ViewChild(MdAutocompleteTrigger) trigger: MdAutocompleteTrigger;
@ViewChild(MdAutocomplete) panel: MdAutocomplete;
Expand Down
246 changes: 120 additions & 126 deletions src/lib/autocomplete/autocomplete.ts
Original file line number Diff line number Diff line change
@@ -1,126 +1,120 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import {
AfterContentInit,
Component,
ContentChildren,
ElementRef,
Input,
QueryList,
TemplateRef,
ViewChild,
ViewEncapsulation,
ChangeDetectorRef,
ChangeDetectionStrategy,
EventEmitter,
Output,
} from '@angular/core';
import {MdOption, MdOptgroup} from '../core';
import {ActiveDescendantKeyManager} from '@angular/cdk/a11y';


/**
* Autocomplete IDs need to be unique across components, so this counter exists outside of
* the component definition.
*/
let _uniqueAutocompleteIdCounter = 0;

/** Event object that is emitted when an autocomplete option is selected */
export class MdAutocompleteSelectedEvent {
constructor(public source: MdAutocomplete, public option: MdOption) { }
}


@Component({
moduleId: module.id,
selector: 'md-autocomplete, mat-autocomplete',
templateUrl: 'autocomplete.html',
styleUrls: ['autocomplete.css'],
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
exportAs: 'mdAutocomplete',
host: {
'class': 'mat-autocomplete'
}
})
export class MdAutocomplete implements AfterContentInit {

/** Manages active item in option list based on key events. */
_keyManager: ActiveDescendantKeyManager<MdOption>;

/** Whether the autocomplete panel should be visible, depending on option length. */
showPanel = false;

/** @docs-private */
@ViewChild(TemplateRef) template: TemplateRef<any>;

/** Element for the panel containing the autocomplete options. */
@ViewChild('panel') panel: ElementRef;

/** @docs-private */
@ContentChildren(MdOption, { descendants: true }) options: QueryList<MdOption>;

/** @docs-private */
@ContentChildren(MdOptgroup) optionGroups: QueryList<MdOptgroup>;

/** Function that maps an option's control value to its display value in the trigger. */
@Input() displayWith: ((value: any) => string) | null = null;

/** Event that is emitted whenever an option from the list is selected. */
@Output() optionSelected: EventEmitter<MdAutocompleteSelectedEvent> =
new EventEmitter<MdAutocompleteSelectedEvent>();

/** Unique ID to be used by autocomplete trigger's "aria-owns" property. */
id: string = `md-autocomplete-${_uniqueAutocompleteIdCounter++}`;

constructor(private _changeDetectorRef: ChangeDetectorRef) { }

ngAfterContentInit() {
this._keyManager = new ActiveDescendantKeyManager<MdOption>(this.options).withWrap();
}

/**
* Sets the panel scrollTop. This allows us to manually scroll to display options
* above or below the fold, as they are not actually being focused when active.
*/
_setScrollTop(scrollTop: number): void {
if (this.panel) {
this.panel.nativeElement.scrollTop = scrollTop;
}
}

/** Returns the panel's scrollTop. */
_getScrollTop(): number {
return this.panel ? this.panel.nativeElement.scrollTop : 0;
}

/** Panel should hide itself when the option list is empty. */
_setVisibility(): void {
Promise.resolve().then(() => {
this.showPanel = !!this.options.length;
this._changeDetectorRef.markForCheck();
});
}

/** Emits the `select` event. */
_emitSelectEvent(option: MdOption): void {
const event = new MdAutocompleteSelectedEvent(this, option);
this.optionSelected.emit(event);
}

/** Sets a class on the panel based on whether it is visible. */
_getClassList() {
return {
'mat-autocomplete-visible': this.showPanel,
'mat-autocomplete-hidden': !this.showPanel
};
}

}

/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import {
AfterContentInit,
Component,
ContentChildren,
ElementRef,
Input,
QueryList,
TemplateRef,
ViewChild,
ViewEncapsulation,
ChangeDetectorRef,
ChangeDetectionStrategy,
EventEmitter,
Output,
} from '@angular/core';
import {MdOption, MdOptgroup} from '../core';
import {ActiveDescendantKeyManager} from '@angular/cdk/a11y';


/**
* Autocomplete IDs need to be unique across components, so this counter exists outside of
* the component definition.
*/
let _uniqueAutocompleteIdCounter = 0;

/** Event object that is emitted when an autocomplete option is selected */
export class MdAutocompleteSelectedEvent {
constructor(public source: MdAutocomplete, public option: MdOption) { }
}


@Component({
moduleId: module.id,
selector: 'md-autocomplete, mat-autocomplete',
templateUrl: 'autocomplete.html',
styleUrls: ['autocomplete.css'],
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
exportAs: 'mdAutocomplete',
host: {
'class': 'mat-autocomplete'
}
})
export class MdAutocomplete implements AfterContentInit {

/** Manages active item in option list based on key events. */
_keyManager: ActiveDescendantKeyManager<MdOption>;

/** Whether the autocomplete panel should be visible, depending on option length. */
showPanel = false;

/** @docs-private */
@ViewChild(TemplateRef) template: TemplateRef<any>;

/** Element for the panel containing the autocomplete options. */
@ViewChild('panel') panel: ElementRef;

/** @docs-private */
@ContentChildren(MdOption, { descendants: true }) options: QueryList<MdOption>;

/** @docs-private */
@ContentChildren(MdOptgroup) optionGroups: QueryList<MdOptgroup>;

/** Function that maps an option's control value to its display value in the trigger. */
@Input() displayWith: ((value: any) => string) | null = null;

/** Event that is emitted whenever an option from the list is selected. */
@Output() optionSelected: EventEmitter<MdAutocompleteSelectedEvent> =
new EventEmitter<MdAutocompleteSelectedEvent>();

/** Classes to be passed to the select panel. Supports the same syntax as `ngClass`. */
@Input() panelClass: string|string[]|Set<string>|{[key: string]: any};

/** Unique ID to be used by autocomplete trigger's "aria-owns" property. */
id: string = `md-autocomplete-${_uniqueAutocompleteIdCounter++}`;

constructor(private _changeDetectorRef: ChangeDetectorRef) { }

ngAfterContentInit() {
this._keyManager = new ActiveDescendantKeyManager<MdOption>(this.options).withWrap();
}

/**
* Sets the panel scrollTop. This allows us to manually scroll to display options
* above or below the fold, as they are not actually being focused when active.
*/
_setScrollTop(scrollTop: number): void {
if (this.panel) {
this.panel.nativeElement.scrollTop = scrollTop;
}
}

/** Returns the panel's scrollTop. */
_getScrollTop(): number {
return this.panel ? this.panel.nativeElement.scrollTop : 0;
}

/** Panel should hide itself when the option list is empty. */
_setVisibility(): void {
Promise.resolve().then(() => {
this.showPanel = !!this.options.length;
this._changeDetectorRef.markForCheck();
});
}

/** Emits the `select` event. */
_emitSelectEvent(option: MdOption): void {
const event = new MdAutocompleteSelectedEvent(this, option);
this.optionSelected.emit(event);
}

}

0 comments on commit d0d7fda

Please sign in to comment.