Skip to content

Commit a49969f

Browse files
committed
feat(material/select): add option to enable inlined overlay
1 parent 8173e0a commit a49969f

File tree

5 files changed

+31
-16
lines changed

5 files changed

+31
-16
lines changed

goldens/material/paginator/index.api.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import { AfterViewChecked } from '@angular/core';
1313
import { AfterViewInit } from '@angular/core';
1414
import { BooleanInput } from '@angular/cdk/coercion';
1515
import { CdkConnectedOverlay } from '@angular/cdk/overlay';
16-
import { CdkOverlayOrigin } from '@angular/cdk/overlay';
1716
import { ChangeDetectorRef } from '@angular/core';
1817
import { ConnectedPosition } from '@angular/cdk/overlay';
1918
import { ControlValueAccessor } from '@angular/forms';

goldens/material/select/index.api.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import { AfterViewChecked } from '@angular/core';
1313
import { AfterViewInit } from '@angular/core';
1414
import { BooleanInput } from '@angular/cdk/coercion';
1515
import { CdkConnectedOverlay } from '@angular/cdk/overlay';
16-
import { CdkOverlayOrigin } from '@angular/cdk/overlay';
1716
import { ChangeDetectorRef } from '@angular/core';
1817
import { ConnectedPosition } from '@angular/cdk/overlay';
1918
import { ControlValueAccessor } from '@angular/forms';
@@ -280,6 +279,7 @@ export class MatSelect implements AfterContentInit, OnChanges, OnDestroy, OnInit
280279
set hideSingleSelectionIndicator(value: boolean);
281280
get id(): string;
282281
set id(value: string);
282+
get inlineOverlayAfter(): ElementRef | undefined;
283283
_isRtl(): boolean;
284284
_keyManager: ActiveDescendantKeyManager<MatOption>;
285285
get multiple(): boolean;
@@ -297,6 +297,8 @@ export class MatSelect implements AfterContentInit, OnChanges, OnDestroy, OnInit
297297
// (undocumented)
298298
static ngAcceptInputType_multiple: unknown;
299299
// (undocumented)
300+
static ngAcceptInputType_overlayInlined: unknown;
301+
// (undocumented)
300302
static ngAcceptInputType_required: unknown;
301303
// (undocumented)
302304
static ngAcceptInputType_tabIndex: unknown;
@@ -327,6 +329,7 @@ export class MatSelect implements AfterContentInit, OnChanges, OnDestroy, OnInit
327329
options: QueryList<MatOption>;
328330
readonly optionSelectionChanges: Observable<MatOptionSelectionChange>;
329331
protected _overlayDir: CdkConnectedOverlay;
332+
overlayInlined: boolean;
330333
// (undocumented)
331334
_overlayPanelClass: string | string[];
332335
_overlayWidth: string | number;
@@ -341,7 +344,7 @@ export class MatSelect implements AfterContentInit, OnChanges, OnDestroy, OnInit
341344
get placeholder(): string;
342345
set placeholder(value: string);
343346
_positions: ConnectedPosition[];
344-
_preferredOverlayOrigin: CdkOverlayOrigin | ElementRef | undefined;
347+
_preferredOverlayOrigin: ElementRef | undefined;
345348
registerOnChange(fn: (value: any) => void): void;
346349
registerOnTouched(fn: () => {}): void;
347350
get required(): boolean;
@@ -372,7 +375,7 @@ export class MatSelect implements AfterContentInit, OnChanges, OnDestroy, OnInit
372375
protected _viewportRuler: ViewportRuler;
373376
writeValue(value: any): void;
374377
// (undocumented)
375-
static ɵcmp: i0.ɵɵComponentDeclaration<MatSelect, "mat-select", ["matSelect"], { "userAriaDescribedBy": { "alias": "aria-describedby"; "required": false; }; "panelClass": { "alias": "panelClass"; "required": false; }; "disabled": { "alias": "disabled"; "required": false; }; "disableRipple": { "alias": "disableRipple"; "required": false; }; "tabIndex": { "alias": "tabIndex"; "required": false; }; "hideSingleSelectionIndicator": { "alias": "hideSingleSelectionIndicator"; "required": false; }; "placeholder": { "alias": "placeholder"; "required": false; }; "required": { "alias": "required"; "required": false; }; "multiple": { "alias": "multiple"; "required": false; }; "disableOptionCentering": { "alias": "disableOptionCentering"; "required": false; }; "compareWith": { "alias": "compareWith"; "required": false; }; "value": { "alias": "value"; "required": false; }; "ariaLabel": { "alias": "aria-label"; "required": false; }; "ariaLabelledby": { "alias": "aria-labelledby"; "required": false; }; "errorStateMatcher": { "alias": "errorStateMatcher"; "required": false; }; "typeaheadDebounceInterval": { "alias": "typeaheadDebounceInterval"; "required": false; }; "sortComparator": { "alias": "sortComparator"; "required": false; }; "id": { "alias": "id"; "required": false; }; "panelWidth": { "alias": "panelWidth"; "required": false; }; "canSelectNullableOptions": { "alias": "canSelectNullableOptions"; "required": false; }; }, { "openedChange": "openedChange"; "_openedStream": "opened"; "_closedStream": "closed"; "selectionChange": "selectionChange"; "valueChange": "valueChange"; }, ["customTrigger", "options", "optionGroups"], ["mat-select-trigger", "*"], true, never>;
378+
static ɵcmp: i0.ɵɵComponentDeclaration<MatSelect, "mat-select", ["matSelect"], { "userAriaDescribedBy": { "alias": "aria-describedby"; "required": false; }; "panelClass": { "alias": "panelClass"; "required": false; }; "disabled": { "alias": "disabled"; "required": false; }; "disableRipple": { "alias": "disableRipple"; "required": false; }; "tabIndex": { "alias": "tabIndex"; "required": false; }; "hideSingleSelectionIndicator": { "alias": "hideSingleSelectionIndicator"; "required": false; }; "placeholder": { "alias": "placeholder"; "required": false; }; "required": { "alias": "required"; "required": false; }; "multiple": { "alias": "multiple"; "required": false; }; "disableOptionCentering": { "alias": "disableOptionCentering"; "required": false; }; "compareWith": { "alias": "compareWith"; "required": false; }; "value": { "alias": "value"; "required": false; }; "ariaLabel": { "alias": "aria-label"; "required": false; }; "ariaLabelledby": { "alias": "aria-labelledby"; "required": false; }; "errorStateMatcher": { "alias": "errorStateMatcher"; "required": false; }; "typeaheadDebounceInterval": { "alias": "typeaheadDebounceInterval"; "required": false; }; "sortComparator": { "alias": "sortComparator"; "required": false; }; "id": { "alias": "id"; "required": false; }; "panelWidth": { "alias": "panelWidth"; "required": false; }; "canSelectNullableOptions": { "alias": "canSelectNullableOptions"; "required": false; }; "overlayInlined": { "alias": "overlayInlined"; "required": false; }; }, { "openedChange": "openedChange"; "_openedStream": "opened"; "_closedStream": "closed"; "selectionChange": "selectionChange"; "valueChange": "valueChange"; }, ["customTrigger", "options", "optionGroups"], ["mat-select-trigger", "*"], true, never>;
376379
// (undocumented)
377380
static ɵfac: i0.ɵɵFactoryDeclaration<MatSelect, never>;
378381
}

src/dev-app/select/select-demo.html

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
[class.demo-drinks-width-large]="drinksWidth === '400px'">
1212
<mat-label>Drink</mat-label>
1313
<mat-select [(ngModel)]="currentDrink" [required]="drinksRequired"
14-
[disabled]="drinksDisabled" #drinkControl="ngModel">
14+
[disabled]="drinksDisabled" overlayInlined #drinkControl="ngModel">
1515
<mat-option [disabled]="drinksOptionsDisabled === 'all'">None</mat-option>
1616
@for (drink of drinks; track drink; let index = $index) {
1717
<mat-option [value]="drink.value"
@@ -73,7 +73,7 @@
7373
<mat-card-content>
7474
<mat-form-field [color]="pokemonTheme">
7575
<mat-label>Pokemon</mat-label>
76-
<mat-select multiple [(ngModel)]="currentPokemon"
76+
<mat-select multiple overlayInlined [(ngModel)]="currentPokemon"
7777
[required]="pokemonRequired" [disabled]="pokemonDisabled" #pokemonControl="ngModel">
7878
@for (creature of pokemon; track creature) {
7979
<mat-option [value]="creature.value" [disabled]="pokemonOptionsDisabled">
@@ -108,7 +108,7 @@
108108
<mat-card-content>
109109
<mat-form-field>
110110
<mat-label>Digimon</mat-label>
111-
<mat-select [(value)]="currentDigimon">
111+
<mat-select overlayInlined [(value)]="currentDigimon">
112112
<mat-option>None</mat-option>
113113
@for (creature of digimon; track creature) {
114114
<mat-option [value]="creature.value">{{ creature.viewValue }}</mat-option>
@@ -129,7 +129,7 @@
129129
<mat-card-content>
130130
<mat-form-field>
131131
<mat-label>Pokemon</mat-label>
132-
<mat-select [(ngModel)]="currentPokemonFromGroup">
132+
<mat-select overlayInlined [(ngModel)]="currentPokemonFromGroup">
133133
@for (group of pokemonGroups; track group) {
134134
<mat-optgroup [label]="group.name" [disabled]="group.disabled">
135135
@for (creature of group.pokemon; track creature) {
@@ -151,6 +151,7 @@
151151
<mat-select [(ngModel)]="currentDrinkObject"
152152
[required]="drinkObjectRequired"
153153
[compareWith]="compareByValue ? compareDrinkObjectsByValue : compareByReference"
154+
overlayInlined
154155
#drinkObjectControl="ngModel">
155156
@for (drink of drinks; track drink) {
156157
<mat-option [value]="drink" [disabled]="drink.disabled">{{ drink.viewValue }}</mat-option>
@@ -179,7 +180,7 @@
179180
<p>
180181
<mat-form-field appearance="fill">
181182
<mat-label>Fill</mat-label>
182-
<mat-select [(value)]="currentAppearanceValue">
183+
<mat-select overlayInlined [(value)]="currentAppearanceValue">
183184
<mat-option>None</mat-option>
184185
@for (creature of digimon; track creature) {
185186
<mat-option [value]="creature.value">{{ creature.viewValue }}</mat-option>
@@ -191,7 +192,7 @@
191192
<p>
192193
<mat-form-field appearance="outline">
193194
<mat-label>Outline</mat-label>
194-
<mat-select [(value)]="currentAppearanceValue">
195+
<mat-select overlayInlined [(value)]="currentAppearanceValue">
195196
<mat-option>None</mat-option>
196197
@for (creature of digimon; track creature) {
197198
<mat-option [value]="creature.value">{{ creature.viewValue }}</mat-option>
@@ -212,7 +213,7 @@
212213
<mat-card-content>
213214
<mat-form-field>
214215
<mat-label>Food I would like to eat</mat-label>
215-
<mat-select [formControl]="foodControl">
216+
<mat-select overlayInlined [formControl]="foodControl">
216217
@for (food of foods; track food) {
217218
<mat-option [value]="food.value">{{ food.viewValue }}</mat-option>
218219
}
@@ -238,7 +239,7 @@
238239
<mat-card-content>
239240
<mat-form-field>
240241
<mat-label>Starter pokemon</mat-label>
241-
<mat-select (selectionChange)="latestChangeEvent = $event">
242+
<mat-select overlayInlined (selectionChange)="latestChangeEvent = $event">
242243
@for (creature of pokemon; track creature) {
243244
<mat-option [value]="creature.value">{{ creature.viewValue }}</mat-option>
244245
}
@@ -387,7 +388,7 @@ <h4>Error message with errorStateMatcher</h4>
387388
<p class="demo-narrow-sandwich">
388389
<mat-form-field>
389390
<mat-label>Bread</mat-label>
390-
<mat-select [(ngModel)]="sandwichBread"
391+
<mat-select overlayInlined [(ngModel)]="sandwichBread"
391392
[hideSingleSelectionIndicator]="sandwichHideSingleSelectionIndicator">
392393
@for (bread of breads; track bread) {
393394
<mat-option [value]="bread.value">{{ bread.viewValue }}</mat-option>
@@ -396,7 +397,7 @@ <h4>Error message with errorStateMatcher</h4>
396397
</mat-form-field>
397398
<mat-form-field>
398399
<mat-label>Meat</mat-label>
399-
<mat-select [(ngModel)]="sandwichMeat"
400+
<mat-select overlayInlined [(ngModel)]="sandwichMeat"
400401
[hideSingleSelectionIndicator]="sandwichHideSingleSelectionIndicator">
401402
@for (meat of meats; track meat) {
402403
<mat-option [value]="meat.value">{{ meat.viewValue }}</mat-option>
@@ -405,7 +406,7 @@ <h4>Error message with errorStateMatcher</h4>
405406
</mat-form-field>
406407
<mat-form-field>
407408
<mat-label>Cheese</mat-label>
408-
<mat-select [(ngModel)]="sandwichCheese"
409+
<mat-select overlayInlined [(ngModel)]="sandwichCheese"
409410
[hideSingleSelectionIndicator]="sandwichHideSingleSelectionIndicator">
410411
@for (cheese of cheeses; track cheese) {
411412
<mat-option [value]="cheese.value">{{ cheese.viewValue }}</mat-option>

src/material/select/select.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
[cdkConnectedOverlayPositions]="_positions"
4141
[cdkConnectedOverlayWidth]="_overlayWidth"
4242
[cdkConnectedOverlayFlexibleDimensions]="true"
43+
[cdkConnectedOverlayInsertAfter]="inlineOverlayAfter"
4344
(detach)="close()"
4445
(backdropClick)="close()"
4546
(overlayKeydown)="_handleOverlayKeydown($event)">

src/material/select/select.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,7 @@ export class MatSelect
346346
_keyManager: ActiveDescendantKeyManager<MatOption>;
347347

348348
/** Ideal origin for the overlay panel. */
349-
_preferredOverlayOrigin: CdkOverlayOrigin | ElementRef | undefined;
349+
_preferredOverlayOrigin: ElementRef | undefined;
350350

351351
/** Width of the overlay panel. */
352352
_overlayWidth: string | number;
@@ -555,6 +555,12 @@ export class MatSelect
555555
@Input({transform: booleanAttribute})
556556
canSelectNullableOptions: boolean = this._defaultOptions?.canSelectNullableOptions ?? false;
557557

558+
/**
559+
* Whether to inline the overlay, instead of using the global overlay container.
560+
*/
561+
@Input({transform: booleanAttribute})
562+
overlayInlined: boolean;
563+
558564
/** Combined stream of all of the child options' change events. */
559565
readonly optionSelectionChanges: Observable<MatOptionSelectionChange> = defer(() => {
560566
const options = this.options;
@@ -948,6 +954,11 @@ export class MatSelect
948954
return this._selectionModel.selected[0].viewValue;
949955
}
950956

957+
/** Whether to inline the overlay and after which element. */
958+
get inlineOverlayAfter(): ElementRef | undefined {
959+
return this.overlayInlined ? this._parentFormField?.getConnectedOverlayOrigin() : undefined;
960+
}
961+
951962
/** Refreshes the error state of the select. */
952963
updateErrorState() {
953964
this._errorStateTracker.updateErrorState();

0 commit comments

Comments
 (0)