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

Commit

Permalink
feat(menu): Menu Improvements - Complete overhaul
Browse files Browse the repository at this point in the history
* New mdc-menu-divider component * New mdc-menu-anchor directive * New mdc-menu-item directive *
Update MDC Menu for MDC v0.13.0

BREAKING CHANGE: Removed [items] property. You'll need to start using the new mdc-menu-item
directive.
  • Loading branch information
trimox committed Jun 14, 2017
1 parent 0d353aa commit cb4061d
Show file tree
Hide file tree
Showing 9 changed files with 104 additions and 55 deletions.
10 changes: 7 additions & 3 deletions src/lib/menu/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

import { MenuComponent } from './menu';
import { MenuItemDirective } from './menu-item';
import { MenuComponent } from './menu.component';
import { MenuItemDirective } from './menu-item.directive';
import { MenuAnchorDirective } from './menu-anchor.directive';
import { MenuDividerComponent } from './menu-divider.component';

const MENU_COMPONENTS = [
MenuComponent,
MenuItemDirective
MenuItemDirective,
MenuAnchorDirective,
MenuDividerComponent
];

@NgModule({
Expand Down
5 changes: 3 additions & 2 deletions src/lib/menu/menu-adapter.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export interface MDCMenuAdapter {
removeClass: (string) => void
hasClass: (string) => void
hasNecessaryDom: () => boolean
getAttributeForEventTarget: (target: EventTarget, attributeName: string) => string
getInnerDimensions: () => { width: number, height: number }
hasAnchor: () => boolean
getAnchorDimensions: () => { width: number, height: number, top: number, right: number, bottom: number, left: number }
Expand All @@ -12,8 +13,8 @@ export interface MDCMenuAdapter {
getNumberOfItems: () => number
registerInteractionHandler: (type: string, handler: EventListener) => void
deregisterInteractionHandler: (type: string, handler: EventListener) => void
registerDocumentClickHandler: (handler: EventListener) => void
deregisterDocumentClickHandler: (handler: EventListener) => void
registerBodyClickHandler: (handler: EventListener) => void
deregisterBodyClickHandler: (handler: EventListener) => void
getYParamsForItemAtIndex: (index: number) => { top: number, height: number }
setTransitionDelayForItemAtIndex: (index: number, value: string) => void
getIndexForEventTarget: (target: EventTarget) => number
Expand Down
11 changes: 11 additions & 0 deletions src/lib/menu/menu-anchor.directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import {
Directive,
HostBinding,
} from '@angular/core';

@Directive({
selector: '[mdc-menu-anchor]'
})
export class MenuAnchorDirective {
@HostBinding('class') className: string = 'mdc-menu-anchor';
}
9 changes: 9 additions & 0 deletions src/lib/menu/menu-divider.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import {
Component
} from '@angular/core';

@Component({
selector: 'mdc-menu-divider',
template: '<li class="mdc-list-divider" role="seperator"><ng-content></ng-content></li>'
})
export class MenuDividerComponent { }
40 changes: 40 additions & 0 deletions src/lib/menu/menu-item.directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import {
Directive,
ElementRef,
HostBinding,
Input,
Renderer2,
} from '@angular/core';

@Directive({
selector: 'mdc-menu-item'
})
export class MenuItemDirective {
private disabled_: boolean = false;

@Input() id: string;
@Input() label: string;
@Input() icon: string;
@Input()
get disabled() {
return this.disabled_;
}
set disabled(value: boolean) {
this.disabled_ = value;
if (value) {
this._renderer.setAttribute(this._root.nativeElement, 'aria-disabled', 'true');
this.tabindex = -1;
} else {
this._renderer.removeAttribute(this._root.nativeElement, 'aria-disabled');
this.tabindex = 0;
}
}
@HostBinding('class') className: string = 'mdc-list-item';
@HostBinding('attr.role') role: string = 'menuitem';
@HostBinding('tabindex') tabindex: number = 0;
itemEl: ElementRef = this._root;

constructor(
private _renderer: Renderer2,
private _root: ElementRef) { }
}
19 changes: 0 additions & 19 deletions src/lib/menu/menu-item.ts

This file was deleted.

3 changes: 3 additions & 0 deletions src/lib/menu/menu.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<ul #menuContainer class="mdc-simple-menu__items mdc-list" role="menu" aria-hidden="true">
<ng-content select="mdc-menu-item, mdc-menu-divider"></ng-content>
</ul>
59 changes: 31 additions & 28 deletions src/lib/menu/menu.ts → src/lib/menu/menu.component.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import {
AfterViewInit,
Component,
ContentChildren,
ElementRef,
EventEmitter,
HostBinding,
Input,
Output,
OnDestroy,
Output,
QueryList,
Renderer2,
ViewChild,
ViewChildren,
ViewEncapsulation
} from '@angular/core';
import { MDCMenuAdapter } from './menu-adapter';
import { MenuItemDirective } from './menu-item';
import { MenuItemDirective } from './menu-item.directive';

const { MDCSimpleMenuFoundation } = require('@material/menu');
const { MDCSimpleMenuFoundation } = require('@material/menu/simple');
const { getTransformPropertyName } = require('@material/menu/util');
const MDC_MENU_STYLES = require('@material/menu/mdc-menu.scss');
const MDC_LIST_STYLES = require('@material/list/mdc-list.scss');
Expand All @@ -25,18 +25,19 @@ type UnlistenerMap = WeakMap<EventListener, Function>;

@Component({
selector: 'mdc-menu',
templateUrl: './menu.html',
styles: [String(MDC_MENU_STYLES)],
templateUrl: './menu.component.html',
styles: [String(MDC_MENU_STYLES), String(MDC_LIST_STYLES)],
encapsulation: ViewEncapsulation.None
})
export class MenuComponent implements AfterViewInit, OnDestroy {
@Input() items: MenuItemDirective[];
private previousFocus_: any;

@Output() cancel: EventEmitter<void> = new EventEmitter<void>();
@Output() select: EventEmitter<number> = new EventEmitter<number>();
@HostBinding('class') className: string = 'mdc-simple-menu';
@HostBinding('tabindex') tabindex: number = -1;
@ViewChild('itemsContainer') public itemsContainerEl: ElementRef;
@ViewChildren(MenuItemDirective) menuItems: QueryList<MenuItemDirective>;
@ViewChild('menuContainer') public menuContainerEl: ElementRef;
@ContentChildren(MenuItemDirective) menuItems: QueryList<MenuItemDirective>;

private _unlisteners: Map<string, UnlistenerMap> = new Map<string, UnlistenerMap>();

Expand All @@ -49,11 +50,14 @@ export class MenuComponent implements AfterViewInit, OnDestroy {
const { _renderer: renderer, _root: root } = this;
renderer.removeClass(root.nativeElement, className);
},
getAttributeForEventTarget: (target: any, attributeName) => {
return target.getAttribute(attributeName);
},
hasClass: (className: string) => {
const { _root: root } = this;
return root.nativeElement.classList.contains(className);
},
hasNecessaryDom: () => Boolean(this.itemsContainerEl),
hasNecessaryDom: () => Boolean(this.menuContainerEl),
getInnerDimensions: () => {
const { _root: root } = this;
return {
Expand All @@ -80,13 +84,11 @@ export class MenuComponent implements AfterViewInit, OnDestroy {
renderer.setStyle(root.nativeElement, getTransformPropertyName(window), `scale(${x}, ${y})`);
},
setInnerScale: (x: number, y: number) => {
if (this.itemsContainerEl) {
const { _renderer: renderer, _root: root } = this;
renderer.setStyle(this.itemsContainerEl.nativeElement, getTransformPropertyName(window), `scale(${x}, ${y})`);
}
const { _renderer: renderer, _root: root } = this;
renderer.setStyle(this.menuContainerEl.nativeElement, getTransformPropertyName(window), `scale(${x}, ${y})`);
},
getNumberOfItems: () => {
return this.items ? this.items.length : 0;
return this.menuItems ? this.menuItems.length : 0;
},
registerInteractionHandler: (type: string, handler: EventListener) => {
if (this._root) {
Expand All @@ -96,36 +98,37 @@ export class MenuComponent implements AfterViewInit, OnDestroy {
deregisterInteractionHandler: (type: string, handler: EventListener) => {
this.unlisten_(type, handler);
},
registerDocumentClickHandler: (handler: EventListener) => {
registerBodyClickHandler: (handler: EventListener) => {
if (this._root) {
this.listen_('click', handler, this._root.nativeElement.ownerDocument);
}
},
deregisterDocumentClickHandler: (handler: EventListener) => {
deregisterBodyClickHandler: (handler: EventListener) => {
this.unlisten_('click', handler);
},
getYParamsForItemAtIndex: (index: number) => {
const { offsetTop: top, offsetHeight: height } = this.menuItems.toArray()[index].root.nativeElement;
const { offsetTop: top, offsetHeight: height } = this.menuItems.toArray()[index].itemEl.nativeElement;
return { top, height };
},
setTransitionDelayForItemAtIndex: (index: number, value: string) => {
const { _renderer: renderer, _root: root } = this;
renderer.setStyle(this.menuItems.toArray()[index].root.nativeElement, 'transition-delay', value);
renderer.setStyle(this.menuItems.toArray()[index].itemEl.nativeElement, 'transition-delay', value);
},
getIndexForEventTarget: (target: any) => {
if (!target.attributes.id) {
return -1;
}
return this.items.findIndex(_ => _.id == target.attributes.id.value);
getIndexForEventTarget: (target: EventTarget) => {
return this.menuItems.toArray().findIndex((_) => _.itemEl.nativeElement === target);
},
notifySelected: (evtData) => {
this.select.emit(evtData.index);
},
notifyCancel: () => {
this.cancel.emit();
},
saveFocus: () => { }, /* TODO */
restoreFocus: () => { }, /* TODO */
saveFocus: () => this.previousFocus_ = document.activeElement,
restoreFocus: () => {
if (this.previousFocus_) {
this.previousFocus_.focus()
}
},
isFocused: () => {
const { _root: root } = this;
return root.nativeElement.ownerDocument.activeElement === root.nativeElement;
Expand All @@ -136,12 +139,12 @@ export class MenuComponent implements AfterViewInit, OnDestroy {
getFocusedItemIndex: () => {
const { _root: root } = this;
return this.menuItems.length ? this.menuItems.toArray().findIndex(_ =>
_.root.nativeElement === root.nativeElement.ownerDocument.activeElement) : -1;
_.itemEl.nativeElement === root.nativeElement.ownerDocument.activeElement) : -1;
},
focusItemAtIndex: (index: number) => {
const { _root: root } = this;
if (this.menuItems.toArray()[index] !== undefined) {
this.menuItems.toArray()[index].root.nativeElement.focus();
this.menuItems.toArray()[index].itemEl.nativeElement.focus();
} else {
// set focus back to root element when index is undefined
root.nativeElement.focus();
Expand Down
3 changes: 0 additions & 3 deletions src/lib/menu/menu.html

This file was deleted.

0 comments on commit cb4061d

Please sign in to comment.