Skip to content

Commit 368bc9d

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

File tree

3 files changed

+68
-49
lines changed

3 files changed

+68
-49
lines changed

src/lib/select/select.html

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#origin="cdkOverlayOrigin"
77
#trigger>
88
<span class="mat-select-value">
9+
<div #measureFontSize class="mat-select-measure-font-size" aria-hidden="true"></div>
910
<span class="mat-select-value-text" *ngIf="!empty" [ngSwitch]="!!customTrigger">
1011
<span *ngSwitchDefault>{{ triggerValue }}</span>
1112
<ng-content select="md-select-trigger, mat-select-trigger" *ngSwitchCase="true"></ng-content>
@@ -36,7 +37,8 @@
3637
(@transformPanel.done)="_onPanelDone()"
3738
(keydown)="_handlePanelKeydown($event)"
3839
[style.transformOrigin]="_transformOrigin"
39-
[class.mat-select-panel-done-animating]="_panelDoneAnimating">
40+
[class.mat-select-panel-done-animating]="_panelDoneAnimating"
41+
[style.font-size.px]="_triggerFontSize">
4042

4143
<div class="mat-select-content" [@fadeInContent]="'showing'" (@fadeInContent.done)="_onFadeInDone()">
4244
<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: 46 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -78,31 +78,18 @@ let nextUniqueId = 0;
7878
* the trigger element.
7979
*/
8080

81-
/** The fixed height of every option element (option, group header etc.). */
82-
export const SELECT_ITEM_HEIGHT = 48;
83-
8481
/** The max height of the select's overlay panel */
8582
export const SELECT_PANEL_MAX_HEIGHT = 256;
8683

87-
/** The max number of options visible at once in the select panel. */
88-
export const SELECT_MAX_OPTIONS_DISPLAYED =
89-
Math.floor(SELECT_PANEL_MAX_HEIGHT / SELECT_ITEM_HEIGHT);
90-
91-
/** The fixed height of the select's trigger element. */
92-
export const SELECT_TRIGGER_HEIGHT = 30;
93-
94-
/**
95-
* Must adjust for the difference in height between the option and the trigger,
96-
* so the text will align on the y axis.
97-
*/
98-
export const SELECT_OPTION_HEIGHT_ADJUSTMENT = (SELECT_ITEM_HEIGHT - SELECT_TRIGGER_HEIGHT) / 2;
99-
10084
/** The panel's padding on the x-axis */
10185
export const SELECT_PANEL_PADDING_X = 16;
10286

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

90+
/** The height of the select items in `em` units. */
91+
export const SELECT_ITEM_HEIGHT_EM = 3;
92+
10693
/**
10794
* Distance between the panel edge and the option text in
10895
* multi-selection mode.
@@ -239,6 +226,12 @@ export class MdSelect extends _MdSelectMixinBase implements AfterContentInit, On
239226
/** Unique id for this input. */
240227
private _uid = `mat-select-${nextUniqueId++}`;
241228

229+
/** The cached height of the trigger element. */
230+
private _triggerHeight: number;
231+
232+
/** The cached font-size of the trigger element. */
233+
_triggerFontSize = 0;
234+
242235
/** Deals with the selection logic. */
243236
_selectionModel: SelectionModel<MdOption>;
244237

@@ -313,6 +306,9 @@ export class MdSelect extends _MdSelectMixinBase implements AfterContentInit, On
313306
/** Trigger that opens the select. */
314307
@ViewChild('trigger') trigger: ElementRef;
315308

309+
/** Element used to measure the font-size of the trigger element. */
310+
@ViewChild('measureFontSize') _measureFontSizeEl: ElementRef;
311+
316312
/** Overlay pane containing the options. */
317313
@ViewChild(ConnectedOverlayDirective) overlayDir: ConnectedOverlayDirective;
318314

@@ -684,7 +680,7 @@ export class MdSelect extends _MdSelectMixinBase implements AfterContentInit, On
684680
// Defer setting the value in order to avoid the "Expression
685681
// has changed after it was checked" errors from Angular.
686682
Promise.resolve().then(() => {
687-
this._setSelectionByValue(this._control ? this._control.value : this._value);
683+
this._setSelectionByValue(this.ngControl ? this.ngControl.value : this._value);
688684
});
689685
}
690686

@@ -899,34 +895,30 @@ export class MdSelect extends _MdSelectMixinBase implements AfterContentInit, On
899895

900896
/** Calculates the scroll position and x- and y-offsets of the overlay panel. */
901897
private _calculateOverlayPosition(): void {
898+
this._triggerHeight = this.trigger.nativeElement.getBoundingClientRect().height;
899+
this._triggerFontSize = this._measureFontSizeEl.nativeElement.getBoundingClientRect().height;
900+
901+
const itemHeight = this._triggerFontSize * SELECT_ITEM_HEIGHT_EM;
902+
902903
const items = this._getItemCount();
903-
const panelHeight = Math.min(items * SELECT_ITEM_HEIGHT, SELECT_PANEL_MAX_HEIGHT);
904-
const scrollContainerHeight = items * SELECT_ITEM_HEIGHT;
904+
const panelHeight = Math.min(items * itemHeight, SELECT_PANEL_MAX_HEIGHT);
905+
const scrollContainerHeight = items * itemHeight;
905906

906907
// The farthest the panel can be scrolled before it hits the bottom
907908
const maxScroll = scrollContainerHeight - panelHeight;
908909

909-
if (!this.empty) {
910-
let selectedOptionOffset = this._getOptionIndex(this._selectionModel.selected[0])!;
910+
// If no value is selected we open the popup to the first item.
911+
let selectedOptionOffset =
912+
this.empty ? 0 : this._getOptionIndex(this._selectionModel.selected[0])!;
911913

912-
selectedOptionOffset += MdOption.countGroupLabelsBeforeOption(selectedOptionOffset,
913-
this.options, this.optionGroups);
914+
selectedOptionOffset += MdOption.countGroupLabelsBeforeOption(selectedOptionOffset,
915+
this.options, this.optionGroups);
914916

915-
// We must maintain a scroll buffer so the selected option will be scrolled to the
916-
// center of the overlay panel rather than the top.
917-
const scrollBuffer = panelHeight / 2;
918-
this._scrollTop = this._calculateOverlayScroll(selectedOptionOffset, scrollBuffer, maxScroll);
919-
this._offsetY = this._calculateOverlayOffsetY(selectedOptionOffset, scrollBuffer, maxScroll);
920-
} else {
921-
// If no option is selected, the panel centers on the first option. In this case,
922-
// we must only adjust for the height difference between the option element
923-
// and the trigger element, then multiply it by -1 to ensure the panel moves
924-
// in the correct direction up the page.
925-
let groupLabels = MdOption.countGroupLabelsBeforeOption(0, this.options, this.optionGroups);
926-
927-
this._offsetY = (SELECT_ITEM_HEIGHT - SELECT_TRIGGER_HEIGHT) / 2 * -1 -
928-
(groupLabels * SELECT_ITEM_HEIGHT);
929-
}
917+
// We must maintain a scroll buffer so the selected option will be scrolled to the
918+
// center of the overlay panel rather than the top.
919+
const scrollBuffer = panelHeight / 2;
920+
this._scrollTop = this._calculateOverlayScroll(selectedOptionOffset, scrollBuffer, maxScroll);
921+
this._offsetY = this._calculateOverlayOffsetY(selectedOptionOffset, scrollBuffer, maxScroll);
930922

931923
this._checkOverlayWithinViewport(maxScroll);
932924
}
@@ -940,8 +932,9 @@ export class MdSelect extends _MdSelectMixinBase implements AfterContentInit, On
940932
*/
941933
_calculateOverlayScroll(selectedIndex: number, scrollBuffer: number,
942934
maxScroll: number): number {
943-
const optionOffsetFromScrollTop = SELECT_ITEM_HEIGHT * selectedIndex;
944-
const halfOptionHeight = SELECT_ITEM_HEIGHT / 2;
935+
const itemHeight = this._triggerFontSize * SELECT_ITEM_HEIGHT_EM;
936+
const optionOffsetFromScrollTop = itemHeight * selectedIndex;
937+
const halfOptionHeight = itemHeight / 2;
945938

946939
// Starts at the optionOffsetFromScrollTop, which scrolls the option to the top of the
947940
// scroll container, then subtracts the scroll buffer to scroll the option down to
@@ -1011,31 +1004,34 @@ export class MdSelect extends _MdSelectMixinBase implements AfterContentInit, On
10111004
*/
10121005
private _calculateOverlayOffsetY(selectedIndex: number, scrollBuffer: number,
10131006
maxScroll: number): number {
1007+
const itemHeight = this._triggerFontSize * SELECT_ITEM_HEIGHT_EM;
1008+
const optionHeightAdjustment = (itemHeight - this._triggerHeight) / 2;
1009+
const maxOptionsDisplayed = Math.floor(SELECT_PANEL_MAX_HEIGHT / itemHeight);
10141010
let optionOffsetFromPanelTop: number;
10151011

10161012
if (this._scrollTop === 0) {
1017-
optionOffsetFromPanelTop = selectedIndex * SELECT_ITEM_HEIGHT;
1013+
optionOffsetFromPanelTop = selectedIndex * itemHeight;
10181014
} else if (this._scrollTop === maxScroll) {
1019-
const firstDisplayedIndex = this._getItemCount() - SELECT_MAX_OPTIONS_DISPLAYED;
1015+
const firstDisplayedIndex = this._getItemCount() - maxOptionsDisplayed;
10201016
const selectedDisplayIndex = selectedIndex - firstDisplayedIndex;
10211017

10221018
// Because the panel height is longer than the height of the options alone,
10231019
// there is always extra padding at the top or bottom of the panel. When
10241020
// scrolled to the very bottom, this padding is at the top of the panel and
10251021
// must be added to the offset.
10261022
optionOffsetFromPanelTop =
1027-
selectedDisplayIndex * SELECT_ITEM_HEIGHT + SELECT_PANEL_PADDING_Y;
1023+
selectedDisplayIndex * itemHeight + SELECT_PANEL_PADDING_Y;
10281024
} else {
10291025
// If the option was scrolled to the middle of the panel using a scroll buffer,
10301026
// its offset will be the scroll buffer minus the half height that was added to
10311027
// center it.
1032-
optionOffsetFromPanelTop = scrollBuffer - SELECT_ITEM_HEIGHT / 2;
1028+
optionOffsetFromPanelTop = scrollBuffer - itemHeight / 2;
10331029
}
10341030

10351031
// The final offset is the option's offset from the top, adjusted for the height
10361032
// difference, multiplied by -1 to ensure that the overlay moves in the correct
10371033
// direction up the page.
1038-
return optionOffsetFromPanelTop * -1 - SELECT_OPTION_HEIGHT_ADJUSTMENT;
1034+
return optionOffsetFromPanelTop * -1 - optionHeightAdjustment;
10391035
}
10401036

10411037
/**
@@ -1045,6 +1041,7 @@ export class MdSelect extends _MdSelectMixinBase implements AfterContentInit, On
10451041
* sets the offset back to 0 to allow the fallback position to take over.
10461042
*/
10471043
private _checkOverlayWithinViewport(maxScroll: number): void {
1044+
const itemHeight = this._triggerFontSize * SELECT_ITEM_HEIGHT_EM;
10481045
const viewportRect = this._viewportRuler.getViewportRect();
10491046
const triggerRect = this._getTriggerRect();
10501047

@@ -1054,7 +1051,7 @@ export class MdSelect extends _MdSelectMixinBase implements AfterContentInit, On
10541051

10551052
const panelHeightTop = Math.abs(this._offsetY);
10561053
const totalPanelHeight =
1057-
Math.min(this._getItemCount() * SELECT_ITEM_HEIGHT, SELECT_PANEL_MAX_HEIGHT);
1054+
Math.min(this._getItemCount() * itemHeight, SELECT_PANEL_MAX_HEIGHT);
10581055
const panelHeightBottom = totalPanelHeight - panelHeightTop - triggerRect.height;
10591056

10601057
if (panelHeightBottom > bottomSpaceAvailable) {
@@ -1110,8 +1107,9 @@ export class MdSelect extends _MdSelectMixinBase implements AfterContentInit, On
11101107

11111108
/** Sets the transform origin point based on the selected option. */
11121109
private _getOriginBasedOnOption(): string {
1113-
const originY =
1114-
Math.abs(this._offsetY) - SELECT_OPTION_HEIGHT_ADJUSTMENT + SELECT_ITEM_HEIGHT / 2;
1110+
const itemHeight = this._triggerFontSize * SELECT_ITEM_HEIGHT_EM;
1111+
const optionHeightAdjustment = (itemHeight - this._triggerHeight) / 2;
1112+
const originY = Math.abs(this._offsetY) - optionHeightAdjustment + itemHeight / 2;
11151113
return `50% ${originY}px 0px`;
11161114
}
11171115

0 commit comments

Comments
 (0)