Skip to content
This repository has been archived by the owner on Oct 7, 2020. It is now read-only.

feat(menu-surface): Add fullwidth property #2259

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions demos/src/app/components/menu-surface/menu-surface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export class Api implements OnInit {
{name: 'anchorElement: Element | mdcMenuSurfaceAnchor', summary: `Set to indicate an element the menu should be anchored to.`},
{name: 'fixed: boolean', summary: `Used to indicate that the menu is using fixed positioning.`},
{name: 'hoistToBody: boolean', summary: `Removes the menu-surface element from the DOM and appends it to the body element. Should be used to overcome overflow: hidden issues.`},
{name: 'fullwidth: boolean', summary: `Sets the menu-surface's width to match that of its parent anchor. Do not use with 'fixed'.`},
]
},
]
Expand Down
23 changes: 13 additions & 10 deletions demos/src/app/components/select/examples.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ <h3 class="demo-content__headline">Select</h3>
<button mdc-button
(click)="standardSelect.disabled = !standardSelect.disabled">Disabled:
{{standardSelect.disabled ? 'On' : 'Off'}}</button>
<button mdc-button
(click)="simpleMenu.fullwidth = !simpleMenu.fullwidth">Menu Fullwidth:
{{simpleMenu.fullwidth ? 'On' : 'Off'}}</button>
<button mdc-button
(click)="standardSelect.setSelectionByValue('banana')">Select
Banana</button>
Expand All @@ -22,7 +25,7 @@ <h3 class="demo-content__headline">Select</h3>
<mdc-select #standardSelect placeholder="Fruit" name="my-select" required
helper="Helper text" helperPersistent validationMessage="Field is required"
(selectionChange)="onSelectionChange($event)">
<mdc-menu class="demo-select-width" [anchorMargin]="{top: 10}">
<mdc-menu #simpleMenu fullwidth>
<mdc-list>
<mdc-list-item></mdc-list-item>
<mdc-list-item value="apple">Apple</mdc-list-item>
Expand All @@ -40,7 +43,7 @@ <h3 class="demo-content__headline">Select</h3>
<div class="demo-content">
<h3 class="demo-content__headline">No label</h3>
<mdc-select>
<mdc-menu class="demo-select-width" [anchorMargin]="{top: 10}">
<mdc-menu [anchorMargin]="{top: 10}">
<mdc-list>
<mdc-list-item></mdc-list-item>
<mdc-list-item value="apple">Apple</mdc-list-item>
Expand All @@ -67,7 +70,7 @@ <h3 class="demo-content__headline">Leading Icon</h3>
<mdc-select #meal required validationMessage="Meal selection is required"
placeholder="Pick a Meal">
<mdc-icon mdcSelectIcon>fastfood</mdc-icon>
<mdc-menu class="demo-select-width">
<mdc-menu>
<mdc-list>
<mdc-list-item *ngFor="let food of foods" [value]="food.value"
[disabled]="food.disabled">
Expand All @@ -84,7 +87,7 @@ <h3 class="demo-content__headline">Custom Enhanced</h3>
<div class="demo-layout__row">
<div class="demo-container">
<mdc-select placeholder="Standard" class="custom-select-shape-radius">
<mdc-menu class="demo-select-width">
<mdc-menu>
<mdc-list>
<mdc-list-item *ngFor="let food of foods" [value]="food.value"
[disabled]="food.disabled">
Expand All @@ -97,7 +100,7 @@ <h3 class="demo-content__headline">Custom Enhanced</h3>
<div class="demo-container">
<mdc-select placeholder="Standard" outlined
class="custom-select-outline-shape-radius">
<mdc-menu class="demo-select-width">
<mdc-menu>
<mdc-list>
<mdc-list-item *ngFor="let food of foods" [value]="food.value"
[disabled]="food.disabled">
Expand All @@ -110,7 +113,7 @@ <h3 class="demo-content__headline">Custom Enhanced</h3>
<div class="demo-container">
<mdc-select placeholder="Standard" outlined
class="custom-select-outline-color">
<mdc-menu class="demo-select-width">
<mdc-menu>
<mdc-list>
<mdc-list-item *ngFor="let food of foods" [value]="food.value"
[disabled]="food.disabled">
Expand All @@ -129,7 +132,7 @@ <h3 class="demo-content__headline">Lazy Load</h3>
<form [formGroup]="lazyLoadForm" #formDirectiveLazy="ngForm">
<mdc-select outlined formControlName="lazySelect"
validationMessage="Selection is required" placeholder="Example">
<mdc-menu class="demo-select-width">
<mdc-menu>
<mdc-list>
<mdc-list-item *ngFor="let food of lazyFoods" [value]="food.value"
[disabled]="food.disabled">{{food.viewValue}}</mdc-list-item>
Expand All @@ -150,7 +153,7 @@ <h3 class="demo-content__headline">Lazy Load</h3>
<h3 class="demo-content__headline">Select with [compareWith]</h3>
<form [formGroup]="formCompareWith">
<mdc-select formControlName="fruit" [compareWith]="compareFn">
<mdc-menu class="demo-select-width">
<mdc-menu>
<mdc-list>
<mdc-list-item *ngFor="let fruit of fruits" [value]="fruit">
{{fruit.name}}</mdc-list-item>
Expand Down Expand Up @@ -179,7 +182,7 @@ <h3 class="demo-content__headline">Select with ngModel</h3>
<mdc-select #select placeholder="Favorite food" name="food" required
outlined ngModel #demoNgModel="ngModel"
(selectionChange)="onSelectionChange($event)">
<mdc-menu class="demo-select-width">
<mdc-menu>
<mdc-list>
<mdc-list-item *ngFor="let food of foods" [value]="food.value"
[disabled]="food.disabled">{{food.viewValue}}</mdc-list-item>
Expand Down Expand Up @@ -218,7 +221,7 @@ <h3 class="demo-content__headline">Select with FormControl</h3>
#formDirective="ngForm">
<mdc-select #favoriteFood placeholder="Favorite food"
formControlName="favoriteFood" validationMessage="Selection is required">
<mdc-menu class="demo-select-width">
<mdc-menu>
<mdc-list>
<mdc-list-item *ngFor="let food of foods" [value]="food.value"
[disabled]="food.disabled">{{food.viewValue}}</mdc-list-item>
Expand Down
4 changes: 0 additions & 4 deletions demos/src/styles/_select.scss
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,3 @@
.custom-select-outline-shape-radius {
@include select.outline-shape-radius(50%);
}

.demo-select-width {
width: 200px;
}
116 changes: 83 additions & 33 deletions packages/menu-surface/menu-surface-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import {
EventEmitter,
NgZone,
Input,
OnChanges,
Optional,
Output
Output,
SimpleChanges,
} from '@angular/core';
import {coerceBooleanProperty} from '@angular/cdk/coercion';
import {Platform} from '@angular/cdk/platform';
Expand Down Expand Up @@ -43,12 +45,16 @@ const ANCHOR_CORNER_MAP = {
};

@Directive()
export abstract class MdcMenuSurfaceBase extends MDCComponent<MDCMenuSurfaceFoundation> {
export abstract class MdcMenuSurfaceBase extends MDCComponent<MDCMenuSurfaceFoundation>
implements OnChanges {
/** Emits whenever the component is destroyed. */
private _destroy = new Subject<void>();

private _initialized = false;
private _previousFocus?: Element;

_root!: Element;

@Input()
get open(): boolean {
return this._open;
Expand Down Expand Up @@ -101,12 +107,7 @@ export abstract class MdcMenuSurfaceBase extends MDCComponent<MDCMenuSurfaceFoun
}
set fixed(value: boolean) {
this._fixed = coerceBooleanProperty(value);
if (this._fixed) {
this._getHostElement().classList.add('mdc-menu-surface--fixed');
} else {
this._getHostElement().classList.remove('mdc-menu-surface--fixed');
}
this._foundation.setFixedPosition(this._fixed);
this._syncSurfaceFixed();
}
private _fixed: boolean = false;

Expand Down Expand Up @@ -142,6 +143,16 @@ export abstract class MdcMenuSurfaceBase extends MDCComponent<MDCMenuSurfaceFoun
}
private _hoistToBody: boolean = false;

@Input()
get fullwidth(): boolean {
return this._fullwidth;
}
set fullwidth(value: boolean) {
this._fullwidth = coerceBooleanProperty(value);
this._syncSurfaceFullwidth();
}
private _fullwidth: boolean = false;

/** Emits an event whenever the menu surface is opened. */
@Output() readonly opened: EventEmitter<void> = new EventEmitter<void>();

Expand All @@ -153,9 +164,9 @@ export abstract class MdcMenuSurfaceBase extends MDCComponent<MDCMenuSurfaceFoun

getDefaultFoundation() {
const adapter: MDCMenuSurfaceAdapter = {
addClass: (className: string) => this._getHostElement().classList.add(className),
removeClass: (className: string) => this._getHostElement().classList.remove(className),
hasClass: (className: string) => this._getHostElement().classList.contains(className),
addClass: (className: string) => this._root.classList.add(className),
removeClass: (className: string) => this._root.classList.remove(className),
hasClass: (className: string) => this._root.classList.contains(className),
hasAnchor: () => !!this.anchorElement,
notifyClose: () => {
this.closed.emit();
Expand All @@ -165,26 +176,36 @@ export abstract class MdcMenuSurfaceBase extends MDCComponent<MDCMenuSurfaceFoun
this.opened.emit();
this._registerWindowClickListener();
},
isElementInContainer: (el: Element) => this._getHostElement() === el || this._getHostElement().contains(el),
isElementInContainer: (el: Element) => this._root === el || this._root.contains(el),
isRtl: () => this.platform.isBrowser ?
window.getComputedStyle(this._getHostElement()).getPropertyValue('direction') === 'rtl' : false,
window.getComputedStyle(this._root).getPropertyValue('direction') === 'rtl' : false,
setTransformOrigin: (origin: string) =>
this.platform.isBrowser ?
this._getHostElement().style[`${util.getTransformPropertyName(window)}-origin` as any] = origin : false,
isFocused: () => document?.activeElement === this._getHostElement() ?? false,
(this._root as HTMLElement).style[`${util.getTransformPropertyName(window)}-origin` as any] = origin : false,
isFocused: () => document?.activeElement === this._root ?? false,
saveFocus: () => this._previousFocus = document?.activeElement ?? undefined,
restoreFocus: () => {
if (this.platform.isBrowser) {
if (this._getHostElement().contains(document.activeElement)) {
if (this._root.contains(document.activeElement)) {
(<HTMLElement>this._previousFocus)?.focus();
}
}
},
getInnerDimensions: () =>
({width: this._getHostElement().offsetWidth, height: this._getHostElement().offsetHeight}),
({
width: (this._root as HTMLElement).offsetWidth,
height: (this._root as HTMLElement).offsetHeight
}),
getAnchorDimensions: () =>
this.platform.isBrowser || !this.anchorElement ?
this._anchorElement!.getBoundingClientRect() : {top: 0, right: 0, bottom: 0, left: 0, width: 0, height: 0},
this._anchorElement!.getBoundingClientRect() : {
top: 0,
right: 0,
bottom: 0,
left: 0,
width: 0,
height: 0
},
getWindowDimensions: () => ({
width: this.platform.isBrowser ? window.innerWidth : 0,
height: this.platform.isBrowser ? window.innerHeight : 0
Expand All @@ -198,12 +219,12 @@ export abstract class MdcMenuSurfaceBase extends MDCComponent<MDCMenuSurfaceFoun
y: this.platform.isBrowser ? window.pageYOffset : 0
}),
setPosition: (position: {left: number, right: number, top: number, bottom: number}) => {
this._getHostElement().style.left = 'left' in position ? `${position.left}px` : '';
this._getHostElement().style.right = 'right' in position ? `${position.right}px` : '';
this._getHostElement().style.top = 'top' in position ? `${position.top}px` : '';
this._getHostElement().style.bottom = 'bottom' in position ? `${position.bottom}px` : '';
(this._root as HTMLElement).style.left = 'left' in position ? `${position.left}px` : '';
(this._root as HTMLElement).style.right = 'right' in position ? `${position.right}px` : '';
(this._root as HTMLElement).style.top = 'top' in position ? `${position.top}px` : '';
(this._root as HTMLElement).style.bottom = 'bottom' in position ? `${position.bottom}px` : '';
},
setMaxHeight: (height: string) => this._getHostElement().style.maxHeight = height
setMaxHeight: (height: string) => (this._root as HTMLElement).style.maxHeight = height
};
return new MDCMenuSurfaceFoundation(adapter);
}
Expand All @@ -214,11 +235,28 @@ export abstract class MdcMenuSurfaceBase extends MDCComponent<MDCMenuSurfaceFoun
@Optional() private _ngZone: NgZone,
public elementRef: ElementRef<HTMLElement>) {
super(elementRef);

this._root = this.elementRef.nativeElement;
}

ngOnChanges(changes: SimpleChanges) {
if (!this._initialized) {
return;
}

if (changes['fixed']) {
this._syncSurfaceFixed();
}

if (changes['fullwidth']) {
this._syncSurfaceFullwidth();
}
}

protected initMenuSurface(): void {
this._initialized = true;
this._foundation.init();
this.anchorElement = this._getHostElement().parentElement ?? this.anchorElement;
this.anchorElement = this._root.parentElement ?? this.anchorElement;
this._registerKeydownListener();
}

Expand All @@ -234,7 +272,24 @@ export abstract class MdcMenuSurfaceBase extends MDCComponent<MDCMenuSurfaceFoun
}

if (this.hoistToBody) {
document.body!.removeChild(this._getHostElement());
document.body!.removeChild(this._root);
}
}

private _syncSurfaceFixed(): void {
if (this.fixed) {
this._root.classList.add('mdc-menu-surface--fixed');
} else {
this._root.classList.remove('mdc-menu-surface--fixed');
}
this._foundation.setFixedPosition(this.fixed);
}

private _syncSurfaceFullwidth(): void {
if (this._fullwidth) {
this._root.classList.add('mdc-menu-surface--fullwidth');
} else {
this._root.classList.remove('mdc-menu-surface--fullwidth');
}
}

Expand All @@ -247,15 +302,15 @@ export abstract class MdcMenuSurfaceBase extends MDCComponent<MDCMenuSurfaceFoun
return;
}

const parentEl = this._getHostElement().parentElement;
const parentEl = this._root.parentElement;
if (parentEl) {
document.body!.appendChild(parentEl.removeChild(this._getHostElement()));
document.body!.appendChild(parentEl.removeChild(this._root));
this._foundation.setIsHoisted(true);
}
}

private _registerKeydownListener(): void {
fromEvent<KeyboardEvent>(this._getHostElement(), 'keydown')
fromEvent<KeyboardEvent>(this._root, 'keydown')
.pipe(takeUntil(this._destroy))
.subscribe(evt => {
this._foundation.handleKeydown(evt);
Expand All @@ -280,9 +335,4 @@ export abstract class MdcMenuSurfaceBase extends MDCComponent<MDCMenuSurfaceFoun
private _deregisterWindowClickListener(): void {
this._windowClickSubscription?.unsubscribe();
}

/** Retrieves the DOM element of the component host. */
protected _getHostElement(): HTMLElement {
return this.elementRef.nativeElement;
}
}