Skip to content

Commit db8fe76

Browse files
committed
make dropdown adjust to form field's font size (still 1px rounding erorr
in some cases)
1 parent 1f8da0b commit db8fe76

File tree

3 files changed

+66
-45
lines changed

3 files changed

+66
-45
lines changed

src/lib/select/select.html

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<div #trigger #origin="cdkOverlayOrigin" cdk-overlay-origin class="mat-select-trigger"
22
aria-hidden="true" (click)="toggle()">
33
<span class="mat-select-value">
4+
<div #measureFontSize class="mat-select-measure-font-size" aria-hidden="true"></div>
45
<span class="mat-select-value-text" *ngIf="!empty" [ngSwitch]="!!customTrigger">
56
<span *ngSwitchDefault>{{ triggerValue }}</span>
67
<ng-content select="md-select-trigger, mat-select-trigger" *ngSwitchCase="true"></ng-content>
@@ -31,7 +32,8 @@
3132
(@transformPanel.done)="_onPanelDone()"
3233
(keydown)="_handlePanelKeydown($event)"
3334
[style.transformOrigin]="_transformOrigin"
34-
[class.mat-select-panel-done-animating]="_panelDoneAnimating">
35+
[class.mat-select-panel-done-animating]="_panelDoneAnimating"
36+
[style.font-size.px]="_triggerFontSize">
3537

3638
<div class="mat-select-content" [@fadeInContent]="'showing'" (@fadeInContent.done)="_onFadeInDone()">
3739
<ng-content></ng-content>

src/lib/select/select.scss

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ $mat-select-trigger-min-width: 112px !default;
88
$mat-select-arrow-size: 5px !default;
99
$mat-select-arrow-margin: 4px !default;
1010
$mat-select-panel-max-height: 256px !default;
11+
$mat-select-item-height: 3em !default;
1112

1213
.mat-select {
1314
display: inline-block;
@@ -65,3 +66,21 @@ $mat-select-panel-max-height: 256px !default;
6566
outline: solid 1px;
6667
}
6768
}
69+
70+
.mat-select-measure-font-size {
71+
position: absolute;
72+
display: block;
73+
height: 1em;
74+
visibility: hidden;
75+
pointer-events: none;
76+
}
77+
78+
// Override optgroup and option to scale based on font-size of the trigger.
79+
.mat-select-panel {
80+
.mat-optgroup-label,
81+
.mat-option {
82+
font-size: inherit;
83+
line-height: $mat-select-item-height;
84+
height: $mat-select-item-height;
85+
}
86+
}

src/lib/select/select.ts

Lines changed: 44 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -64,31 +64,18 @@ let nextUniqueId = 0;
6464
* the trigger element.
6565
*/
6666

67-
/** The fixed height of every option element (option, group header etc.). */
68-
export const SELECT_ITEM_HEIGHT = 48;
69-
7067
/** The max height of the select's overlay panel */
7168
export const SELECT_PANEL_MAX_HEIGHT = 256;
7269

73-
/** The max number of options visible at once in the select panel. */
74-
export const SELECT_MAX_OPTIONS_DISPLAYED =
75-
Math.floor(SELECT_PANEL_MAX_HEIGHT / SELECT_ITEM_HEIGHT);
76-
77-
/** The fixed height of the select's trigger element. */
78-
export const SELECT_TRIGGER_HEIGHT = 30;
79-
80-
/**
81-
* Must adjust for the difference in height between the option and the trigger,
82-
* so the text will align on the y axis.
83-
*/
84-
export const SELECT_OPTION_HEIGHT_ADJUSTMENT = (SELECT_ITEM_HEIGHT - SELECT_TRIGGER_HEIGHT) / 2;
85-
8670
/** The panel's padding on the x-axis */
8771
export const SELECT_PANEL_PADDING_X = 16;
8872

8973
/** The panel's x axis padding if it is indented (e.g. there is an option group). */
9074
export const SELECT_PANEL_INDENT_PADDING_X = SELECT_PANEL_PADDING_X * 2;
9175

76+
/** The height of the select items in `em` units. */
77+
export const SELECT_ITEM_HEIGHT_EM = 3;
78+
9279
/**
9380
* Distance between the panel edge and the option text in
9481
* multi-selection mode.
@@ -223,6 +210,12 @@ export class MdSelect extends _MdSelectMixinBase implements AfterContentInit, On
223210
/** Tab index for the element. */
224211
private _tabIndex: number;
225212

213+
/** The cached height of the trigger element. */
214+
private _triggerHeight: number;
215+
216+
/** The cached font-size of the trigger element. */
217+
_triggerFontSize = 0;
218+
226219
/** Deals with the selection logic. */
227220
_selectionModel: SelectionModel<MdOption>;
228221

@@ -297,6 +290,9 @@ export class MdSelect extends _MdSelectMixinBase implements AfterContentInit, On
297290
/** Trigger that opens the select. */
298291
@ViewChild('trigger') trigger: ElementRef;
299292

293+
/** Element used to measure the font-size of the trigger element. */
294+
@ViewChild('measureFontSize') _measureFontSizeEl: ElementRef;
295+
300296
/** Overlay pane containing the options. */
301297
@ViewChild(ConnectedOverlayDirective) overlayDir: ConnectedOverlayDirective;
302298

@@ -866,31 +862,29 @@ export class MdSelect extends _MdSelectMixinBase implements AfterContentInit, On
866862

867863
/** Calculates the scroll position and x- and y-offsets of the overlay panel. */
868864
private _calculateOverlayPosition(): void {
865+
this._triggerHeight = this.trigger.nativeElement.getBoundingClientRect().height;
866+
this._triggerFontSize = this._measureFontSizeEl.nativeElement.getBoundingClientRect().height;
867+
868+
const itemHeight = this._triggerFontSize * SELECT_ITEM_HEIGHT_EM;
869+
869870
const items = this._getItemCount();
870-
const panelHeight = Math.min(items * SELECT_ITEM_HEIGHT, SELECT_PANEL_MAX_HEIGHT);
871-
const scrollContainerHeight = items * SELECT_ITEM_HEIGHT;
871+
const panelHeight = Math.min(items * itemHeight, SELECT_PANEL_MAX_HEIGHT);
872+
const scrollContainerHeight = items * itemHeight;
872873

873874
// The farthest the panel can be scrolled before it hits the bottom
874875
const maxScroll = scrollContainerHeight - panelHeight;
875876

876-
if (!this.empty) {
877-
let selectedOptionOffset = this._getOptionIndex(this._selectionModel.selected[0])!;
877+
// If no value is selected we open the popup to the first item.
878+
let selectedOptionOffset =
879+
this.empty ? 0 : this._getOptionIndex(this._selectionModel.selected[0])!;
878880

879-
selectedOptionOffset += this._getLabelCountBeforeOption(selectedOptionOffset);
881+
selectedOptionOffset += this._getLabelCountBeforeOption(selectedOptionOffset);
880882

881-
// We must maintain a scroll buffer so the selected option will be scrolled to the
882-
// center of the overlay panel rather than the top.
883-
const scrollBuffer = panelHeight / 2;
884-
this._scrollTop = this._calculateOverlayScroll(selectedOptionOffset, scrollBuffer, maxScroll);
885-
this._offsetY = this._calculateOverlayOffsetY(selectedOptionOffset, scrollBuffer, maxScroll);
886-
} else {
887-
// If no option is selected, the panel centers on the first option. In this case,
888-
// we must only adjust for the height difference between the option element
889-
// and the trigger element, then multiply it by -1 to ensure the panel moves
890-
// in the correct direction up the page.
891-
this._offsetY = (SELECT_ITEM_HEIGHT - SELECT_TRIGGER_HEIGHT) / 2 * -1 -
892-
(this._getLabelCountBeforeOption(0) * SELECT_ITEM_HEIGHT);
893-
}
883+
// We must maintain a scroll buffer so the selected option will be scrolled to the
884+
// center of the overlay panel rather than the top.
885+
const scrollBuffer = panelHeight / 2;
886+
this._scrollTop = this._calculateOverlayScroll(selectedOptionOffset, scrollBuffer, maxScroll);
887+
this._offsetY = this._calculateOverlayOffsetY(selectedOptionOffset, scrollBuffer, maxScroll);
894888

895889
this._checkOverlayWithinViewport(maxScroll);
896890
}
@@ -904,8 +898,9 @@ export class MdSelect extends _MdSelectMixinBase implements AfterContentInit, On
904898
*/
905899
_calculateOverlayScroll(selectedIndex: number, scrollBuffer: number,
906900
maxScroll: number): number {
907-
const optionOffsetFromScrollTop = SELECT_ITEM_HEIGHT * selectedIndex;
908-
const halfOptionHeight = SELECT_ITEM_HEIGHT / 2;
901+
const itemHeight = this._triggerFontSize * SELECT_ITEM_HEIGHT_EM;
902+
const optionOffsetFromScrollTop = itemHeight * selectedIndex;
903+
const halfOptionHeight = itemHeight / 2;
909904

910905
// Starts at the optionOffsetFromScrollTop, which scrolls the option to the top of the
911906
// scroll container, then subtracts the scroll buffer to scroll the option down to
@@ -975,31 +970,34 @@ export class MdSelect extends _MdSelectMixinBase implements AfterContentInit, On
975970
*/
976971
private _calculateOverlayOffsetY(selectedIndex: number, scrollBuffer: number,
977972
maxScroll: number): number {
973+
const itemHeight = this._triggerFontSize * SELECT_ITEM_HEIGHT_EM;
974+
const optionHeightAdjustment = (itemHeight - this._triggerHeight) / 2;
975+
const maxOptionsDisplayed = Math.floor(SELECT_PANEL_MAX_HEIGHT / itemHeight);
978976
let optionOffsetFromPanelTop: number;
979977

980978
if (this._scrollTop === 0) {
981-
optionOffsetFromPanelTop = selectedIndex * SELECT_ITEM_HEIGHT;
979+
optionOffsetFromPanelTop = selectedIndex * itemHeight;
982980
} else if (this._scrollTop === maxScroll) {
983-
const firstDisplayedIndex = this._getItemCount() - SELECT_MAX_OPTIONS_DISPLAYED;
981+
const firstDisplayedIndex = this._getItemCount() - maxOptionsDisplayed;
984982
const selectedDisplayIndex = selectedIndex - firstDisplayedIndex;
985983

986984
// Because the panel height is longer than the height of the options alone,
987985
// there is always extra padding at the top or bottom of the panel. When
988986
// scrolled to the very bottom, this padding is at the top of the panel and
989987
// must be added to the offset.
990988
optionOffsetFromPanelTop =
991-
selectedDisplayIndex * SELECT_ITEM_HEIGHT + SELECT_PANEL_PADDING_Y;
989+
selectedDisplayIndex * itemHeight + SELECT_PANEL_PADDING_Y;
992990
} else {
993991
// If the option was scrolled to the middle of the panel using a scroll buffer,
994992
// its offset will be the scroll buffer minus the half height that was added to
995993
// center it.
996-
optionOffsetFromPanelTop = scrollBuffer - SELECT_ITEM_HEIGHT / 2;
994+
optionOffsetFromPanelTop = scrollBuffer - itemHeight / 2;
997995
}
998996

999997
// The final offset is the option's offset from the top, adjusted for the height
1000998
// difference, multiplied by -1 to ensure that the overlay moves in the correct
1001999
// direction up the page.
1002-
return optionOffsetFromPanelTop * -1 - SELECT_OPTION_HEIGHT_ADJUSTMENT;
1000+
return optionOffsetFromPanelTop * -1 - optionHeightAdjustment;
10031001
}
10041002

10051003
/**
@@ -1009,6 +1007,7 @@ export class MdSelect extends _MdSelectMixinBase implements AfterContentInit, On
10091007
* sets the offset back to 0 to allow the fallback position to take over.
10101008
*/
10111009
private _checkOverlayWithinViewport(maxScroll: number): void {
1010+
const itemHeight = this._triggerFontSize * SELECT_ITEM_HEIGHT_EM;
10121011
const viewportRect = this._viewportRuler.getViewportRect();
10131012
const triggerRect = this._getTriggerRect();
10141013

@@ -1018,7 +1017,7 @@ export class MdSelect extends _MdSelectMixinBase implements AfterContentInit, On
10181017

10191018
const panelHeightTop = Math.abs(this._offsetY);
10201019
const totalPanelHeight =
1021-
Math.min(this._getItemCount() * SELECT_ITEM_HEIGHT, SELECT_PANEL_MAX_HEIGHT);
1020+
Math.min(this._getItemCount() * itemHeight, SELECT_PANEL_MAX_HEIGHT);
10221021
const panelHeightBottom = totalPanelHeight - panelHeightTop - triggerRect.height;
10231022

10241023
if (panelHeightBottom > bottomSpaceAvailable) {
@@ -1074,8 +1073,9 @@ export class MdSelect extends _MdSelectMixinBase implements AfterContentInit, On
10741073

10751074
/** Sets the transform origin point based on the selected option. */
10761075
private _getOriginBasedOnOption(): string {
1077-
const originY =
1078-
Math.abs(this._offsetY) - SELECT_OPTION_HEIGHT_ADJUSTMENT + SELECT_ITEM_HEIGHT / 2;
1076+
const itemHeight = this._triggerFontSize * SELECT_ITEM_HEIGHT_EM;
1077+
const optionHeightAdjustment = (itemHeight - this._triggerHeight) / 2;
1078+
const originY = Math.abs(this._offsetY) - optionHeightAdjustment + itemHeight / 2;
10791079
return `50% ${originY}px 0px`;
10801080
}
10811081

0 commit comments

Comments
 (0)