From 1377f27987fb4e37d138b426aa698ccd2e5f6afc Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Thu, 1 Oct 2020 13:49:50 -0700 Subject: [PATCH 1/2] feat(material/select): add a global option to specify overlay panel class --- .../mdc-select/select.html | 1 + .../mdc-select/select.spec.ts | 9 +++++++-- src/material-experimental/mdc-select/select.ts | 2 ++ src/material/select/select.html | 1 + src/material/select/select.spec.ts | 9 +++++++-- src/material/select/select.ts | 17 +++++++++++------ tools/public_api_guard/material/select.d.ts | 5 ++++- 7 files changed, 33 insertions(+), 11 deletions(-) diff --git a/src/material-experimental/mdc-select/select.html b/src/material-experimental/mdc-select/select.html index d0492b26de7c..9a97b630dde6 100644 --- a/src/material-experimental/mdc-select/select.html +++ b/src/material-experimental/mdc-select/select.html @@ -19,6 +19,7 @@ cdkConnectedOverlayLockPosition cdkConnectedOverlayHasBackdrop cdkConnectedOverlayBackdropClass="cdk-overlay-transparent-backdrop" + [cdkConnectedOverlayPanelClass]="_overlayPanelClass" [cdkConnectedOverlayScrollStrategy]="_scrollStrategy" [cdkConnectedOverlayOrigin]="_preferredOverlayOrigin || fallbackOverlayOrigin" [cdkConnectedOverlayOpen]="panelOpen" diff --git a/src/material-experimental/mdc-select/select.spec.ts b/src/material-experimental/mdc-select/select.spec.ts index d98eb49e532f..5d8367d1ccd3 100644 --- a/src/material-experimental/mdc-select/select.spec.ts +++ b/src/material-experimental/mdc-select/select.spec.ts @@ -3732,20 +3732,25 @@ describe('MDC-based MatSelect', () => { }); - it('should be able to provide default values through an injection token', () => { + it('should be able to provide default values through an injection token', async () => { configureMatSelectTestingModule([NgModelSelect], [{ provide: MAT_SELECT_CONFIG, useValue: { disableOptionCentering: true, - typeaheadDebounceInterval: 1337 + typeaheadDebounceInterval: 1337, + overlayPanelClass: 'test-panel-class', } as MatSelectConfig }]); const fixture = TestBed.createComponent(NgModelSelect); fixture.detectChanges(); const select = fixture.componentInstance.select; + select.open(); + fixture.detectChanges(); + await fixture.whenStable(); expect(select.disableOptionCentering).toBe(true); expect(select.typeaheadDebounceInterval).toBe(1337); + expect(document.querySelector('.cdk-overlay-pane')?.classList).toContain('test-panel-class'); }); it('should not not throw if the select is inside an ng-container with ngIf', fakeAsync(() => { diff --git a/src/material-experimental/mdc-select/select.ts b/src/material-experimental/mdc-select/select.ts index 7280d9eb6643..91e0aaa30f0a 100644 --- a/src/material-experimental/mdc-select/select.ts +++ b/src/material-experimental/mdc-select/select.ts @@ -112,6 +112,8 @@ export class MatSelect extends _MatSelectBase implements OnInit /** Width of the overlay panel. */ _overlayWidth: number; + _overlayPanelClass: string | string[] = this._defaultOptions?.overlayPanelClass || ''; + get shouldLabelFloat(): boolean { // Since the panel doesn't overlap the trigger, we // want the label to only float when there's a value. diff --git a/src/material/select/select.html b/src/material/select/select.html index 785edff07848..f6c93d004dca 100644 --- a/src/material/select/select.html +++ b/src/material/select/select.html @@ -19,6 +19,7 @@ cdkConnectedOverlayLockPosition cdkConnectedOverlayHasBackdrop cdkConnectedOverlayBackdropClass="cdk-overlay-transparent-backdrop" + [cdkConnectedOverlayPanelClass]="_overlayPanelClass" [cdkConnectedOverlayScrollStrategy]="_scrollStrategy" [cdkConnectedOverlayOrigin]="origin" [cdkConnectedOverlayOpen]="panelOpen" diff --git a/src/material/select/select.spec.ts b/src/material/select/select.spec.ts index 58549ae57ac8..16620666e9fb 100644 --- a/src/material/select/select.spec.ts +++ b/src/material/select/select.spec.ts @@ -4583,20 +4583,25 @@ describe('MatSelect', () => { }); - it('should be able to provide default values through an injection token', () => { + it('should be able to provide default values through an injection token', async () => { configureMatSelectTestingModule([NgModelSelect], [{ provide: MAT_SELECT_CONFIG, useValue: { disableOptionCentering: true, - typeaheadDebounceInterval: 1337 + typeaheadDebounceInterval: 1337, + overlayPanelClass: 'test-panel-class', } as MatSelectConfig }]); const fixture = TestBed.createComponent(NgModelSelect); fixture.detectChanges(); const select = fixture.componentInstance.select; + select.open(); + fixture.detectChanges(); + await fixture.whenStable(); expect(select.disableOptionCentering).toBe(true); expect(select.typeaheadDebounceInterval).toBe(1337); + expect(document.querySelector('.cdk-overlay-pane')?.classList).toContain('test-panel-class'); }); it('should not not throw if the select is inside an ng-container with ngIf', fakeAsync(() => { diff --git a/src/material/select/select.ts b/src/material/select/select.ts index b87ec57168b7..ff6973ab6cf9 100644 --- a/src/material/select/select.ts +++ b/src/material/select/select.ts @@ -179,6 +179,9 @@ export interface MatSelectConfig { /** Time to wait in milliseconds after the last keystroke before moving focus to an item. */ typeaheadDebounceInterval?: number; + + /** Class or list of classes to be applied to the menu's overlay panel. */ + overlayPanelClass?: string | string[]; } /** Injection token that can be used to provide the default options the select module. */ @@ -475,6 +478,8 @@ export abstract class _MatSelectBase extends _MatSelectMixinBase implements A */ @Output() readonly valueChange: EventEmitter = new EventEmitter(); + _overlayPanelClass: string | string[] = this._defaultOptions?.overlayPanelClass || ''; + constructor( protected _viewportRuler: ViewportRuler, protected _changeDetectorRef: ChangeDetectorRef, @@ -489,7 +494,7 @@ export abstract class _MatSelectBase extends _MatSelectMixinBase implements A @Attribute('tabindex') tabIndex: string, @Inject(MAT_SELECT_SCROLL_STRATEGY) scrollStrategyFactory: any, private _liveAnnouncer: LiveAnnouncer, - @Optional() @Inject(MAT_SELECT_CONFIG) defaults?: MatSelectConfig) { + @Optional() @Inject(MAT_SELECT_CONFIG) protected _defaultOptions?: MatSelectConfig) { super(elementRef, _defaultErrorStateMatcher, _parentForm, _parentFormGroup, ngControl); @@ -506,13 +511,13 @@ export abstract class _MatSelectBase extends _MatSelectMixinBase implements A // Force setter to be called in case id was not specified. this.id = this.id; - if (defaults) { - if (defaults.disableOptionCentering != null) { - this.disableOptionCentering = defaults.disableOptionCentering; + if (_defaultOptions) { + if (_defaultOptions.disableOptionCentering != null) { + this.disableOptionCentering = _defaultOptions.disableOptionCentering; } - if (defaults.typeaheadDebounceInterval != null) { - this.typeaheadDebounceInterval = defaults.typeaheadDebounceInterval; + if (_defaultOptions.typeaheadDebounceInterval != null) { + this.typeaheadDebounceInterval = _defaultOptions.typeaheadDebounceInterval; } } } diff --git a/tools/public_api_guard/material/select.d.ts b/tools/public_api_guard/material/select.d.ts index d5f54035b08e..646c8208f609 100644 --- a/tools/public_api_guard/material/select.d.ts +++ b/tools/public_api_guard/material/select.d.ts @@ -2,12 +2,14 @@ export declare abstract class _MatSelectBase extends _MatSelectMixinBase impl _ariaDescribedby: string; protected _changeDetectorRef: ChangeDetectorRef; readonly _closedStream: Observable; + protected _defaultOptions?: MatSelectConfig | undefined; protected readonly _destroy: Subject; _keyManager: ActiveDescendantKeyManager; protected _ngZone: NgZone; _onChange: (value: any) => void; _onTouched: () => void; readonly _openedStream: Observable; + _overlayPanelClass: string | string[]; _panelDoneAnimatingStream: Subject; protected _parentFormField: MatFormField; abstract _positions: ConnectedPosition[]; @@ -56,7 +58,7 @@ export declare abstract class _MatSelectBase extends _MatSelectMixinBase impl get value(): any; set value(newValue: any); readonly valueChange: EventEmitter; - constructor(_viewportRuler: ViewportRuler, _changeDetectorRef: ChangeDetectorRef, _ngZone: NgZone, _defaultErrorStateMatcher: ErrorStateMatcher, elementRef: ElementRef, _dir: Directionality, _parentForm: NgForm, _parentFormGroup: FormGroupDirective, _parentFormField: MatFormField, ngControl: NgControl, tabIndex: string, scrollStrategyFactory: any, _liveAnnouncer: LiveAnnouncer, defaults?: MatSelectConfig); + constructor(_viewportRuler: ViewportRuler, _changeDetectorRef: ChangeDetectorRef, _ngZone: NgZone, _defaultErrorStateMatcher: ErrorStateMatcher, elementRef: ElementRef, _dir: Directionality, _parentForm: NgForm, _parentFormGroup: FormGroupDirective, _parentFormField: MatFormField, ngControl: NgControl, tabIndex: string, scrollStrategyFactory: any, _liveAnnouncer: LiveAnnouncer, _defaultOptions?: MatSelectConfig | undefined); protected _canOpen(): boolean; _getAriaActiveDescendant(): string | null; protected abstract _getChangeEvent(value: any): C; @@ -145,6 +147,7 @@ export declare class MatSelectChange { export interface MatSelectConfig { disableOptionCentering?: boolean; + overlayPanelClass?: string | string[]; typeaheadDebounceInterval?: number; } From 7118aad70ece6f5aeb0ef01c46da3463e0efa92f Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Thu, 1 Oct 2020 14:37:34 -0700 Subject: [PATCH 2/2] fix(material/select): address comments --- .../mdc-select/select.spec.ts | 6 +++--- .../mdc-select/select.ts | 2 -- src/material/select/select.spec.ts | 6 +++--- src/material/select/select.ts | 20 +++++-------------- tools/public_api_guard/material/select.d.ts | 1 - 5 files changed, 11 insertions(+), 24 deletions(-) diff --git a/src/material-experimental/mdc-select/select.spec.ts b/src/material-experimental/mdc-select/select.spec.ts index 5d8367d1ccd3..fd90951b73df 100644 --- a/src/material-experimental/mdc-select/select.spec.ts +++ b/src/material-experimental/mdc-select/select.spec.ts @@ -3732,7 +3732,7 @@ describe('MDC-based MatSelect', () => { }); - it('should be able to provide default values through an injection token', async () => { + it('should be able to provide default values through an injection token', fakeAsync(() => { configureMatSelectTestingModule([NgModelSelect], [{ provide: MAT_SELECT_CONFIG, useValue: { @@ -3746,12 +3746,12 @@ describe('MDC-based MatSelect', () => { const select = fixture.componentInstance.select; select.open(); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(select.disableOptionCentering).toBe(true); expect(select.typeaheadDebounceInterval).toBe(1337); expect(document.querySelector('.cdk-overlay-pane')?.classList).toContain('test-panel-class'); - }); + })); it('should not not throw if the select is inside an ng-container with ngIf', fakeAsync(() => { configureMatSelectTestingModule([SelectInNgContainer]); diff --git a/src/material-experimental/mdc-select/select.ts b/src/material-experimental/mdc-select/select.ts index 91e0aaa30f0a..7280d9eb6643 100644 --- a/src/material-experimental/mdc-select/select.ts +++ b/src/material-experimental/mdc-select/select.ts @@ -112,8 +112,6 @@ export class MatSelect extends _MatSelectBase implements OnInit /** Width of the overlay panel. */ _overlayWidth: number; - _overlayPanelClass: string | string[] = this._defaultOptions?.overlayPanelClass || ''; - get shouldLabelFloat(): boolean { // Since the panel doesn't overlap the trigger, we // want the label to only float when there's a value. diff --git a/src/material/select/select.spec.ts b/src/material/select/select.spec.ts index 16620666e9fb..dd985c16e1b2 100644 --- a/src/material/select/select.spec.ts +++ b/src/material/select/select.spec.ts @@ -4583,7 +4583,7 @@ describe('MatSelect', () => { }); - it('should be able to provide default values through an injection token', async () => { + it('should be able to provide default values through an injection token', fakeAsync(() => { configureMatSelectTestingModule([NgModelSelect], [{ provide: MAT_SELECT_CONFIG, useValue: { @@ -4597,12 +4597,12 @@ describe('MatSelect', () => { const select = fixture.componentInstance.select; select.open(); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(select.disableOptionCentering).toBe(true); expect(select.typeaheadDebounceInterval).toBe(1337); expect(document.querySelector('.cdk-overlay-pane')?.classList).toContain('test-panel-class'); - }); + })); it('should not not throw if the select is inside an ng-container with ngIf', fakeAsync(() => { configureMatSelectTestingModule([SelectInNgContainer]); diff --git a/src/material/select/select.ts b/src/material/select/select.ts index ff6973ab6cf9..a83d6313cf08 100644 --- a/src/material/select/select.ts +++ b/src/material/select/select.ts @@ -314,6 +314,8 @@ export abstract class _MatSelectBase extends _MatSelectMixinBase implements A /** Strategy that will be used to handle scrolling while the select panel is open. */ _scrollStrategy: ScrollStrategy; + _overlayPanelClass: string | string[] = this._defaultOptions?.overlayPanelClass || ''; + /** Whether the select is focused. */ get focused(): boolean { return this._focused || this._panelOpen; @@ -376,7 +378,7 @@ export abstract class _MatSelectBase extends _MatSelectMixinBase implements A set disableOptionCentering(value: boolean) { this._disableOptionCentering = coerceBooleanProperty(value); } - private _disableOptionCentering: boolean = false; + private _disableOptionCentering = this._defaultOptions?.disableOptionCentering ?? false; /** * Function to compare the option values with the selected values. The first argument @@ -425,7 +427,7 @@ export abstract class _MatSelectBase extends _MatSelectMixinBase implements A set typeaheadDebounceInterval(value: number) { this._typeaheadDebounceInterval = coerceNumberProperty(value); } - private _typeaheadDebounceInterval: number; + private _typeaheadDebounceInterval = this._defaultOptions?.typeaheadDebounceInterval ?? 0; /** * Function used to sort the values in a select in multiple mode. @@ -478,8 +480,6 @@ export abstract class _MatSelectBase extends _MatSelectMixinBase implements A */ @Output() readonly valueChange: EventEmitter = new EventEmitter(); - _overlayPanelClass: string | string[] = this._defaultOptions?.overlayPanelClass || ''; - constructor( protected _viewportRuler: ViewportRuler, protected _changeDetectorRef: ChangeDetectorRef, @@ -494,7 +494,7 @@ export abstract class _MatSelectBase extends _MatSelectMixinBase implements A @Attribute('tabindex') tabIndex: string, @Inject(MAT_SELECT_SCROLL_STRATEGY) scrollStrategyFactory: any, private _liveAnnouncer: LiveAnnouncer, - @Optional() @Inject(MAT_SELECT_CONFIG) protected _defaultOptions?: MatSelectConfig) { + @Optional() @Inject(MAT_SELECT_CONFIG) private _defaultOptions?: MatSelectConfig) { super(elementRef, _defaultErrorStateMatcher, _parentForm, _parentFormGroup, ngControl); @@ -510,16 +510,6 @@ export abstract class _MatSelectBase extends _MatSelectMixinBase implements A // Force setter to be called in case id was not specified. this.id = this.id; - - if (_defaultOptions) { - if (_defaultOptions.disableOptionCentering != null) { - this.disableOptionCentering = _defaultOptions.disableOptionCentering; - } - - if (_defaultOptions.typeaheadDebounceInterval != null) { - this.typeaheadDebounceInterval = _defaultOptions.typeaheadDebounceInterval; - } - } } ngOnInit() { diff --git a/tools/public_api_guard/material/select.d.ts b/tools/public_api_guard/material/select.d.ts index 646c8208f609..1375d4105fe8 100644 --- a/tools/public_api_guard/material/select.d.ts +++ b/tools/public_api_guard/material/select.d.ts @@ -2,7 +2,6 @@ export declare abstract class _MatSelectBase extends _MatSelectMixinBase impl _ariaDescribedby: string; protected _changeDetectorRef: ChangeDetectorRef; readonly _closedStream: Observable; - protected _defaultOptions?: MatSelectConfig | undefined; protected readonly _destroy: Subject; _keyManager: ActiveDescendantKeyManager; protected _ngZone: NgZone;