Skip to content

Commit 3703cc9

Browse files
authored
feat(material/select): add input to control the panel width (#27188)
Adds an input that allows users to control the width of the panel. Fixes #26000.
1 parent 1e5f885 commit 3703cc9

File tree

6 files changed

+87
-20
lines changed

6 files changed

+87
-20
lines changed

src/material/legacy-select/public-api.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,12 @@
88

99
export {MatLegacySelectModule} from './select-module';
1010
export {matLegacySelectAnimations} from './select-animations';
11-
export {MatLegacySelectChange, MatLegacySelect, MatLegacySelectTrigger} from './select';
11+
export {
12+
MatLegacySelectChange,
13+
MatLegacySelect,
14+
MatLegacySelectTrigger,
15+
MatLegacySelectConfig,
16+
} from './select';
1217

1318
export {
1419
/**
@@ -40,10 +45,4 @@ export {
4045
* @breaking-change 17.0.0
4146
*/
4247
MAT_SELECT_TRIGGER as MAT_LEGACY_SELECT_TRIGGER,
43-
44-
/**
45-
* @deprecated Use `MatSelectConfig` from `@angular/material/select` instead. See https://material.angular.io/guide/mdc-migration for information about migrating.
46-
* @breaking-change 17.0.0
47-
*/
48-
MatSelectConfig as MatLegacySelectConfig,
4948
} from '@angular/material/select';

src/material/legacy-select/select.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import {
2525
MatLegacyOption,
2626
MatLegacyOptgroup,
2727
} from '@angular/material/legacy-core';
28-
import {MAT_SELECT_TRIGGER, _MatSelectBase} from '@angular/material/select';
28+
import {MAT_SELECT_TRIGGER, _MatSelectBase, MatSelectConfig} from '@angular/material/select';
2929
import {MatLegacyFormFieldControl} from '@angular/material/legacy-form-field';
3030
import {take, takeUntil} from 'rxjs/operators';
3131
import {matLegacySelectAnimations} from './select-animations';
@@ -101,6 +101,12 @@ export class MatLegacySelectChange {
101101
) {}
102102
}
103103

104+
/**
105+
* @deprecated Use `MatSelectConfig` from `@angular/material/select` instead. See https://material.angular.io/guide/mdc-migration for information about migrating.
106+
* @breaking-change 17.0.0
107+
*/
108+
export type MatLegacySelectConfig = Omit<MatSelectConfig, 'panelWidth'>;
109+
104110
/**
105111
* Allows the user to customize the trigger that is displayed when the select has a value.
106112
* @deprecated Use `MatSelectTrigger` from `@angular/material/select` instead. See https://material.angular.io/guide/mdc-migration for information about migrating.

src/material/select/select.spec.ts

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1524,6 +1524,42 @@ describe('MDC-based MatSelect', () => {
15241524
expect(parseInt(pane.style.width || '0')).toBeGreaterThan(initialWidth);
15251525
}));
15261526

1527+
it('should be able to set a custom width on the select panel', fakeAsync(() => {
1528+
fixture.componentInstance.panelWidth = '42px';
1529+
fixture.detectChanges();
1530+
1531+
trigger.click();
1532+
fixture.detectChanges();
1533+
flush();
1534+
1535+
const pane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement;
1536+
expect(pane.style.width).toBe('42px');
1537+
}));
1538+
1539+
it('should not set a width on the panel if panelWidth is null', fakeAsync(() => {
1540+
fixture.componentInstance.panelWidth = null;
1541+
fixture.detectChanges();
1542+
1543+
trigger.click();
1544+
fixture.detectChanges();
1545+
flush();
1546+
1547+
const pane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement;
1548+
expect(pane.style.width).toBeFalsy();
1549+
}));
1550+
1551+
it('should not set a width on the panel if panelWidth is an empty string', fakeAsync(() => {
1552+
fixture.componentInstance.panelWidth = '';
1553+
fixture.detectChanges();
1554+
1555+
trigger.click();
1556+
fixture.detectChanges();
1557+
flush();
1558+
1559+
const pane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement;
1560+
expect(pane.style.width).toBeFalsy();
1561+
}));
1562+
15271563
it('should not attempt to open a select that does not have any options', fakeAsync(() => {
15281564
fixture.componentInstance.foods = [];
15291565
fixture.detectChanges();
@@ -4430,6 +4466,7 @@ describe('MDC-based MatSelect', () => {
44304466
disableOptionCentering: true,
44314467
typeaheadDebounceInterval: 1337,
44324468
overlayPanelClass: 'test-panel-class',
4469+
panelWidth: null,
44334470
} as MatSelectConfig,
44344471
},
44354472
],
@@ -4444,6 +4481,7 @@ describe('MDC-based MatSelect', () => {
44444481
expect(select.disableOptionCentering).toBe(true);
44454482
expect(select.typeaheadDebounceInterval).toBe(1337);
44464483
expect(document.querySelector('.cdk-overlay-pane')?.classList).toContain('test-panel-class');
4484+
expect(select.panelWidth).toBeNull();
44474485
}));
44484486

44494487
it('should be able to hide checkmark icon through an injection token', () => {
@@ -4542,7 +4580,8 @@ describe('MDC-based MatSelect', () => {
45424580
[tabIndex]="tabIndexOverride" [aria-describedby]="ariaDescribedBy"
45434581
[aria-label]="ariaLabel" [aria-labelledby]="ariaLabelledby"
45444582
[panelClass]="panelClass" [disableRipple]="disableRipple"
4545-
[typeaheadDebounceInterval]="typeaheadDebounceInterval">
4583+
[typeaheadDebounceInterval]="typeaheadDebounceInterval"
4584+
[panelWidth]="panelWidth">
45464585
<mat-option *ngFor="let food of foods" [value]="food.value" [disabled]="food.disabled">
45474586
{{ capitalize ? food.viewValue.toUpperCase() : food.viewValue }}
45484587
</mat-option>
@@ -4577,6 +4616,7 @@ class BasicSelect {
45774616
disableRipple: boolean;
45784617
typeaheadDebounceInterval: number;
45794618
capitalize = false;
4619+
panelWidth: string | null | number = 'auto';
45804620

45814621
@ViewChild(MatSelect, {static: true}) select: MatSelect;
45824622
@ViewChildren(MatOption) options: QueryList<MatOption>;

src/material/select/select.ts

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,12 @@ export interface MatSelectConfig {
138138

139139
/** Wheter icon indicators should be hidden for single-selection. */
140140
hideSingleSelectionIndicator?: boolean;
141+
142+
/**
143+
* Width of the panel. If set to `auto`, the panel will match the trigger width.
144+
* If set to null or an empty string, the panel will grow to match the longest option's text.
145+
*/
146+
panelWidth?: string | number | null;
141147
}
142148

143149
/** Injection token that can be used to provide the default options the select module. */
@@ -1282,6 +1288,15 @@ export class MatSelect extends _MatSelectBase<MatSelectChange> implements OnInit
12821288
@ContentChildren(MAT_OPTGROUP, {descendants: true}) optionGroups: QueryList<MatOptgroup>;
12831289
@ContentChild(MAT_SELECT_TRIGGER) customTrigger: MatSelectTrigger;
12841290

1291+
/**
1292+
* Width of the panel. If set to `auto`, the panel will match the trigger width.
1293+
* If set to null or an empty string, the panel will grow to match the longest option's text.
1294+
*/
1295+
@Input() panelWidth: string | number | null =
1296+
this._defaultOptions && typeof this._defaultOptions.panelWidth !== 'undefined'
1297+
? this._defaultOptions.panelWidth
1298+
: 'auto';
1299+
12851300
_positions: ConnectedPosition[] = [
12861301
{
12871302
originX: 'start',
@@ -1302,7 +1317,7 @@ export class MatSelect extends _MatSelectBase<MatSelectChange> implements OnInit
13021317
_preferredOverlayOrigin: CdkOverlayOrigin | ElementRef | undefined;
13031318

13041319
/** Width of the overlay panel. */
1305-
_overlayWidth: number;
1320+
_overlayWidth: string | number;
13061321

13071322
override get shouldLabelFloat(): boolean {
13081323
// Since the panel doesn't overlap the trigger, we
@@ -1378,12 +1393,16 @@ export class MatSelect extends _MatSelectBase<MatSelectChange> implements OnInit
13781393
}
13791394

13801395
/** Gets how wide the overlay panel should be. */
1381-
private _getOverlayWidth() {
1382-
const refToMeasure =
1383-
this._preferredOverlayOrigin instanceof CdkOverlayOrigin
1384-
? this._preferredOverlayOrigin.elementRef
1385-
: this._preferredOverlayOrigin || this._elementRef;
1386-
return refToMeasure.nativeElement.getBoundingClientRect().width;
1396+
private _getOverlayWidth(): string | number {
1397+
if (this.panelWidth === 'auto') {
1398+
const refToMeasure =
1399+
this._preferredOverlayOrigin instanceof CdkOverlayOrigin
1400+
? this._preferredOverlayOrigin.elementRef
1401+
: this._preferredOverlayOrigin || this._elementRef;
1402+
return refToMeasure.nativeElement.getBoundingClientRect().width;
1403+
}
1404+
1405+
return this.panelWidth === null ? '' : this.panelWidth;
13871406
}
13881407

13891408
/** Whether checkmark indicator for single-selection options is hidden. */

tools/public_api_guard/material/legacy-select.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ import { MAT_SELECT_SCROLL_STRATEGY_PROVIDER_FACTORY as MAT_LEGACY_SELECT_SCROLL
2020
import { MAT_SELECT_TRIGGER as MAT_LEGACY_SELECT_TRIGGER } from '@angular/material/select';
2121
import { MatLegacyOptgroup } from '@angular/material/legacy-core';
2222
import { MatLegacyOption } from '@angular/material/legacy-core';
23-
import { MatSelectConfig as MatLegacySelectConfig } from '@angular/material/select';
2423
import { _MatSelectBase } from '@angular/material/select';
24+
import { MatSelectConfig } from '@angular/material/select';
2525
import { OnInit } from '@angular/core';
2626
import { QueryList } from '@angular/core';
2727

@@ -83,7 +83,8 @@ export class MatLegacySelectChange {
8383
value: any;
8484
}
8585

86-
export { MatLegacySelectConfig }
86+
// @public @deprecated (undocumented)
87+
export type MatLegacySelectConfig = Omit<MatSelectConfig, 'panelWidth'>;
8788

8889
// @public @deprecated (undocumented)
8990
export class MatLegacySelectModule {

tools/public_api_guard/material/select.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,8 @@ export class MatSelect extends _MatSelectBase<MatSelectChange> implements OnInit
9595
optionGroups: QueryList<MatOptgroup>;
9696
// (undocumented)
9797
options: QueryList<MatOption>;
98-
_overlayWidth: number;
98+
_overlayWidth: string | number;
99+
panelWidth: string | number | null;
99100
// (undocumented)
100101
protected _positioningSettled(): void;
101102
// (undocumented)
@@ -108,7 +109,7 @@ export class MatSelect extends _MatSelectBase<MatSelectChange> implements OnInit
108109
protected _skipPredicate: (option: MatOption) => boolean;
109110
_syncParentProperties(): void;
110111
// (undocumented)
111-
static ɵcmp: i0.ɵɵComponentDeclaration<MatSelect, "mat-select", ["matSelect"], { "disabled": { "alias": "disabled"; "required": false; }; "disableRipple": { "alias": "disableRipple"; "required": false; }; "tabIndex": { "alias": "tabIndex"; "required": false; }; "hideSingleSelectionIndicator": { "alias": "hideSingleSelectionIndicator"; "required": false; }; }, {}, ["customTrigger", "options", "optionGroups"], ["mat-select-trigger", "*"], false, never>;
112+
static ɵcmp: i0.ɵɵComponentDeclaration<MatSelect, "mat-select", ["matSelect"], { "disabled": { "alias": "disabled"; "required": false; }; "disableRipple": { "alias": "disableRipple"; "required": false; }; "tabIndex": { "alias": "tabIndex"; "required": false; }; "panelWidth": { "alias": "panelWidth"; "required": false; }; "hideSingleSelectionIndicator": { "alias": "hideSingleSelectionIndicator"; "required": false; }; }, {}, ["customTrigger", "options", "optionGroups"], ["mat-select-trigger", "*"], false, never>;
112113
// (undocumented)
113114
static ɵfac: i0.ɵɵFactoryDeclaration<MatSelect, never>;
114115
}
@@ -242,6 +243,7 @@ export interface MatSelectConfig {
242243
disableOptionCentering?: boolean;
243244
hideSingleSelectionIndicator?: boolean;
244245
overlayPanelClass?: string | string[];
246+
panelWidth?: string | number | null;
245247
typeaheadDebounceInterval?: number;
246248
}
247249

0 commit comments

Comments
 (0)