Skip to content

Commit

Permalink
feat(material/select): add input to control the panel width (#27188)
Browse files Browse the repository at this point in the history
Adds an input that allows users to control the width of the panel.

Fixes #26000.
  • Loading branch information
crisbeto committed May 30, 2023
1 parent 1e5f885 commit 3703cc9
Show file tree
Hide file tree
Showing 6 changed files with 87 additions and 20 deletions.
13 changes: 6 additions & 7 deletions src/material/legacy-select/public-api.ts
Expand Up @@ -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 {
/**
Expand Down Expand Up @@ -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';
8 changes: 7 additions & 1 deletion src/material/legacy-select/select.ts
Expand Up @@ -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';
Expand Down Expand Up @@ -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<MatSelectConfig, 'panelWidth'>;

/**
* 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.
Expand Down
42 changes: 41 additions & 1 deletion src/material/select/select.spec.ts
Expand Up @@ -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();
Expand Down Expand Up @@ -4430,6 +4466,7 @@ describe('MDC-based MatSelect', () => {
disableOptionCentering: true,
typeaheadDebounceInterval: 1337,
overlayPanelClass: 'test-panel-class',
panelWidth: null,
} as MatSelectConfig,
},
],
Expand All @@ -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', () => {
Expand Down Expand Up @@ -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">
<mat-option *ngFor="let food of foods" [value]="food.value" [disabled]="food.disabled">
{{ capitalize ? food.viewValue.toUpperCase() : food.viewValue }}
</mat-option>
Expand Down Expand Up @@ -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<MatOption>;
Expand Down
33 changes: 26 additions & 7 deletions src/material/select/select.ts
Expand Up @@ -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. */
Expand Down Expand Up @@ -1282,6 +1288,15 @@ export class MatSelect extends _MatSelectBase<MatSelectChange> implements OnInit
@ContentChildren(MAT_OPTGROUP, {descendants: true}) optionGroups: QueryList<MatOptgroup>;
@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',
Expand All @@ -1302,7 +1317,7 @@ export class MatSelect extends _MatSelectBase<MatSelectChange> 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
Expand Down Expand Up @@ -1378,12 +1393,16 @@ export class MatSelect extends _MatSelectBase<MatSelectChange> 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. */
Expand Down
5 changes: 3 additions & 2 deletions tools/public_api_guard/material/legacy-select.md
Expand Up @@ -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';

Expand Down Expand Up @@ -83,7 +83,8 @@ export class MatLegacySelectChange {
value: any;
}

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

// @public @deprecated (undocumented)
export class MatLegacySelectModule {
Expand Down
6 changes: 4 additions & 2 deletions tools/public_api_guard/material/select.md
Expand Up @@ -95,7 +95,8 @@ export class MatSelect extends _MatSelectBase<MatSelectChange> implements OnInit
optionGroups: QueryList<MatOptgroup>;
// (undocumented)
options: QueryList<MatOption>;
_overlayWidth: number;
_overlayWidth: string | number;
panelWidth: string | number | null;
// (undocumented)
protected _positioningSettled(): void;
// (undocumented)
Expand All @@ -108,7 +109,7 @@ export class MatSelect extends _MatSelectBase<MatSelectChange> implements OnInit
protected _skipPredicate: (option: MatOption) => boolean;
_syncParentProperties(): void;
// (undocumented)
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>;
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>;
// (undocumented)
static ɵfac: i0.ɵɵFactoryDeclaration<MatSelect, never>;
}
Expand Down Expand Up @@ -242,6 +243,7 @@ export interface MatSelectConfig {
disableOptionCentering?: boolean;
hideSingleSelectionIndicator?: boolean;
overlayPanelClass?: string | string[];
panelWidth?: string | number | null;
typeaheadDebounceInterval?: number;
}

Expand Down

0 comments on commit 3703cc9

Please sign in to comment.