mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
prototype(menu): create prototype menu based on MDC Web (#15857)
* prototype(menu): create prototype menu based on MDC Web Creates a prototype of `mat-menu` that uses MDC web for styling. * refactor: avoid duplicating mat-menu styles and template
This commit is contained in:
committed by
mmalerba
parent
8b5c0f12c8
commit
8f2391720d
@@ -1 +1,208 @@
|
||||
// TODO: copy tests from existing mat-menu, update as necessary to fix.
|
||||
import {browser, by, element, ExpectedConditions, Key, protractor} from 'protractor';
|
||||
import {
|
||||
expectAlignedWith,
|
||||
expectFocusOn,
|
||||
expectLocation,
|
||||
expectToExist,
|
||||
pressKeys,
|
||||
} from '../util/index';
|
||||
|
||||
const presenceOf = ExpectedConditions.presenceOf;
|
||||
const not = ExpectedConditions.not;
|
||||
|
||||
describe('menu', () => {
|
||||
const menuSelector = '.mat-mdc-menu-panel';
|
||||
const page = {
|
||||
menu: () => element(by.css(menuSelector)),
|
||||
start: () => element(by.id('start')),
|
||||
trigger: () => element(by.id('trigger')),
|
||||
triggerTwo: () => element(by.id('trigger-two')),
|
||||
backdrop: () => element(by.css('.cdk-overlay-backdrop')),
|
||||
items: (index: number) => element.all(by.css('[mat-menu-item]')).get(index),
|
||||
textArea: () => element(by.id('text')),
|
||||
beforeTrigger: () => element(by.id('before-t')),
|
||||
aboveTrigger: () => element(by.id('above-t')),
|
||||
combinedTrigger: () => element(by.id('combined-t')),
|
||||
beforeMenu: () => element(by.css(`${menuSelector}.before`)),
|
||||
aboveMenu: () => element(by.css(`${menuSelector}.above`)),
|
||||
combinedMenu: () => element(by.css(`${menuSelector}.combined`)),
|
||||
getResultText: () => page.textArea().getText(),
|
||||
};
|
||||
|
||||
beforeEach(async () => await browser.get('/mdc-menu'));
|
||||
|
||||
it('should open menu when the trigger is clicked', async () => {
|
||||
await expectToExist(menuSelector, false);
|
||||
await page.trigger().click();
|
||||
|
||||
await expectToExist(menuSelector);
|
||||
expect(await page.menu().getText()).toEqual('One\nTwo\nThree\nFour');
|
||||
});
|
||||
|
||||
it('should close menu when menu item is clicked', async () => {
|
||||
await page.trigger().click();
|
||||
await page.items(0).click();
|
||||
await expectToExist(menuSelector, false);
|
||||
});
|
||||
|
||||
it('should run click handlers on regular menu items', async () => {
|
||||
await page.trigger().click();
|
||||
await page.items(0).click();
|
||||
expect(await page.getResultText()).toEqual('one');
|
||||
|
||||
await page.trigger().click();
|
||||
await page.items(1).click();
|
||||
expect(await page.getResultText()).toEqual('two');
|
||||
});
|
||||
|
||||
it('should run not run click handlers on disabled menu items', async () => {
|
||||
await page.trigger().click();
|
||||
await page.items(2).click();
|
||||
expect(await page.getResultText()).toEqual('');
|
||||
});
|
||||
|
||||
it('should support multiple triggers opening the same menu', async () => {
|
||||
await page.triggerTwo().click();
|
||||
|
||||
expect(await page.menu().getText()).toEqual('One\nTwo\nThree\nFour');
|
||||
await expectAlignedWith(page.menu(), '#trigger-two');
|
||||
|
||||
await page.backdrop().click();
|
||||
await browser.wait(not(presenceOf(element(by.css(menuSelector)))));
|
||||
await browser.wait(not(presenceOf(element(by.css('.cdk-overlay-backdrop')))));
|
||||
|
||||
await page.trigger().click();
|
||||
|
||||
expect(await page.menu().getText()).toEqual('One\nTwo\nThree\nFour');
|
||||
await expectAlignedWith(page.menu(), '#trigger');
|
||||
|
||||
await page.backdrop().click();
|
||||
|
||||
await browser.wait(not(presenceOf(element(by.css(menuSelector)))));
|
||||
await browser.wait(not(presenceOf(element(by.css('.cdk-overlay-backdrop')))));
|
||||
});
|
||||
|
||||
it('should mirror classes on host to menu template in overlay', async () => {
|
||||
await page.trigger().click();
|
||||
expect(await page.menu().getAttribute('class')).toContain('mat-mdc-menu-panel');
|
||||
expect(await page.menu().getAttribute('class')).toContain('custom');
|
||||
});
|
||||
|
||||
describe('keyboard events', () => {
|
||||
beforeEach(async () => {
|
||||
// click start button to avoid tabbing past navigation
|
||||
await page.start().click();
|
||||
await pressKeys(Key.TAB);
|
||||
});
|
||||
|
||||
it('should auto-focus the first item when opened with ENTER', async () => {
|
||||
await pressKeys(Key.ENTER);
|
||||
await expectFocusOn(page.items(0));
|
||||
});
|
||||
|
||||
it('should auto-focus the first item when opened with SPACE', async () => {
|
||||
await pressKeys(Key.SPACE);
|
||||
await expectFocusOn(page.items(0));
|
||||
});
|
||||
|
||||
it('should focus the first item when opened by mouse', async () => {
|
||||
await page.trigger().click();
|
||||
await expectFocusOn(page.items(0));
|
||||
});
|
||||
|
||||
it('should focus subsequent items when down arrow is pressed', async () => {
|
||||
await pressKeys(Key.ENTER, Key.DOWN);
|
||||
await expectFocusOn(page.items(1));
|
||||
});
|
||||
|
||||
it('should focus previous items when up arrow is pressed', async () => {
|
||||
await pressKeys(Key.ENTER, Key.DOWN, Key.UP);
|
||||
await expectFocusOn(page.items(0));
|
||||
});
|
||||
|
||||
it('should skip disabled items using arrow keys', async () => {
|
||||
await pressKeys(Key.ENTER, Key.DOWN, Key.DOWN);
|
||||
await expectFocusOn(page.items(3));
|
||||
|
||||
await pressKeys(Key.UP);
|
||||
await expectFocusOn(page.items(1));
|
||||
});
|
||||
|
||||
it('should close the menu when tabbing past items', async () => {
|
||||
await pressKeys(Key.ENTER, Key.TAB);
|
||||
await expectToExist(menuSelector, false);
|
||||
|
||||
await pressKeys(Key.TAB, Key.ENTER);
|
||||
await expectToExist(menuSelector);
|
||||
|
||||
await pressKeys(protractor.Key.chord(Key.SHIFT, Key.TAB));
|
||||
await expectToExist(menuSelector, false);
|
||||
});
|
||||
|
||||
it('should wrap back to menu when arrow keying past items', async () => {
|
||||
let down = Key.DOWN;
|
||||
await pressKeys(Key.ENTER, down, down, down);
|
||||
await expectFocusOn(page.items(0));
|
||||
|
||||
await pressKeys(Key.UP);
|
||||
await expectFocusOn(page.items(3));
|
||||
});
|
||||
|
||||
it('should focus before and after trigger when tabbing past items', async () => {
|
||||
let shiftTab = protractor.Key.chord(Key.SHIFT, Key.TAB);
|
||||
|
||||
await pressKeys(Key.ENTER, Key.TAB);
|
||||
await expectFocusOn(page.triggerTwo());
|
||||
|
||||
// navigate back to trigger
|
||||
await pressKeys(shiftTab, Key.ENTER, shiftTab);
|
||||
await expectFocusOn(page.start());
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('position - ', () => {
|
||||
|
||||
it('should default menu alignment to "after below" when not set', async () => {
|
||||
await page.trigger().click();
|
||||
|
||||
// menu.x should equal trigger.x, menu.y should equal trigger.y
|
||||
await expectAlignedWith(page.menu(), '#trigger');
|
||||
});
|
||||
|
||||
it('should align overlay end to origin end when x-position is "before"', async () => {
|
||||
await page.beforeTrigger().click();
|
||||
|
||||
const trigger = await page.beforeTrigger().getLocation();
|
||||
|
||||
// the menu's right corner must be attached to the trigger's right corner.
|
||||
// menu = 112px wide. trigger = 60px wide. 112 - 60 = 52px of menu to the left of trigger.
|
||||
// trigger.x (left corner) - 52px (menu left of trigger) = expected menu.x (left corner)
|
||||
// menu.y should equal trigger.y because only x position has changed.
|
||||
await expectLocation(page.beforeMenu(), {x: trigger.x - 52, y: trigger.y});
|
||||
});
|
||||
|
||||
it('should align overlay bottom to origin bottom when y-position is "above"', async () => {
|
||||
await page.aboveTrigger().click();
|
||||
|
||||
const trigger = await page.aboveTrigger().getLocation();
|
||||
|
||||
// the menu's bottom corner must be attached to the trigger's bottom corner.
|
||||
// menu.x should equal trigger.x because only y position has changed.
|
||||
// menu = 64px high. trigger = 20px high. 64 - 20 = 44px of menu extending up past trigger.
|
||||
// trigger.y (top corner) - 44px (menu above trigger) = expected menu.y (top corner)
|
||||
await expectLocation(page.aboveMenu(), {x: trigger.x, y: trigger.y - 44});
|
||||
});
|
||||
|
||||
it('should align menu to top left of trigger when "below" and "above"', async () => {
|
||||
await page.combinedTrigger().click();
|
||||
|
||||
const trigger = await page.combinedTrigger().getLocation();
|
||||
|
||||
// trigger.x (left corner) - 52px (menu left of trigger) = expected menu.x
|
||||
// trigger.y (top corner) - 44px (menu above trigger) = expected menu.y
|
||||
await expectLocation(page.combinedMenu(), {x: trigger.x - 52, y: trigger.y - 44});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,7 +13,7 @@ const not = ExpectedConditions.not;
|
||||
describe('menu', () => {
|
||||
const menuSelector = '.mat-menu-panel';
|
||||
const page = {
|
||||
menu: () => element(by.css('.mat-menu-panel')),
|
||||
menu: () => element(by.css(menuSelector)),
|
||||
start: () => element(by.id('start')),
|
||||
trigger: () => element(by.id('trigger')),
|
||||
triggerTwo: () => element(by.id('trigger-two')),
|
||||
@@ -23,9 +23,9 @@ describe('menu', () => {
|
||||
beforeTrigger: () => element(by.id('before-t')),
|
||||
aboveTrigger: () => element(by.id('above-t')),
|
||||
combinedTrigger: () => element(by.id('combined-t')),
|
||||
beforeMenu: () => element(by.css('.mat-menu-panel.before')),
|
||||
aboveMenu: () => element(by.css('.mat-menu-panel.above')),
|
||||
combinedMenu: () => element(by.css('.mat-menu-panel.combined')),
|
||||
beforeMenu: () => element(by.css(`${menuSelector}.before`)),
|
||||
aboveMenu: () => element(by.css(`${menuSelector}.above`)),
|
||||
combinedMenu: () => element(by.css(`${menuSelector}.combined`)),
|
||||
getResultText: () => page.textArea().getText(),
|
||||
};
|
||||
|
||||
|
||||
@@ -7,14 +7,24 @@
|
||||
*/
|
||||
|
||||
import {NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {MatMenuModule} from '@angular/material-experimental/mdc-menu';
|
||||
import {RouterModule} from '@angular/router';
|
||||
import {MatToolbarModule} from '@angular/material/toolbar';
|
||||
import {MatIconModule} from '@angular/material/icon';
|
||||
import {MatDividerModule} from '@angular/material/divider';
|
||||
import {MatButtonModule} from '@angular/material/button';
|
||||
import {MdcMenuDemo} from './mdc-menu-demo';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
MatMenuModule,
|
||||
RouterModule.forChild([{path: '', component: MdcMenuDemo}]),
|
||||
MatButtonModule,
|
||||
MatToolbarModule,
|
||||
MatIconModule,
|
||||
MatDividerModule,
|
||||
],
|
||||
declarations: [MdcMenuDemo],
|
||||
})
|
||||
|
||||
@@ -1,2 +1,189 @@
|
||||
<!-- TODO: copy in demo template from existing mat-menu demo. -->
|
||||
Not yet implemented.
|
||||
<div class="demo-menu">
|
||||
<div class="demo-menu-section">
|
||||
<p>You clicked on: {{ selected }}</p>
|
||||
|
||||
<mat-toolbar>
|
||||
<button mat-icon-button [matMenuTriggerFor]="menu" aria-label="Open basic menu">
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>
|
||||
</mat-toolbar>
|
||||
|
||||
<mat-menu #menu="matMenu">
|
||||
<button mat-menu-item *ngFor="let item of items" (click)="select(item.text)" [disabled]="item.disabled">
|
||||
{{ item.text }}
|
||||
</button>
|
||||
</mat-menu>
|
||||
</div>
|
||||
<div class="demo-menu-section">
|
||||
<p>Menu with divider</p>
|
||||
|
||||
<mat-toolbar>
|
||||
<button mat-icon-button [matMenuTriggerFor]="divider" aria-label="Open basic menu">
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>
|
||||
</mat-toolbar>
|
||||
|
||||
<mat-menu #divider="matMenu">
|
||||
<ng-container *ngFor="let item of items; last as last">
|
||||
<button mat-menu-item [disabled]="item.disabled">
|
||||
{{ item.text }}
|
||||
</button>
|
||||
<mat-divider *ngIf="!last"></mat-divider>
|
||||
</ng-container>
|
||||
</mat-menu>
|
||||
</div>
|
||||
<div class="demo-menu-section">
|
||||
<p>Nested menu</p>
|
||||
|
||||
<mat-toolbar>
|
||||
<button mat-icon-button [matMenuTriggerFor]="animals">
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>
|
||||
</mat-toolbar>
|
||||
|
||||
<mat-menu #animals="matMenu">
|
||||
<button mat-menu-item [matMenuTriggerFor]="vertebrates">Vertebrates</button>
|
||||
<button mat-menu-item [matMenuTriggerFor]="invertebrates">Invertebrates</button>
|
||||
</mat-menu>
|
||||
|
||||
<mat-menu #vertebrates="matMenu">
|
||||
<button mat-menu-item [matMenuTriggerFor]="fish">Fishes</button>
|
||||
<button mat-menu-item [matMenuTriggerFor]="amphibians">Amphibians</button>
|
||||
<button mat-menu-item [matMenuTriggerFor]="reptiles">Reptiles</button>
|
||||
<button mat-menu-item>Birds</button>
|
||||
<button mat-menu-item>Mammals</button>
|
||||
</mat-menu>
|
||||
|
||||
<mat-menu #invertebrates="matMenu">
|
||||
<button mat-menu-item>Insects</button>
|
||||
<button mat-menu-item>Molluscs</button>
|
||||
<button mat-menu-item>Crustaceans</button>
|
||||
<button mat-menu-item>Corals</button>
|
||||
<button mat-menu-item>Arachnids</button>
|
||||
<button mat-menu-item>Velvet worms</button>
|
||||
<button mat-menu-item>Horseshoe crabs</button>
|
||||
</mat-menu>
|
||||
|
||||
<mat-menu #fish="matMenu">
|
||||
<button mat-menu-item>Baikal oilfish</button>
|
||||
<button mat-menu-item>Bala shark</button>
|
||||
<button mat-menu-item>Ballan wrasse</button>
|
||||
<button mat-menu-item>Bamboo shark</button>
|
||||
<button mat-menu-item>Banded killifish</button>
|
||||
</mat-menu>
|
||||
|
||||
<mat-menu #amphibians="matMenu">
|
||||
<button mat-menu-item>Sonoran desert toad</button>
|
||||
<button mat-menu-item>Western toad</button>
|
||||
<button mat-menu-item>Arroyo toad</button>
|
||||
<button mat-menu-item>Yosemite toad</button>
|
||||
</mat-menu>
|
||||
|
||||
<mat-menu #reptiles="matMenu">
|
||||
<button mat-menu-item>Banded Day Gecko</button>
|
||||
<button mat-menu-item>Banded Gila Monster</button>
|
||||
<button mat-menu-item>Black Tree Monitor</button>
|
||||
<button mat-menu-item>Blue Spiny Lizard</button>
|
||||
</mat-menu>
|
||||
</div>
|
||||
<div class="demo-menu-section">
|
||||
<p>Clicking these will navigate:</p>
|
||||
<mat-toolbar>
|
||||
<button mat-icon-button [matMenuTriggerFor]="anchorMenu" aria-label="Open anchor menu">
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>
|
||||
</mat-toolbar>
|
||||
|
||||
<mat-menu #anchorMenu="matMenu">
|
||||
<a mat-menu-item *ngFor="let item of items" href="http://www.google.com" [disabled]="item.disabled">
|
||||
{{ item.text }}
|
||||
</a>
|
||||
</mat-menu>
|
||||
</div>
|
||||
<div class="demo-menu-section">
|
||||
<p>
|
||||
Position x: before
|
||||
</p>
|
||||
<mat-toolbar class="demo-end-icon">
|
||||
<button mat-icon-button [matMenuTriggerFor]="posXMenu" aria-label="Open x-positioned menu">
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>
|
||||
</mat-toolbar>
|
||||
|
||||
<mat-menu xPosition="before" #posXMenu="matMenu">
|
||||
<button mat-menu-item *ngFor="let item of iconItems" [disabled]="item.disabled">
|
||||
<mat-icon>{{ item.icon }}</mat-icon>
|
||||
{{ item.text }}
|
||||
</button>
|
||||
</mat-menu>
|
||||
</div>
|
||||
<div class="demo-menu-section">
|
||||
<p>
|
||||
Position y: above
|
||||
</p>
|
||||
<mat-toolbar>
|
||||
<button mat-icon-button [matMenuTriggerFor]="posYMenu" aria-label="Open y-positioned menu">
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>
|
||||
</mat-toolbar>
|
||||
|
||||
<mat-menu yPosition="above" #posYMenu="matMenu">
|
||||
<button mat-menu-item *ngFor="let item of items" [disabled]="item.disabled">
|
||||
{{ item.text }}
|
||||
</button>
|
||||
</mat-menu>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="demo-menu">
|
||||
<div class="demo-menu-section">
|
||||
<p>overlapTrigger: true</p>
|
||||
|
||||
<mat-toolbar>
|
||||
<button mat-icon-button [mat-menu-trigger-for]="menuOverlay">
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>
|
||||
</mat-toolbar>
|
||||
|
||||
<mat-menu [overlapTrigger]="true" #menuOverlay="matMenu">
|
||||
<button mat-menu-item *ngFor="let item of items" [disabled]="item.disabled">
|
||||
{{ item.text }}
|
||||
</button>
|
||||
</mat-menu>
|
||||
</div>
|
||||
<div class="demo-menu-section">
|
||||
<p>
|
||||
Position x: before, overlapTrigger: true
|
||||
</p>
|
||||
<mat-toolbar class="demo-end-icon">
|
||||
<button mat-icon-button [mat-menu-trigger-for]="posXMenuOverlay">
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>
|
||||
</mat-toolbar>
|
||||
|
||||
<mat-menu xPosition="before" [overlapTrigger]="true" #posXMenuOverlay="matMenu">
|
||||
<button mat-menu-item *ngFor="let item of iconItems" [disabled]="item.disabled">
|
||||
<mat-icon>{{ item.icon }}</mat-icon>
|
||||
{{ item.text }}
|
||||
</button>
|
||||
</mat-menu>
|
||||
</div>
|
||||
<div class="demo-menu-section">
|
||||
<p>
|
||||
Position y: above, overlapTrigger: true
|
||||
</p>
|
||||
<mat-toolbar>
|
||||
<button mat-icon-button [mat-menu-trigger-for]="posYMenuOverlay">
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>
|
||||
</mat-toolbar>
|
||||
|
||||
<mat-menu yPosition="above" [overlapTrigger]="true" #posYMenuOverlay="matMenu">
|
||||
<button mat-menu-item *ngFor="let item of items" [disabled]="item.disabled">
|
||||
{{ item.text }}
|
||||
</button>
|
||||
</mat-menu>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="height: 500px">This div is for testing scrolled menus.</div>
|
||||
|
||||
@@ -1 +1,13 @@
|
||||
// TODO: copy in demo styles from existing mat-menu demo.
|
||||
.demo-menu {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
|
||||
.demo-menu-section {
|
||||
width: 300px;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.demo-end-icon {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,4 +15,19 @@ import {Component} from '@angular/core';
|
||||
styleUrls: ['mdc-menu-demo.css'],
|
||||
})
|
||||
export class MdcMenuDemo {
|
||||
selected = '';
|
||||
items = [
|
||||
{text: 'Refresh'},
|
||||
{text: 'Settings'},
|
||||
{text: 'Help', disabled: true},
|
||||
{text: 'Sign Out'}
|
||||
];
|
||||
|
||||
iconItems = [
|
||||
{text: 'Redial', icon: 'dialpad'},
|
||||
{text: 'Check voicemail', icon: 'voicemail', disabled: true},
|
||||
{text: 'Disable alerts', icon: 'notifications_off'}
|
||||
];
|
||||
|
||||
select(text: string) { this.selected = text; }
|
||||
}
|
||||
|
||||
@@ -1 +1,48 @@
|
||||
<!-- TODO: copy implementation from existing mat-menu e2e page. -->
|
||||
<div>
|
||||
<div style="float:left">
|
||||
<div id="text">{{ selected }}</div>
|
||||
<button id="start">START</button>
|
||||
<button [matMenuTriggerFor]="menu" id="trigger">TRIGGER</button>
|
||||
<button [matMenuTriggerFor]="menu" id="trigger-two">TRIGGER 2</button>
|
||||
|
||||
<mat-menu #menu="matMenu" yPosition="below" class="custom" overlapTrigger>
|
||||
<button mat-menu-item (click)="selected='one'">One</button>
|
||||
<button mat-menu-item (click)="selected='two'">Two</button>
|
||||
<button mat-menu-item (click)="selected='three'" disabled>Three</button>
|
||||
<button mat-menu-item>Four</button>
|
||||
</mat-menu>
|
||||
|
||||
<button [matMenuTriggerFor]="beforeMenu" id="before-t">
|
||||
BEFORE
|
||||
</button>
|
||||
<mat-menu
|
||||
xPosition="before"
|
||||
yPosition="below"
|
||||
class="before"
|
||||
#beforeMenu="matMenu"
|
||||
overlapTrigger>
|
||||
<button mat-menu-item>Item</button>
|
||||
</mat-menu>
|
||||
|
||||
<div class="bottom-row">
|
||||
<button [matMenuTriggerFor]="aboveMenu" id="above-t">ABOVE</button>
|
||||
<mat-menu yPosition="above" class="above" #aboveMenu="matMenu" overlapTrigger>
|
||||
<button mat-menu-item>Item</button>
|
||||
</mat-menu>
|
||||
|
||||
<button [matMenuTriggerFor]="combined" id="combined-t">
|
||||
BOTH
|
||||
</button>
|
||||
<mat-menu
|
||||
xPosition="before"
|
||||
yPosition="above"
|
||||
class="combined"
|
||||
#combined="matMenu"
|
||||
overlapTrigger>
|
||||
<button mat-menu-item>Item</button>
|
||||
</mat-menu>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -12,7 +12,17 @@ import {Component} from '@angular/core';
|
||||
moduleId: module.id,
|
||||
selector: 'mdc-menu-e2e',
|
||||
templateUrl: 'mdc-menu-e2e.html',
|
||||
styles: [`
|
||||
#before-t, #above-t, #combined-t {
|
||||
width: 60px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.bottom-row {
|
||||
margin-top: 5px;
|
||||
}
|
||||
`]
|
||||
})
|
||||
export class MdcMenuE2e {
|
||||
// TODO: copy implementation from existing mat-menu e2e page.
|
||||
selected: string = '';
|
||||
}
|
||||
|
||||
@@ -10,6 +10,9 @@ ng_module(
|
||||
module_name = "@angular/material-experimental/mdc-menu",
|
||||
assets = [":menu_scss"] + glob(["**/*.html"]),
|
||||
deps = [
|
||||
"@npm//@angular/animations",
|
||||
"@npm//@angular/common",
|
||||
"@npm//@angular/core",
|
||||
"@npm//material-components-web",
|
||||
] + CDK_TARGETS + MATERIAL_TARGETS,
|
||||
)
|
||||
@@ -20,10 +23,19 @@ sass_library(
|
||||
deps = [
|
||||
"//src/material/core:core_scss_lib",
|
||||
"//src/material-experimental/mdc-helpers:mdc_helpers_scss_lib",
|
||||
"//src/material-experimental/mdc-helpers:mdc_scss_deps_lib",
|
||||
],
|
||||
)
|
||||
|
||||
sass_binary(
|
||||
name = "menu_scss",
|
||||
src = "menu.scss",
|
||||
include_paths = [
|
||||
"external/npm/node_modules",
|
||||
],
|
||||
deps = [
|
||||
":menu_scss_lib",
|
||||
"//src/material/core:all_themes",
|
||||
"//src/material-experimental/mdc-helpers:mdc_scss_deps_lib",
|
||||
]
|
||||
)
|
||||
|
||||
@@ -1 +1,95 @@
|
||||
This is a placeholder for the MDC-based implementation of menu.
|
||||
This is prototype of an alternate version of `<mat-menu>` built on top of
|
||||
[MDC Web](https://github.com/material-components/material-components-web). It demonstrates how
|
||||
Angular Material could use MDC Web under the hood while still exposing the same API Angular users as
|
||||
the existing `<mat-menu>`. This component is experimental and should not be used in production.
|
||||
|
||||
## How to use
|
||||
Assuming your application is already up and running using Angular Material, you can add this
|
||||
component by following these steps:
|
||||
|
||||
1. Install Angular Material Experimental & MDC WEB:
|
||||
|
||||
```bash
|
||||
npm i material-components-web @angular/material-experimental
|
||||
```
|
||||
|
||||
2. In your `angular.json`, make sure `node_modules/` is listed as a Sass include path. This is
|
||||
needed for the Sass compiler to be able to find the MDC Web Sass files.
|
||||
|
||||
```json
|
||||
...
|
||||
"styles": [
|
||||
"src/styles.scss"
|
||||
],
|
||||
"stylePreprocessorOptions": {
|
||||
"includePaths": [
|
||||
"node_modules/"
|
||||
]
|
||||
},
|
||||
...
|
||||
```
|
||||
|
||||
3. Import the experimental `MatMenuModule` and add it to the module that declares your
|
||||
component:
|
||||
|
||||
```ts
|
||||
import {MatMenuModule} from '@angular/material-experimental/mdc-menu';
|
||||
|
||||
@NgModule({
|
||||
declarations: [MyComponent],
|
||||
imports: [MatMenuModule],
|
||||
})
|
||||
export class MyModule {}
|
||||
```
|
||||
|
||||
4. Add use `<mat-menu>` in your component's template, just like you would the normal
|
||||
`<mat-menu>`:
|
||||
|
||||
```html
|
||||
<button [matMenuTriggerFor]="menu">Menu</button>
|
||||
<mat-menu #menu="matMenu">
|
||||
<button mat-menu-item>Item 1</button>
|
||||
<button mat-menu-item>Item 2</button>
|
||||
</mat-menu>
|
||||
```
|
||||
|
||||
5. Add the theme and typography mixins to your Sass. (There is currently no pre-built CSS option for
|
||||
the experimental `<mat-menu>`):
|
||||
|
||||
```scss
|
||||
@import '~@angular/material/theming';
|
||||
@import '~@angular/material-experimental/mdc-menu';
|
||||
|
||||
$my-primary: mat-palette($mat-indigo);
|
||||
$my-accent: mat-palette($mat-pink, A200, A100, A400);
|
||||
$my-theme: mat-light-theme($my-primary, $my-accent);
|
||||
|
||||
@include mat-menu-theme-mdc($my-theme);
|
||||
@include mat-menu-typography-mdc();
|
||||
```
|
||||
|
||||
## API differences
|
||||
The experimental menu API closely matches the
|
||||
[API of the standard menu](https://material.angular.io/components/menu/api).
|
||||
`@angular/material-experimental/mdc-menu` exports symbols with the same name and public interface
|
||||
as all of the symbols found under `@angular/material/menu`, except for the following
|
||||
differences:
|
||||
|
||||
* The experimental `MatMenu` does not support increasing the elevation of a sub-menu, based on its depth.
|
||||
|
||||
## Replacing the standard menu in an existing app
|
||||
Because the experimental API mirrors the API for the standard menu, it can easily be swapped in
|
||||
by just changing the import paths. There is currently no schematic for this, but you can run the
|
||||
following string replace across your TypeScript files:
|
||||
|
||||
```bash
|
||||
grep -lr --include="*.ts" --exclude-dir="node_modules" \
|
||||
--exclude="*.d.ts" "['\"]@angular/material/menu['\"]" | xargs sed -i \
|
||||
"s/['\"]@angular\/material\/menu['\"]/'@angular\/material-experimental\/mdc-menu'/g"
|
||||
```
|
||||
|
||||
CSS styles and tests that depend on implementation details of mat-menu (such as getting elements
|
||||
from the template by class name) will need to be manually updated.
|
||||
|
||||
There are some small visual differences between this menu and the standard mat-menu. This
|
||||
menu has a different font size and elevation `box-shadow`.
|
||||
|
||||
@@ -1,18 +1,60 @@
|
||||
@import '@material/menu-surface/mixins';
|
||||
@import '@material/menu-surface/variables';
|
||||
@import '@material/list/mixins';
|
||||
@import '@material/list/variables';
|
||||
@import '@material/theme/functions';
|
||||
@import '../mdc-helpers/mdc-helpers';
|
||||
|
||||
@mixin mat-menu-theme-mdc($theme) {
|
||||
@include mat-using-mdc-theme($theme) {
|
||||
// TODO: MDC theme styles here.
|
||||
@include mdc-menu-surface-core-styles($mat-theme-styles-query);
|
||||
@include mdc-list-without-ripple($mat-theme-styles-query);
|
||||
|
||||
// MDC doesn't appear to have disabled styling for menu
|
||||
// items so we have to grey them out ourselves.
|
||||
.mat-mdc-menu-item[disabled] {
|
||||
&, &::after {
|
||||
@include mdc-theme-prop(color, text-disabled-on-background);
|
||||
}
|
||||
}
|
||||
|
||||
// Since we're creating `mat-icon` and the submenu trigger
|
||||
// chevron ourselves, we have to handle the color as well.
|
||||
.mat-mdc-menu-item .mat-icon-no-color,
|
||||
.mat-mdc-menu-item-submenu-trigger::after {
|
||||
@include mdc-theme-prop(color, text-icon-on-background);
|
||||
}
|
||||
|
||||
// MDC's hover and focus styles are tied to their ripples which we aren't using.
|
||||
.mat-mdc-menu-item:hover,
|
||||
.mat-mdc-menu-item.cdk-program-focused,
|
||||
.mat-mdc-menu-item.cdk-keyboard-focused,
|
||||
.mat-mdc-menu-item-highlighted {
|
||||
&:not([disabled]) {
|
||||
$color: $mdc-theme-on-surface;
|
||||
background: rgba($color, mdc-states-opacity($color, hover));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@mixin mat-menu-typography-mdc($config) {
|
||||
@if config {
|
||||
@include mat-using-mdc-typography($config) {
|
||||
// TODO: MDC typography styles here.
|
||||
@include mdc-menu-surface-core-styles($mat-typography-styles-query);
|
||||
|
||||
.mat-mdc-menu-content {
|
||||
// Note that we include this private mixin, because the public
|
||||
// one adds a bunch of styles that we aren't using for the menu.
|
||||
@include mdc-list-base_($mat-typography-styles-query);
|
||||
}
|
||||
}
|
||||
}
|
||||
@else {
|
||||
// TODO: MDC typography styles here.
|
||||
@include mdc-menu-surface-core-styles($mat-typography-styles-query);
|
||||
|
||||
.mat-mdc-menu-content {
|
||||
@include mdc-list-base_($mat-typography-styles-query);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<ng-content></ng-content>
|
||||
<div class="mat-mdc-menu-ripple" matRipple
|
||||
[matRippleDisabled]="disableRipple || disabled"
|
||||
[matRippleTrigger]="_getHostElement()">
|
||||
</div>
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Component, ChangeDetectionStrategy, ViewEncapsulation} from '@angular/core';
|
||||
import {MatMenuItem as BaseMatMenuItem} from '@angular/material/menu';
|
||||
|
||||
/**
|
||||
* This directive is intended to be used inside an mat-menu tag.
|
||||
* It exists mostly to set the role attribute.
|
||||
*/
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: '[mat-menu-item]',
|
||||
exportAs: 'matMenuItem',
|
||||
inputs: ['disabled', 'disableRipple'],
|
||||
host: {
|
||||
'[attr.role]': 'role',
|
||||
'class': 'mat-mdc-menu-item',
|
||||
'[class.mat-mdc-menu-item-highlighted]': '_highlighted',
|
||||
'[class.mat-mdc-menu-item-submenu-trigger]': '_triggersSubmenu',
|
||||
'[attr.tabindex]': '_getTabIndex()',
|
||||
'[attr.aria-disabled]': 'disabled',
|
||||
'[attr.disabled]': 'disabled || null',
|
||||
},
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
templateUrl: 'menu-item.html',
|
||||
providers: [
|
||||
{provide: BaseMatMenuItem, useExisting: MatMenuItem},
|
||||
]
|
||||
})
|
||||
export class MatMenuItem extends BaseMatMenuItem {}
|
||||
@@ -1 +1,16 @@
|
||||
<!-- TODO: MDC template here. -->
|
||||
<ng-template>
|
||||
<div
|
||||
class="mat-mdc-menu-panel mdc-menu-surface mdc-menu-surface--open"
|
||||
[ngClass]="_classList"
|
||||
(keydown)="_handleKeydown($event)"
|
||||
(click)="closed.emit('click')"
|
||||
[@transformMenu]="_panelAnimationState"
|
||||
(@transformMenu.start)="_onAnimationStart($event)"
|
||||
(@transformMenu.done)="_onAnimationDone($event)"
|
||||
tabindex="-1"
|
||||
role="menu">
|
||||
<div class="mat-mdc-menu-content mdc-list">
|
||||
<ng-content></ng-content>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
@@ -1 +1,68 @@
|
||||
// TODO: MDC core styles here.
|
||||
@import '@material/menu-surface/mixins';
|
||||
@import '@material/list/mixins';
|
||||
@import '@material/list/variables';
|
||||
@import '../../material/core/style/menu-common';
|
||||
@import '../../material/core/style/button-common';
|
||||
|
||||
@include mdc-menu-surface-core-styles($query: structure);
|
||||
|
||||
.mat-mdc-menu-content {
|
||||
// Note that we include this private mixin, because the public
|
||||
// one adds a bunch of styles that we aren't using for the menu.
|
||||
@include mdc-list-base_($query: structure);
|
||||
}
|
||||
|
||||
.mat-mdc-menu-panel {
|
||||
// Include Material's base menu panel styling which contain the `min-width`, `max-width` and some
|
||||
// styling to make scrolling smoother. MDC doesn't include the `min-width` and `max-width`, even
|
||||
// though they're explicitly defined in spec.
|
||||
@include mat-menu-base;
|
||||
|
||||
// The CDK positioning uses flexbox to anchor the element, whereas MDC uses `position: absolute`.
|
||||
position: static;
|
||||
}
|
||||
|
||||
.mat-mdc-menu-item {
|
||||
// Note that we include this private mixin, because the public
|
||||
// one adds a bunch of styles that we aren't using for the menu.
|
||||
@include mdc-list-item-base_;
|
||||
|
||||
// MDC's menu items are `<li>` nodes which don't need resets, however ours
|
||||
// can be anything, including buttons, so we need to do the reset ourselves.
|
||||
@include mat-button-reset;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
box-sizing: border-box;
|
||||
color: inherit;
|
||||
font-size: inherit;
|
||||
background: none;
|
||||
|
||||
&[disabled] {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.mat-icon {
|
||||
margin-right: $mdc-list-side-padding;
|
||||
}
|
||||
|
||||
[dir='rtl'] & {
|
||||
text-align: right;
|
||||
|
||||
.mat-icon {
|
||||
margin-right: 0;
|
||||
margin-left: $mdc-list-side-padding;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Renders out a chevron on menu items that trigger a sub-menu.
|
||||
.mat-mdc-menu-item-submenu-trigger {
|
||||
@include mat-menu-item-submenu-trigger($mdc-list-side-padding);
|
||||
}
|
||||
|
||||
// Increase specificity because ripple styles are part of the `mat-core` mixin and can
|
||||
// potentially overwrite the absolute position of the container.
|
||||
.mat-mdc-menu-item .mat-mdc-menu-ripple {
|
||||
@include mat-menu-item-ripple;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,20 +6,52 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ChangeDetectionStrategy, Component, ViewEncapsulation} from '@angular/core';
|
||||
import {ChangeDetectionStrategy, Component, ViewEncapsulation, Provider} from '@angular/core';
|
||||
import {Overlay, ScrollStrategy} from '@angular/cdk/overlay';
|
||||
import {
|
||||
MatMenu as BaseMatMenu,
|
||||
MAT_MENU_PANEL,
|
||||
matMenuAnimations,
|
||||
MAT_MENU_SCROLL_STRATEGY,
|
||||
} from '@angular/material/menu';
|
||||
|
||||
/** @docs-private */
|
||||
export function MAT_MENU_SCROLL_STRATEGY_FACTORY(overlay: Overlay): () => ScrollStrategy {
|
||||
return () => overlay.scrollStrategies.reposition();
|
||||
}
|
||||
|
||||
/** @docs-private */
|
||||
export const MAT_MENU_SCROLL_STRATEGY_FACTORY_PROVIDER: Provider = {
|
||||
provide: MAT_MENU_SCROLL_STRATEGY,
|
||||
deps: [Overlay],
|
||||
useFactory: MAT_MENU_SCROLL_STRATEGY_FACTORY,
|
||||
};
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'mat-menu',
|
||||
templateUrl: 'menu.html',
|
||||
styleUrls: ['menu.css'],
|
||||
host: {
|
||||
'class': 'mat-mdc-menu',
|
||||
},
|
||||
exportAs: 'matMenu',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
exportAs: 'matMenu',
|
||||
animations: [
|
||||
matMenuAnimations.transformMenu,
|
||||
matMenuAnimations.fadeInItems
|
||||
],
|
||||
providers: [
|
||||
{provide: MAT_MENU_PANEL, useExisting: MatMenu},
|
||||
{provide: BaseMatMenu, useExisting: MatMenu},
|
||||
]
|
||||
})
|
||||
export class MatMenu {
|
||||
// TODO: set up MDC foundation class.
|
||||
export class MatMenu extends BaseMatMenu {
|
||||
setElevation(_depth: number) {
|
||||
// TODO(crisbeto): MDC's styles come with elevation already and we haven't mapped our mixins
|
||||
// to theirs. Disable the elevation stacking for now until everything has been mapped.
|
||||
// The following unit tests should be re-enabled:
|
||||
// - should not remove mat-elevation class from overlay when panelClass is changed
|
||||
// - should increase the sub-menu elevation based on its depth
|
||||
// - should update the elevation when the same menu is opened at a different depth
|
||||
// - should not increase the elevation if the user specified a custom one
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,13 +8,22 @@
|
||||
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {NgModule} from '@angular/core';
|
||||
import {MatCommonModule} from '@angular/material/core';
|
||||
import {MatMenu} from './menu';
|
||||
import {MatCommonModule, MatRippleModule} from '@angular/material/core';
|
||||
import {OverlayModule} from '@angular/cdk/overlay';
|
||||
import {_MatMenuDirectivesModule} from '@angular/material/menu';
|
||||
import {MatMenu, MAT_MENU_SCROLL_STRATEGY_FACTORY_PROVIDER} from './menu';
|
||||
import {MatMenuItem} from './menu-item';
|
||||
|
||||
@NgModule({
|
||||
imports: [MatCommonModule, CommonModule],
|
||||
exports: [MatMenu, MatCommonModule],
|
||||
declarations: [MatMenu],
|
||||
imports: [
|
||||
CommonModule,
|
||||
MatRippleModule,
|
||||
MatCommonModule,
|
||||
OverlayModule,
|
||||
_MatMenuDirectivesModule
|
||||
],
|
||||
exports: [MatMenu, MatMenuItem, _MatMenuDirectivesModule],
|
||||
declarations: [MatMenu, MatMenuItem],
|
||||
providers: [MAT_MENU_SCROLL_STRATEGY_FACTORY_PROVIDER]
|
||||
})
|
||||
export class MatMenuModule {
|
||||
}
|
||||
export class MatMenuModule {}
|
||||
|
||||
@@ -6,5 +6,22 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
export * from './menu';
|
||||
export {MatMenu} from './menu';
|
||||
export {MatMenuItem} from './menu-item';
|
||||
export * from './module';
|
||||
|
||||
export {
|
||||
_MatMenuDirectivesModule,
|
||||
fadeInItems,
|
||||
MAT_MENU_DEFAULT_OPTIONS,
|
||||
MAT_MENU_PANEL,
|
||||
MAT_MENU_SCROLL_STRATEGY,
|
||||
matMenuAnimations,
|
||||
MatMenuContent,
|
||||
MatMenuDefaultOptions,
|
||||
MatMenuPanel,
|
||||
MatMenuTrigger,
|
||||
MenuPositionX,
|
||||
MenuPositionY,
|
||||
transformMenu,
|
||||
} from '@angular/material/menu';
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
@import './variables';
|
||||
@import './list-common';
|
||||
@import './layout-common';
|
||||
@import './vendor-prefixes';
|
||||
|
||||
/** The mixins below are shared between mat-menu and mat-select */
|
||||
@@ -57,3 +58,46 @@ $mat-menu-icon-margin: 16px !default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@mixin mat-menu-item-submenu-trigger($side-padding, $triangle-height: 10px) {
|
||||
// Increase the side padding to prevent the indicator from overlapping the text.
|
||||
padding-right: $side-padding * 2;
|
||||
|
||||
// Renders a triangle to indicate that the menu item will open a sub-menu.
|
||||
&::after {
|
||||
$size: $triangle-height / 2;
|
||||
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
border-width: $size 0 $size $size;
|
||||
border-color: transparent transparent transparent currentColor;
|
||||
content: '';
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: $side-padding;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
[dir='rtl'] & {
|
||||
padding-right: $side-padding;
|
||||
padding-left: $side-padding * 2;
|
||||
|
||||
&::after {
|
||||
right: auto;
|
||||
left: $side-padding;
|
||||
transform: rotateY(180deg) translateY(-50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@mixin mat-menu-item-ripple() {
|
||||
@include mat-fill;
|
||||
|
||||
// Prevent any pointer events on the ripple container for a menu item. The ripple container is
|
||||
// not the trigger element for the ripples and can be therefore disabled like that. Disabling
|
||||
// the pointer events ensures that there is no `click` event that can bubble up and cause
|
||||
// the menu panel to close.
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@@ -41,7 +41,9 @@ export const matMenuAnimations: {
|
||||
transform: 'scale(0.8)'
|
||||
})),
|
||||
transition('void => enter', group([
|
||||
query('.mat-menu-content', animate('100ms linear', style({opacity: 1}))),
|
||||
query('.mat-menu-content, .mat-mdc-menu-content', animate('100ms linear', style({
|
||||
opacity: 1
|
||||
}))),
|
||||
animate('120ms cubic-bezier(0, 0, 0.2, 1)', style({transform: 'scale(1)'})),
|
||||
])),
|
||||
transition('* => void', animate('100ms 25ms linear', style({opacity: 0})))
|
||||
|
||||
@@ -11,13 +11,24 @@ import {CommonModule} from '@angular/common';
|
||||
import {NgModule} from '@angular/core';
|
||||
import {MatCommonModule, MatRippleModule} from '@angular/material/core';
|
||||
import {MatMenuContent} from './menu-content';
|
||||
import {MatMenu} from './menu';
|
||||
import {_MatMenu} from './menu';
|
||||
import {MatMenuItem} from './menu-item';
|
||||
import {
|
||||
MatMenuTrigger,
|
||||
MAT_MENU_SCROLL_STRATEGY_FACTORY_PROVIDER,
|
||||
} from './menu-trigger';
|
||||
|
||||
/**
|
||||
* Used by both the current `MatMenuModule` and the MDC `MatMenuModule`
|
||||
* to declare the menu-related directives.
|
||||
*/
|
||||
@NgModule({
|
||||
exports: [MatMenuTrigger, MatMenuContent, MatCommonModule],
|
||||
declarations: [MatMenuTrigger, MatMenuContent],
|
||||
providers: [MAT_MENU_SCROLL_STRATEGY_FACTORY_PROVIDER]
|
||||
})
|
||||
// tslint:disable-next-line:class-name
|
||||
export class _MatMenuDirectivesModule {}
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -25,9 +36,10 @@ import {
|
||||
MatCommonModule,
|
||||
MatRippleModule,
|
||||
OverlayModule,
|
||||
_MatMenuDirectivesModule,
|
||||
],
|
||||
exports: [MatMenu, MatMenuItem, MatMenuTrigger, MatMenuContent, MatCommonModule],
|
||||
declarations: [MatMenu, MatMenuItem, MatMenuTrigger, MatMenuContent],
|
||||
exports: [_MatMenu, MatMenuItem, _MatMenuDirectivesModule],
|
||||
declarations: [_MatMenu, MatMenuItem],
|
||||
providers: [MAT_MENU_SCROLL_STRATEGY_FACTORY_PROVIDER]
|
||||
})
|
||||
export class MatMenuModule {}
|
||||
|
||||
@@ -407,7 +407,7 @@ export class MatMenuTrigger implements AfterContentInit, OnDestroy {
|
||||
positionStrategy: this._overlay.position()
|
||||
.flexibleConnectedTo(this._element)
|
||||
.withLockedPosition()
|
||||
.withTransformOriginOn('.mat-menu-panel'),
|
||||
.withTransformOriginOn('.mat-menu-panel, .mat-mdc-menu-panel'),
|
||||
backdropClass: this.menu.backdropClass || 'cdk-overlay-transparent-backdrop',
|
||||
scrollStrategy: this._scrollStrategy(),
|
||||
direction: this._dir
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// TODO(kara): update vars for desktop when MD team responds
|
||||
@import '../core/style/button-common';
|
||||
@import '../core/style/layout-common';
|
||||
@import '../core/style/menu-common';
|
||||
@import '../../cdk/a11y/a11y';
|
||||
|
||||
@@ -51,36 +50,7 @@ $mat-menu-submenu-indicator-size: 10px !default;
|
||||
}
|
||||
|
||||
.mat-menu-item-submenu-trigger {
|
||||
// Increase the side padding to prevent the indicator from overlapping the text.
|
||||
padding-right: $mat-menu-side-padding * 2;
|
||||
|
||||
// Renders a triangle to indicate that the menu item will open a sub-menu.
|
||||
&::after {
|
||||
$size: $mat-menu-submenu-indicator-size / 2;
|
||||
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
border-width: $size 0 $size $size;
|
||||
border-color: transparent transparent transparent currentColor;
|
||||
content: '';
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: $mat-menu-side-padding;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
[dir='rtl'] & {
|
||||
padding-right: $mat-menu-side-padding;
|
||||
padding-left: $mat-menu-side-padding * 2;
|
||||
|
||||
&::after {
|
||||
right: auto;
|
||||
left: $mat-menu-side-padding;
|
||||
transform: rotateY(180deg) translateY(-50%);
|
||||
}
|
||||
}
|
||||
@include mat-menu-item-submenu-trigger($mat-menu-side-padding);
|
||||
}
|
||||
|
||||
button.mat-menu-item {
|
||||
@@ -90,11 +60,5 @@ button.mat-menu-item {
|
||||
// Increase specificity because ripple styles are part of the `mat-core` mixin and can
|
||||
// potentially overwrite the absolute position of the container.
|
||||
.mat-menu-item .mat-menu-ripple {
|
||||
@include mat-fill;
|
||||
|
||||
// Prevent any pointer events on the ripple container for a menu item. The ripple container is
|
||||
// not the trigger element for the ripples and can be therefore disabled like that. Disabling
|
||||
// the pointer events ensures that there is no `click` event that can bubble up and cause
|
||||
// the menu panel to close.
|
||||
pointer-events: none;
|
||||
@include mat-menu-item-ripple;
|
||||
}
|
||||
|
||||
+38
-18
@@ -89,24 +89,10 @@ export function MAT_MENU_DEFAULT_OPTIONS_FACTORY(): MatMenuDefaultOptions {
|
||||
*/
|
||||
const MAT_MENU_BASE_ELEVATION = 4;
|
||||
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'mat-menu',
|
||||
templateUrl: 'menu.html',
|
||||
styleUrls: ['menu.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
exportAs: 'matMenu',
|
||||
animations: [
|
||||
matMenuAnimations.transformMenu,
|
||||
matMenuAnimations.fadeInItems
|
||||
],
|
||||
providers: [
|
||||
{provide: MAT_MENU_PANEL, useExisting: MatMenu}
|
||||
]
|
||||
})
|
||||
export class MatMenu implements AfterContentInit, MatMenuPanel<MatMenuItem>, OnInit, OnDestroy {
|
||||
/** Base class with all of the `MatMenu` functionality. */
|
||||
// tslint:disable-next-line:class-name
|
||||
export class _MatMenuBase implements AfterContentInit, MatMenuPanel<MatMenuItem>, OnInit,
|
||||
OnDestroy {
|
||||
private _keyManager: FocusKeyManager<MatMenuItem>;
|
||||
private _xPosition: MenuPositionX = this._defaultOptions.xPosition;
|
||||
private _yPosition: MenuPositionY = this._defaultOptions.yPosition;
|
||||
@@ -426,3 +412,37 @@ export class MatMenu implements AfterContentInit, MatMenuPanel<MatMenuItem>, OnI
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class MatMenu extends _MatMenuBase {}
|
||||
|
||||
// Note on the weird inheritance setup: we need three classes, because the MDC-based menu has to
|
||||
// extend `MatMenu`, however keeping a reference to it will cause the inlined template and styles
|
||||
// to be retained as well. The MDC menu also has to provide itself as a `MatMenu` in order for
|
||||
// queries and DI to work correctly, while still not referencing the actual menu class.
|
||||
// Class responsibility is split up as follows:
|
||||
// * _MatMenuBase - provides all the functionality without any of the Angular metadata.
|
||||
// * MatMenu - keeps the same name symbol name as the current menu and
|
||||
// is used as a provider for DI and query purposes.
|
||||
// * _MatMenu - the actual menu component implementation with the Angular metadata that should
|
||||
// be tree shaken away for MDC.
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'mat-menu',
|
||||
templateUrl: 'menu.html',
|
||||
styleUrls: ['menu.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
exportAs: 'matMenu',
|
||||
animations: [
|
||||
matMenuAnimations.transformMenu,
|
||||
matMenuAnimations.fadeInItems
|
||||
],
|
||||
providers: [
|
||||
{provide: MAT_MENU_PANEL, useExisting: MatMenu},
|
||||
{provide: MatMenu, useExisting: _MatMenu}
|
||||
]
|
||||
})
|
||||
// tslint:disable-next-line:class-name
|
||||
export class _MatMenu extends MatMenu {
|
||||
}
|
||||
|
||||
@@ -6,10 +6,16 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
export {MatMenu, MatMenuDefaultOptions, MAT_MENU_DEFAULT_OPTIONS} from './menu';
|
||||
export {
|
||||
MatMenu,
|
||||
MatMenuDefaultOptions,
|
||||
MAT_MENU_DEFAULT_OPTIONS,
|
||||
_MatMenu,
|
||||
_MatMenuBase,
|
||||
} from './menu';
|
||||
export {MatMenuItem} from './menu-item';
|
||||
export {MatMenuTrigger, MAT_MENU_SCROLL_STRATEGY} from './menu-trigger';
|
||||
export {MatMenuPanel} from './menu-panel';
|
||||
export {MatMenuPanel, MAT_MENU_PANEL} from './menu-panel';
|
||||
export * from './menu-module';
|
||||
export * from './menu-animations';
|
||||
export * from './menu-content';
|
||||
|
||||
@@ -17,9 +17,12 @@ Not yet implemented.
|
||||
<mat-checkbox>with a label</mat-checkbox>
|
||||
|
||||
<h2>MDC menu</h2>
|
||||
|
||||
<!-- TODO: Copy kitchen sink examples for mat-menu here -->
|
||||
Not yet implemented.
|
||||
<button mat-button [matMenuTriggerFor]="menu">Open</button>
|
||||
<mat-menu #menu="matMenu">
|
||||
<button mat-menu-item>Mercy</button>
|
||||
<button mat-menu-item>Lucio</button>
|
||||
<button mat-menu-item disabled>Sombra</button>
|
||||
</mat-menu>
|
||||
|
||||
<h2>MDC radio</h2>
|
||||
|
||||
|
||||
+17
-6
@@ -1,10 +1,7 @@
|
||||
export declare const fadeInItems: AnimationTriggerMetadata;
|
||||
export declare class _MatMenu extends MatMenu {
|
||||
}
|
||||
|
||||
export declare const MAT_MENU_DEFAULT_OPTIONS: InjectionToken<MatMenuDefaultOptions>;
|
||||
|
||||
export declare const MAT_MENU_SCROLL_STRATEGY: InjectionToken<() => ScrollStrategy>;
|
||||
|
||||
export declare class MatMenu implements AfterContentInit, MatMenuPanel<MatMenuItem>, OnInit, OnDestroy {
|
||||
export declare class _MatMenuBase implements AfterContentInit, MatMenuPanel<MatMenuItem>, OnInit, OnDestroy {
|
||||
_animationDone: Subject<AnimationEvent>;
|
||||
_classList: {
|
||||
[key: string]: boolean;
|
||||
@@ -43,6 +40,20 @@ export declare class MatMenu implements AfterContentInit, MatMenuPanel<MatMenuIt
|
||||
setPositionClasses(posX?: MenuPositionX, posY?: MenuPositionY): void;
|
||||
}
|
||||
|
||||
export declare class _MatMenuDirectivesModule {
|
||||
}
|
||||
|
||||
export declare const fadeInItems: AnimationTriggerMetadata;
|
||||
|
||||
export declare const MAT_MENU_DEFAULT_OPTIONS: InjectionToken<MatMenuDefaultOptions>;
|
||||
|
||||
export declare const MAT_MENU_PANEL: InjectionToken<MatMenuPanel<any>>;
|
||||
|
||||
export declare const MAT_MENU_SCROLL_STRATEGY: InjectionToken<() => ScrollStrategy>;
|
||||
|
||||
export declare class MatMenu extends _MatMenuBase {
|
||||
}
|
||||
|
||||
export declare const matMenuAnimations: {
|
||||
readonly transformMenu: AnimationTriggerMetadata;
|
||||
readonly fadeInItems: AnimationTriggerMetadata;
|
||||
|
||||
Reference in New Issue
Block a user