Skip to content

Commit

Permalink
feat(select): configurable panel max-height
Browse files Browse the repository at this point in the history
add option to dynamically set panel max-height on mat-select
update mat-select multiselect demo to use panelMaxHeight option

fix angular#15803
  • Loading branch information
RudolfFrederiksen committed Jun 21, 2019
1 parent 6a7fc81 commit 791e34d
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 13 deletions.
6 changes: 5 additions & 1 deletion src/dev-app/select/select-demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ <h4>Error message with errorStateMatcher</h4>
<mat-card-content>
<mat-form-field [color]="pokemonTheme">
<mat-label>Pokemon</mat-label>
<mat-select multiple [(ngModel)]="currentPokemon"
<mat-select multiple [(ngModel)]="currentPokemon" [panelMaxHeight]="pokemonPanelHeight"
[required]="pokemonRequired" [disabled]="pokemonDisabled" #pokemonControl="ngModel">
<mat-option *ngFor="let creature of pokemon" [value]="creature.value">
{{ creature.viewValue }}
Expand All @@ -239,6 +239,10 @@ <h4>Error message with errorStateMatcher</h4>
<option *ngFor="let theme of availableThemes" [value]="theme.value">{{ theme.name }}</option>
</select>
</p>
<p>
<label for="pokemon-panel-height">Theme:</label>
<input type="number" id="pokemon-panel-height" [(ngModel)]="pokemonPanelHeight">
</p>
<button mat-button (click)="setPokemonValue()">SET VALUE</button>
<button mat-button (click)="pokemonRequired=!pokemonRequired">TOGGLE REQUIRED</button>
<button mat-button (click)="pokemonDisabled=!pokemonDisabled">TOGGLE DISABLED</button>
Expand Down
14 changes: 14 additions & 0 deletions src/dev-app/select/select-demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export class SelectDemo {
topHeightCtrl = new FormControl(0);
drinksTheme = 'primary';
pokemonTheme = 'primary';
pokemonPanelHeight = 256;
compareByValue = true;
selectFormControl = new FormControl('', Validators.required);

Expand Down Expand Up @@ -77,6 +78,19 @@ export class SelectDemo {
{value: 'jigglypuff-4', viewValue: 'Jigglypuff with a really long name that will truncate'},
{value: 'ditto-5', viewValue: 'Ditto'},
{value: 'psyduck-6', viewValue: 'Psyduck'},
{value: 'caterpie-7', viewValue: 'Caterpie'},
{value: 'weedle-8', viewValue: 'Weedle'},
{value: 'pidgey-9', viewValue: 'Pidgey'},
{value: 'rattata-10', viewValue: 'Rattata'},
{value: 'spearow-11', viewValue: 'Spearow'},
{value: 'ekans-12', viewValue: 'Ekans'},
{value: 'sandshrew-13', viewValue: 'Sandshrew'},
{value: 'nidoran-14', viewValue: 'Nidoran'},
{value: 'clefairy-15', viewValue: 'Clefairy'},
{value: 'vulpix-16', viewValue: 'Vulpix'},
{value: 'zubat-17', viewValue: 'Zubat'},
{value: 'oddish-18', viewValue: 'Oddish'},
{value: 'paras-19', viewValue: 'Paras'},
];

availableThemes = [
Expand Down
2 changes: 0 additions & 2 deletions src/material/select/select.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

$mat-select-arrow-size: 5px !default;
$mat-select-arrow-margin: 4px !default;
$mat-select-panel-max-height: 256px !default;
$mat-select-item-height: 3em !default;

$mat-select-placeholder-arrow-space: 2 * ($mat-select-arrow-size + $mat-select-arrow-margin);
Expand Down Expand Up @@ -93,7 +92,6 @@ $mat-select-placeholder-arrow-space: 2 * ($mat-select-arrow-size + $mat-select-a
@include mat-menu-base();
padding-top: 0;
padding-bottom: 0;
max-height: $mat-select-panel-max-height;
min-width: 100%; // prevents some animation twitching and test inconsistencies in IE11
border-radius: 4px;

Expand Down
35 changes: 25 additions & 10 deletions src/material/select/select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import {ActiveDescendantKeyManager, LiveAnnouncer} from '@angular/cdk/a11y';
import {Directionality} from '@angular/cdk/bidi';
import {coerceBooleanProperty} from '@angular/cdk/coercion';
import {coerceBooleanProperty, coerceNumberProperty} from '@angular/cdk/coercion';
import {SelectionModel} from '@angular/cdk/collections';
import {
A,
Expand Down Expand Up @@ -101,9 +101,6 @@ let nextUniqueId = 0;
* the trigger element.
*/

/** The max height of the select's overlay panel */
export const SELECT_PANEL_MAX_HEIGHT = 256;

/** The panel's padding on the x-axis */
export const SELECT_PANEL_PADDING_X = 16;

Expand Down Expand Up @@ -238,6 +235,14 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit,
/** The scroll position of the overlay panel, calculated to center the selected option. */
private _scrollTop = 0;

/**
* The max height of the select's overlay panel
* 256 value is define has follow:
* - display 5 select options of 48 px (240px)
* - remaining 16px help the user to see if other options are available
*/
private _panelMaxHeight = 256;

/** The placeholder displayed in the trigger of the select. */
private _placeholder: string;

Expand Down Expand Up @@ -388,6 +393,13 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit,
this._disableOptionCentering = coerceBooleanProperty(value);
}

/** Define select panel maximum height. */
@Input()
get panelMaxHeight(): number { return this._panelMaxHeight; }
set panelMaxHeight(value: number) {
this._panelMaxHeight = coerceNumberProperty(value) || this._panelMaxHeight;
}

/**
* Function to compare the option values with the selected values. The first argument
* is a value from an option. The second is a value from the selection. A boolean
Expand Down Expand Up @@ -805,6 +817,9 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit,
*/
_onAttached(): void {
this.overlayDir.positionChange.pipe(take(1)).subscribe(() => {
// Set panel max-height dynamically after creation.
this.panel.nativeElement.style.maxHeight = `${this._panelMaxHeight}px`;

this._setPseudoCheckboxPaddingSize();
this._changeDetectorRef.detectChanges();
this._calculateOverlayOffsetX();
Expand Down Expand Up @@ -1037,7 +1052,7 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit,
activeOptionIndex + labelCount,
this._getItemHeight(),
this.panel.nativeElement.scrollTop,
SELECT_PANEL_MAX_HEIGHT
this._panelMaxHeight
);
}

Expand All @@ -1057,7 +1072,7 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit,
private _calculateOverlayPosition(): void {
const itemHeight = this._getItemHeight();
const items = this._getItemCount();
const panelHeight = Math.min(items * itemHeight, SELECT_PANEL_MAX_HEIGHT);
const panelHeight = Math.min(items * itemHeight, this._panelMaxHeight);
const scrollContainerHeight = items * itemHeight;

// The farthest the panel can be scrolled before it hits the bottom
Expand Down Expand Up @@ -1188,7 +1203,7 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit,
maxScroll: number): number {
const itemHeight = this._getItemHeight();
const optionHeightAdjustment = (itemHeight - this._triggerRect.height) / 2;
const maxOptionsDisplayed = Math.floor(SELECT_PANEL_MAX_HEIGHT / itemHeight);
const maxOptionsDisplayed = Math.floor(this._panelMaxHeight / itemHeight);
let optionOffsetFromPanelTop: number;

// Disable offset if requested by user by returning 0 as value to offset
Expand All @@ -1204,8 +1219,8 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit,

// The first item is partially out of the viewport. Therefore we need to calculate what
// portion of it is shown in the viewport and account for it in our offset.
let partialItemHeight =
itemHeight - (this._getItemCount() * itemHeight - SELECT_PANEL_MAX_HEIGHT) % itemHeight;
let partialItemHeight = itemHeight -
(this._getItemCount() * itemHeight - this._panelMaxHeight) % itemHeight;

// Because the panel height is longer than the height of the options alone,
// there is always extra padding at the top or bottom of the panel. When
Expand Down Expand Up @@ -1241,7 +1256,7 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit,

const panelHeightTop = Math.abs(this._offsetY);
const totalPanelHeight =
Math.min(this._getItemCount() * itemHeight, SELECT_PANEL_MAX_HEIGHT);
Math.min(this._getItemCount() * itemHeight, this._panelMaxHeight);
const panelHeightBottom = totalPanelHeight - panelHeightTop - this._triggerRect.height;

if (panelHeightBottom > bottomSpaceAvailable) {
Expand Down

0 comments on commit 791e34d

Please sign in to comment.