From a4bb14c78b79b71770aade805490f96309b1269d Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Sun, 28 May 2023 10:15:04 +0200 Subject: [PATCH] feat(material/select): add input to control the panel width Adds an input that allows users to control the width of the panel. Fixes #26000. --- src/material/legacy-select/public-api.ts | 13 +++--- src/material/legacy-select/select.ts | 8 +++- src/material/select/select.spec.ts | 42 ++++++++++++++++++- src/material/select/select.ts | 33 +++++++++++---- .../material/legacy-select.md | 5 ++- tools/public_api_guard/material/select.md | 6 ++- 6 files changed, 87 insertions(+), 20 deletions(-) diff --git a/src/material/legacy-select/public-api.ts b/src/material/legacy-select/public-api.ts index 8bf62320d912..75fe8a8b1b1f 100644 --- a/src/material/legacy-select/public-api.ts +++ b/src/material/legacy-select/public-api.ts @@ -8,7 +8,12 @@ export {MatLegacySelectModule} from './select-module'; export {matLegacySelectAnimations} from './select-animations'; -export {MatLegacySelectChange, MatLegacySelect, MatLegacySelectTrigger} from './select'; +export { + MatLegacySelectChange, + MatLegacySelect, + MatLegacySelectTrigger, + MatLegacySelectConfig, +} from './select'; export { /** @@ -40,10 +45,4 @@ export { * @breaking-change 17.0.0 */ MAT_SELECT_TRIGGER as MAT_LEGACY_SELECT_TRIGGER, - - /** - * @deprecated Use `MatSelectConfig` from `@angular/material/select` instead. See https://material.angular.io/guide/mdc-migration for information about migrating. - * @breaking-change 17.0.0 - */ - MatSelectConfig as MatLegacySelectConfig, } from '@angular/material/select'; diff --git a/src/material/legacy-select/select.ts b/src/material/legacy-select/select.ts index 84a018e74ccd..c9a407b720f1 100644 --- a/src/material/legacy-select/select.ts +++ b/src/material/legacy-select/select.ts @@ -25,7 +25,7 @@ import { MatLegacyOption, MatLegacyOptgroup, } from '@angular/material/legacy-core'; -import {MAT_SELECT_TRIGGER, _MatSelectBase} from '@angular/material/select'; +import {MAT_SELECT_TRIGGER, _MatSelectBase, MatSelectConfig} from '@angular/material/select'; import {MatLegacyFormFieldControl} from '@angular/material/legacy-form-field'; import {take, takeUntil} from 'rxjs/operators'; import {matLegacySelectAnimations} from './select-animations'; @@ -101,6 +101,12 @@ export class MatLegacySelectChange { ) {} } +/** + * @deprecated Use `MatSelectConfig` from `@angular/material/select` instead. See https://material.angular.io/guide/mdc-migration for information about migrating. + * @breaking-change 17.0.0 + */ +export type MatLegacySelectConfig = Omit; + /** * Allows the user to customize the trigger that is displayed when the select has a value. * @deprecated Use `MatSelectTrigger` from `@angular/material/select` instead. See https://material.angular.io/guide/mdc-migration for information about migrating. diff --git a/src/material/select/select.spec.ts b/src/material/select/select.spec.ts index 9ee41e78d9f6..87fe1e9ae4d8 100644 --- a/src/material/select/select.spec.ts +++ b/src/material/select/select.spec.ts @@ -1524,6 +1524,42 @@ describe('MDC-based MatSelect', () => { expect(parseInt(pane.style.width || '0')).toBeGreaterThan(initialWidth); })); + it('should be able to set a custom width on the select panel', fakeAsync(() => { + fixture.componentInstance.panelWidth = '42px'; + fixture.detectChanges(); + + trigger.click(); + fixture.detectChanges(); + flush(); + + const pane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement; + expect(pane.style.width).toBe('42px'); + })); + + it('should not set a width on the panel if panelWidth is null', fakeAsync(() => { + fixture.componentInstance.panelWidth = null; + fixture.detectChanges(); + + trigger.click(); + fixture.detectChanges(); + flush(); + + const pane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement; + expect(pane.style.width).toBeFalsy(); + })); + + it('should not set a width on the panel if panelWidth is an empty string', fakeAsync(() => { + fixture.componentInstance.panelWidth = ''; + fixture.detectChanges(); + + trigger.click(); + fixture.detectChanges(); + flush(); + + const pane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement; + expect(pane.style.width).toBeFalsy(); + })); + it('should not attempt to open a select that does not have any options', fakeAsync(() => { fixture.componentInstance.foods = []; fixture.detectChanges(); @@ -4430,6 +4466,7 @@ describe('MDC-based MatSelect', () => { disableOptionCentering: true, typeaheadDebounceInterval: 1337, overlayPanelClass: 'test-panel-class', + panelWidth: null, } as MatSelectConfig, }, ], @@ -4444,6 +4481,7 @@ describe('MDC-based MatSelect', () => { expect(select.disableOptionCentering).toBe(true); expect(select.typeaheadDebounceInterval).toBe(1337); expect(document.querySelector('.cdk-overlay-pane')?.classList).toContain('test-panel-class'); + expect(select.panelWidth).toBeNull(); })); it('should be able to hide checkmark icon through an injection token', () => { @@ -4542,7 +4580,8 @@ describe('MDC-based MatSelect', () => { [tabIndex]="tabIndexOverride" [aria-describedby]="ariaDescribedBy" [aria-label]="ariaLabel" [aria-labelledby]="ariaLabelledby" [panelClass]="panelClass" [disableRipple]="disableRipple" - [typeaheadDebounceInterval]="typeaheadDebounceInterval"> + [typeaheadDebounceInterval]="typeaheadDebounceInterval" + [panelWidth]="panelWidth"> {{ capitalize ? food.viewValue.toUpperCase() : food.viewValue }} @@ -4577,6 +4616,7 @@ class BasicSelect { disableRipple: boolean; typeaheadDebounceInterval: number; capitalize = false; + panelWidth: string | null | number = 'auto'; @ViewChild(MatSelect, {static: true}) select: MatSelect; @ViewChildren(MatOption) options: QueryList; diff --git a/src/material/select/select.ts b/src/material/select/select.ts index b9db8e89460b..86600d535a6c 100644 --- a/src/material/select/select.ts +++ b/src/material/select/select.ts @@ -138,6 +138,12 @@ export interface MatSelectConfig { /** Wheter icon indicators should be hidden for single-selection. */ hideSingleSelectionIndicator?: boolean; + + /** + * Width of the panel. If set to `auto`, the panel will match the trigger width. + * If set to null or an empty string, the panel will grow to match the longest option's text. + */ + panelWidth?: string | number | null; } /** Injection token that can be used to provide the default options the select module. */ @@ -1282,6 +1288,15 @@ export class MatSelect extends _MatSelectBase implements OnInit @ContentChildren(MAT_OPTGROUP, {descendants: true}) optionGroups: QueryList; @ContentChild(MAT_SELECT_TRIGGER) customTrigger: MatSelectTrigger; + /** + * Width of the panel. If set to `auto`, the panel will match the trigger width. + * If set to null or an empty string, the panel will grow to match the longest option's text. + */ + @Input() panelWidth: string | number | null = + this._defaultOptions && typeof this._defaultOptions.panelWidth !== 'undefined' + ? this._defaultOptions.panelWidth + : 'auto'; + _positions: ConnectedPosition[] = [ { originX: 'start', @@ -1302,7 +1317,7 @@ export class MatSelect extends _MatSelectBase implements OnInit _preferredOverlayOrigin: CdkOverlayOrigin | ElementRef | undefined; /** Width of the overlay panel. */ - _overlayWidth: number; + _overlayWidth: string | number; override get shouldLabelFloat(): boolean { // Since the panel doesn't overlap the trigger, we @@ -1378,12 +1393,16 @@ export class MatSelect extends _MatSelectBase implements OnInit } /** Gets how wide the overlay panel should be. */ - private _getOverlayWidth() { - const refToMeasure = - this._preferredOverlayOrigin instanceof CdkOverlayOrigin - ? this._preferredOverlayOrigin.elementRef - : this._preferredOverlayOrigin || this._elementRef; - return refToMeasure.nativeElement.getBoundingClientRect().width; + private _getOverlayWidth(): string | number { + if (this.panelWidth === 'auto') { + const refToMeasure = + this._preferredOverlayOrigin instanceof CdkOverlayOrigin + ? this._preferredOverlayOrigin.elementRef + : this._preferredOverlayOrigin || this._elementRef; + return refToMeasure.nativeElement.getBoundingClientRect().width; + } + + return this.panelWidth === null ? '' : this.panelWidth; } /** Whether checkmark indicator for single-selection options is hidden. */ diff --git a/tools/public_api_guard/material/legacy-select.md b/tools/public_api_guard/material/legacy-select.md index d96f4264c1ca..904d5fbfd923 100644 --- a/tools/public_api_guard/material/legacy-select.md +++ b/tools/public_api_guard/material/legacy-select.md @@ -20,8 +20,8 @@ import { MAT_SELECT_SCROLL_STRATEGY_PROVIDER_FACTORY as MAT_LEGACY_SELECT_SCROLL import { MAT_SELECT_TRIGGER as MAT_LEGACY_SELECT_TRIGGER } from '@angular/material/select'; import { MatLegacyOptgroup } from '@angular/material/legacy-core'; import { MatLegacyOption } from '@angular/material/legacy-core'; -import { MatSelectConfig as MatLegacySelectConfig } from '@angular/material/select'; import { _MatSelectBase } from '@angular/material/select'; +import { MatSelectConfig } from '@angular/material/select'; import { OnInit } from '@angular/core'; import { QueryList } from '@angular/core'; @@ -83,7 +83,8 @@ export class MatLegacySelectChange { value: any; } -export { MatLegacySelectConfig } +// @public @deprecated (undocumented) +export type MatLegacySelectConfig = Omit; // @public @deprecated (undocumented) export class MatLegacySelectModule { diff --git a/tools/public_api_guard/material/select.md b/tools/public_api_guard/material/select.md index 1b32acd4b892..a9eb62796514 100644 --- a/tools/public_api_guard/material/select.md +++ b/tools/public_api_guard/material/select.md @@ -95,7 +95,8 @@ export class MatSelect extends _MatSelectBase implements OnInit optionGroups: QueryList; // (undocumented) options: QueryList; - _overlayWidth: number; + _overlayWidth: string | number; + panelWidth: string | number | null; // (undocumented) protected _positioningSettled(): void; // (undocumented) @@ -108,7 +109,7 @@ export class MatSelect extends _MatSelectBase implements OnInit protected _skipPredicate: (option: MatOption) => boolean; _syncParentProperties(): void; // (undocumented) - static ɵcmp: i0.ɵɵComponentDeclaration; + static ɵcmp: i0.ɵɵComponentDeclaration; // (undocumented) static ɵfac: i0.ɵɵFactoryDeclaration; } @@ -242,6 +243,7 @@ export interface MatSelectConfig { disableOptionCentering?: boolean; hideSingleSelectionIndicator?: boolean; overlayPanelClass?: string | string[]; + panelWidth?: string | number | null; typeaheadDebounceInterval?: number; }