diff --git a/src/api/docs/components/menu.html b/src/api/docs/components/menu.html index b2299c8a5..26b9efdab 100644 --- a/src/api/docs/components/menu.html +++ b/src/api/docs/components/menu.html @@ -4,6 +4,10 @@

Menu

+

Nested Menu

+ + + diff --git a/src/api/docs/customization/color.html b/src/api/docs/customization/color.html index 755196f88..25cf5d660 100644 --- a/src/api/docs/customization/color.html +++ b/src/api/docs/customization/color.html @@ -5,24 +5,24 @@

Color

Supported values

import { color } from '@alyle/ui/color';
 
-const Yellow = color(255, 255, 0);  // rgb
-const Black  = color(0);            // number
-const White  = color(0xffffff);     // hex
-const Text   = color(0, 0, 0, .87); // rgba
+const Yellow = color(255, 255, 0);  // rgb
+const Black  = color(0);            // number
+const White  = color(0xffffff);     // hex
+const Text   = color(0, 0, 0, .87); // rgba
 

Note that a 6-digit hexadecimal is different from a 3-digit hexadecimal.

-
color(0xffffff).css() !== color(0xfff).css(); // true
+
color(0xffffff).css() !== color(0xfff).css(); // true
 

How to use color

color and the previously mentioned methods returns an immutable Color.

import { color } from '@alyle/ui/color';
 
-const Yellow = color(255, 255, 0);
+const Yellow = color(255, 255, 0);
 

Using methods:

-
const Yellow = color(255, 255, 0);
+
const Yellow = color(255, 255, 0);
 Yellow.darken(2).alpha(.94).css(); // --> rgba(145,156,0,0.94)
 
-
color(0xffffff).luminance(); // --> 1
-color(0xffffff).luminance(0.5); // --> rgb(194,194,0)
+
color(0xffffff).luminance(); // --> 1
+color(0xffffff).luminance(0.5); // --> rgb(194,194,0)
 
\ No newline at end of file diff --git a/src/api/docs/customization/theming.html b/src/api/docs/customization/theming.html index 7b221268e..74932f49a 100644 --- a/src/api/docs/customization/theming.html +++ b/src/api/docs/customization/theming.html @@ -25,12 +25,12 @@

Overwriting the variables of a the export class CustomMinimaLight implements PartialThemeVariables { name = 'minima-light'; primary = { - default: color(0x2196f3), - contrast: color(0xffffff) + default: color(0x2196f3), + contrast: color(0xffffff) }; accent = { - default: color(0xe91e63), - contrast: color(0xffffff) + default: color(0xe91e63), + contrast: color(0xffffff) }; } @@ -41,12 +41,12 @@

Overwriting the variables of a the export class CustomMinimaDark implements PartialThemeVariables { name = 'minima-dark'; primary = { - default: color(0x9c27b0), - contrast: color(0xffffff) + default: color(0x9c27b0), + contrast: color(0xffffff) }; accent = { - default: color(0x69f0ae), - contrast: color(0x202020) + default: color(0x69f0ae), + contrast: color(0x202020) }; } diff --git a/src/api/docs/demos/field-playground.component.html.html b/src/api/docs/demos/field-playground.component.html.html index cd2195d3d..aea162ad2 100644 --- a/src/api/docs/demos/field-playground.component.html.html +++ b/src/api/docs/demos/field-playground.component.html.html @@ -82,4 +82,37 @@ </ly-field> </ly-grid> </ly-grid> -

\ No newline at end of file + + +<ly-grid container> + <ly-grid item [col]="'3'"> + </ly-grid> + <ly-grid item [col]="'6'"> + <ly-field appearance="outlined" [width]="1"> + <input lyNativeControl [formControl]="accountNumberFormData" value="{{'id'}}" + type="text"> + <ly-label>Account_Number<ly-icon>favorite</ly-icon></ly-label> + <ly-icon lySuffix (click)="openAccSearchDialog()">search</ly-icon> + <ly-hint>Account_Number</ly-hint> + <ly-error>Account_Number_Required</ly-error> + </ly-field> + <button ly-button>I'm a button</button> + </ly-grid> + <ly-grid item [col]="'3'"> + </ly-grid> + + <ly-grid item [col]="'3'"> + </ly-grid> + <ly-grid item [col]="'6'"> + <ly-field appearance="outlined" [width]="1"> + <input lyNativeControl [formControl]="accountNumberFormData" value="{{'id'}}" + type="text"> + <ly-label>Account_Number<ly-icon>favorite</ly-icon></ly-label> + <ly-icon lySuffix (click)="openAccSearchDialog()">search</ly-icon> + <ly-hint>Long text long text long text long text long text long text long text long text long text long text</ly-hint> + </ly-field> + <button ly-button>I'm a button</button> + </ly-grid> + <ly-grid item [col]="'3'"> + </ly-grid> +</ly-grid>
\ No newline at end of file diff --git a/src/api/docs/demos/field-playground.component.ts.html b/src/api/docs/demos/field-playground.component.ts.html index a62b741e1..9c9bcc6ff 100644 --- a/src/api/docs/demos/field-playground.component.ts.html +++ b/src/api/docs/demos/field-playground.component.ts.html @@ -12,5 +12,9 @@ isReadonly = new FormControl(); isDisabled = new FormControl(); password = new FormControl('', [Validators.required, Validators.minLength(8)]); + accountNumberFormData = new FormControl('', [Validators.required]); + openAccSearchDialog() { + console.log('test'); + } }
\ No newline at end of file diff --git a/src/api/docs/demos/menu-playground.component.html.html b/src/api/docs/demos/menu-playground.component.html.html index 747dd4134..7f52e1397 100644 --- a/src/api/docs/demos/menu-playground.component.html.html +++ b/src/api/docs/demos/menu-playground.component.html.html @@ -1,5 +1,5 @@
<button ly-button [openOnHover]="openOnHover" [lyMenuTriggerFor]="_myMenu">
-  I am a button
+  I'm a button
 </button>
 
 <ng-template #_myMenu let-M>
@@ -11,7 +11,7 @@
     [xAxis]="xAxis"
     [yAxis]="yAxis"
   >
-    <button ly-button ly-menu-item *ngFor="let item of [null, null, null]; index as i">
+    <button ly-button ly-menu-item *ngFor="let item of [0, 0, 0, 0]; index as i">
       <span>Item {{ i + 1 }}</span>
     </button>
   </ly-menu>
diff --git a/src/api/docs/demos/menu.lazy.module.ts.html b/src/api/docs/demos/menu.lazy.module.ts.html
index eef76ce71..e6ae13c46 100644
--- a/src/api/docs/demos/menu.lazy.module.ts.html
+++ b/src/api/docs/demos/menu.lazy.module.ts.html
@@ -4,16 +4,20 @@
 import { MenuDemo01Module } from './menu-demo-01/menu-demo-01.module';
 import { MenuPlaygroundComponent } from './menu-playground/menu-playground.component';
 import { MenuPlaygroundModule } from './menu-playground/menu-playground.module';
+import { NestedMenuModule } from './nested-menu/nested-menu.module';
+import { NestedMenuComponent } from './nested-menu/nested-menu.component';
 
 const entryComponents = [
   MenuDemo01Component,
-  MenuPlaygroundComponent
+  MenuPlaygroundComponent,
+  NestedMenuComponent
 ];
 
 @NgModule({
   imports: [
     MenuDemo01Module,
-    MenuPlaygroundModule
+    MenuPlaygroundModule,
+    NestedMenuModule
   ],
   entryComponents
 })
diff --git a/src/api/docs/demos/nested-menu.component.html.html b/src/api/docs/demos/nested-menu.component.html.html
new file mode 100644
index 000000000..61ea1c4b6
--- /dev/null
+++ b/src/api/docs/demos/nested-menu.component.html.html
@@ -0,0 +1,44 @@
+
<button ly-button [lyMenuTriggerFor]="rootMenu">menu</button>
+
+<ng-template #rootMenu let-M>
+  <ly-menu
+    [ref]="M"
+  >
+    <button ly-button ly-menu-item [lyMenuTriggerFor]="subMenu">Sub menu</button>
+    <button ly-button ly-menu-item [lyMenuTriggerFor]="subMenu2">Sub menu 2</button>
+    <button ly-button ly-menu-item *ngFor="let item of [0, 0, 0, 0]; index as i">
+      <span>Item {{ i + 1 }}</span>
+    </button>
+  </ly-menu>
+</ng-template>
+
+<ng-template #subMenu let-T>
+  <ly-menu
+    [ref]="T"
+  >
+    <button ly-button ly-menu-item *ngFor="let item of [0, 0, 0, 0]; index as i">
+      <span>Item {{ i + 1 }}</span>
+    </button>
+  </ly-menu>
+</ng-template>
+
+<ng-template #subMenu2 let-T>
+  <ly-menu
+    [ref]="T"
+  >
+    <button ly-button ly-menu-item [lyMenuTriggerFor]="subMenu3">Sub menu 3</button>
+    <button ly-button ly-menu-item *ngFor="let item of [0, 0, 0, 0]; index as i">
+      <span>Item {{ i + 1 }}</span>
+    </button>
+  </ly-menu>
+</ng-template>
+
+<ng-template #subMenu3 let-T>
+  <ly-menu
+    [ref]="T"
+  >
+    <button ly-button ly-menu-item *ngFor="let item of [0, 0, 0, 0]; index as i">
+      <span>Item {{ i + 1 }}</span>
+    </button>
+  </ly-menu>
+</ng-template>
\ No newline at end of file diff --git a/src/api/docs/demos/nested-menu.component.ts.html b/src/api/docs/demos/nested-menu.component.ts.html new file mode 100644 index 000000000..676c4a8d8 --- /dev/null +++ b/src/api/docs/demos/nested-menu.component.ts.html @@ -0,0 +1,16 @@ +
import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
+
+@Component({
+  selector: 'aui-nested-menu',
+  templateUrl: './nested-menu.component.html',
+  changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class NestedMenuComponent implements OnInit {
+
+  constructor() { }
+
+  ngOnInit(): void {
+  }
+
+}
+
\ No newline at end of file diff --git a/src/api/docs/demos/nested-menu.module.ts.html b/src/api/docs/demos/nested-menu.module.ts.html new file mode 100644 index 000000000..57155115f --- /dev/null +++ b/src/api/docs/demos/nested-menu.module.ts.html @@ -0,0 +1,20 @@ +
import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { LyMenuModule } from '@alyle/ui/menu';
+import { LyButtonModule } from '@alyle/ui/button';
+
+import { NestedMenuComponent } from './nested-menu.component';
+
+
+
+@NgModule({
+  declarations: [NestedMenuComponent],
+  imports: [
+    CommonModule,
+    LyMenuModule,
+    LyButtonModule
+  ],
+  exports: [NestedMenuComponent]
+})
+export class NestedMenuModule { }
+
\ No newline at end of file diff --git a/src/app/docs/components/menu-demo/menu.lazy.module.ts b/src/app/docs/components/menu-demo/menu.lazy.module.ts index 8682778ce..ee8a8eb7c 100644 --- a/src/app/docs/components/menu-demo/menu.lazy.module.ts +++ b/src/app/docs/components/menu-demo/menu.lazy.module.ts @@ -4,16 +4,20 @@ import { MenuDemo01Component } from './menu-demo-01/menu-demo-01.component'; import { MenuDemo01Module } from './menu-demo-01/menu-demo-01.module'; import { MenuPlaygroundComponent } from './menu-playground/menu-playground.component'; import { MenuPlaygroundModule } from './menu-playground/menu-playground.module'; +import { NestedMenuModule } from './nested-menu/nested-menu.module'; +import { NestedMenuComponent } from './nested-menu/nested-menu.component'; const entryComponents = [ MenuDemo01Component, - MenuPlaygroundComponent + MenuPlaygroundComponent, + NestedMenuComponent ]; @NgModule({ imports: [ MenuDemo01Module, - MenuPlaygroundModule + MenuPlaygroundModule, + NestedMenuModule ], entryComponents }) diff --git a/src/app/docs/components/menu-demo/menu.md b/src/app/docs/components/menu-demo/menu.md index 602b79cf4..89d827809 100644 --- a/src/app/docs/components/menu-demo/menu.md +++ b/src/app/docs/components/menu-demo/menu.md @@ -7,6 +7,12 @@ Menus allow users make a selection from multiple options. +## Nested Menu + + + + + ## Menu Playground diff --git a/src/app/docs/components/menu-demo/nested-menu/nested-menu.component.html b/src/app/docs/components/menu-demo/nested-menu/nested-menu.component.html new file mode 100644 index 000000000..8e5620c45 --- /dev/null +++ b/src/app/docs/components/menu-demo/nested-menu/nested-menu.component.html @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/app/docs/components/menu-demo/nested-menu/nested-menu.component.spec.ts b/src/app/docs/components/menu-demo/nested-menu/nested-menu.component.spec.ts new file mode 100644 index 000000000..c209914ae --- /dev/null +++ b/src/app/docs/components/menu-demo/nested-menu/nested-menu.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { NestedMenuComponent } from './nested-menu.component'; + +describe('NestedMenuComponent', () => { + let component: NestedMenuComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ NestedMenuComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(NestedMenuComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/docs/components/menu-demo/nested-menu/nested-menu.component.ts b/src/app/docs/components/menu-demo/nested-menu/nested-menu.component.ts new file mode 100644 index 000000000..3747c458d --- /dev/null +++ b/src/app/docs/components/menu-demo/nested-menu/nested-menu.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; + +@Component({ + selector: 'aui-nested-menu', + templateUrl: './nested-menu.component.html', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class NestedMenuComponent implements OnInit { + + constructor() { } + + ngOnInit(): void { + } + +} diff --git a/src/app/docs/components/menu-demo/nested-menu/nested-menu.module.ts b/src/app/docs/components/menu-demo/nested-menu/nested-menu.module.ts new file mode 100644 index 000000000..38428fb23 --- /dev/null +++ b/src/app/docs/components/menu-demo/nested-menu/nested-menu.module.ts @@ -0,0 +1,19 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { LyMenuModule } from '@alyle/ui/menu'; +import { LyButtonModule } from '@alyle/ui/button'; + +import { NestedMenuComponent } from './nested-menu.component'; + + + +@NgModule({ + declarations: [NestedMenuComponent], + imports: [ + CommonModule, + LyMenuModule, + LyButtonModule + ], + exports: [NestedMenuComponent] +}) +export class NestedMenuModule { } diff --git a/src/lib/menu/menu.html b/src/lib/menu/menu.html index 300898e84..d07208ce3 100644 --- a/src/lib/menu/menu.html +++ b/src/lib/menu/menu.html @@ -1,5 +1,8 @@
+ [@transformMenu]="'enter'" + (@transformMenu.start)="_onAnimationStart($event)" + (@transformMenu.done)="_onAnimationDone($event)" +>
\ No newline at end of file diff --git a/src/lib/menu/menu.ts b/src/lib/menu/menu.ts index 1c4b6fdba..fcf1d436f 100644 --- a/src/lib/menu/menu.ts +++ b/src/lib/menu/menu.ts @@ -3,7 +3,6 @@ import { Component, Directive, ElementRef, - HostBinding, HostListener, Input, OnDestroy, @@ -14,7 +13,11 @@ import { ViewChild, Output, EventEmitter, - OnChanges + OnChanges, + ContentChildren, + forwardRef, + QueryList, + HostBinding } from '@angular/core'; import { LyOverlay, @@ -31,19 +34,21 @@ import { StyleTemplate, lyl, ThemeRef, - LyOverlayPosition + LyOverlayPosition, + StyleRenderer } from '@alyle/ui'; import { trigger, style, animate, transition, - keyframes, - AnimationEvent + AnimationEvent, + group } from '@angular/animations'; import { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion'; -import { Subject } from 'rxjs'; import { ViewportRuler } from '@angular/cdk/scrolling'; +import { Subject, asapScheduler } from 'rxjs'; +import { take, delay } from 'rxjs/operators'; export interface LyMenuTheme { /** Styles for Menu Component */ @@ -59,6 +64,7 @@ const STYLE_PRIORITY = -1; export const STYLES = (theme: ThemeVariables & LyMenuVariables, ref: ThemeRef) => { const menu = ref.selectorsOf(STYLES); + const { after } = theme; return { $priority: STYLE_PRIORITY, root: () => ( @@ -87,27 +93,41 @@ export const STYLES = (theme: ThemeVariables & LyMenuVariables, ref: ThemeRef) = border-radius: 0 width: 100% justify-content: flex-start + }`, + itemSubMenuTrigger: () => lyl `{ + padding-${after}: 32px + &::after { + width: 0 + height: 0 + border-style: solid + border-width: 5px 0 5px 5px + border-color: transparent transparent transparent currentColor + content: "" + display: inline-block + position: absolute + top: 50% + ${after}: 16px + transform: translateY(-50%) + } }` }; }; const ANIMATIONS = [ - trigger('menuEnter', [ - transition('void => in', [ - animate('125ms cubic-bezier(0, 0, 0.2, 1)', keyframes([ - style({ - opacity: 0, - transform: 'scale(0.8)' - }), - style({ - opacity: 1, - transform: 'scale(1)' - }) - ])) - ]), + trigger('transformMenu', [ + transition('void => enter', group([ + style({ + opacity: 0, + transform: 'scale(0.8)' + }), + animate('100ms linear', style({ + opacity: 1 + })), + animate('120ms cubic-bezier(0, 0, 0.2, 1)', style({transform: 'scale(1)'})), + ])) ]), - trigger('menuLeave', [ - transition('* => void', animate('150ms linear', style({ opacity: 0 }))) + trigger('transformMenuLeave', [ + transition('* => void', animate('100ms 25ms linear', style({ opacity: 0 }))) ]) ]; @@ -124,14 +144,35 @@ export class LyMenu implements OnChanges, OnInit, AfterViewInit { * @docs-private */ readonly classes = this._theme.renderStyleSheet(STYLES); + + /** Whether the menu is animating. */ + _isAnimating: boolean; + + /** Whether the menu is destroying. */ + _isDestroying: boolean; + + /** Emits whenever an animation on the menu completes. */ + _animationDone = new Subject(); + + private _menuRef: OverlayFactory; /** * Destroy menu * @docs-private */ destroy: () => void; @ViewChild('container') _container?: ElementRef; + @ContentChildren(forwardRef(() => LyMenuItem)) readonly menuItems?: QueryList; - @Input() ref: LyMenuTriggerFor; + /** Menu Trigger */ + @Input() + set ref(value: LyMenuTriggerFor) { + this._ref = value; + this._menuRef = value._menuRef!; + } + get ref() { + return this._ref; + } + private _ref: LyMenuTriggerFor; /** The point in the anchor where the menu `xAxis` will be attached. */ @Input() xAnchor: XPosition; @@ -174,13 +215,6 @@ export class LyMenu implements OnChanges, OnInit, AfterViewInit { } private _hasBackdrop: boolean = true; - @HostBinding('@menuLeave') menuLeave2; - @HostListener('@menuLeave.done', ['$event']) endAnimation(e: AnimationEvent) { - if (e.toState === 'void') { - this.ref.destroy(); - } - } - constructor( private _theme: LyTheme2, private _el: ElementRef, @@ -192,7 +226,7 @@ export class LyMenu implements OnChanges, OnInit, AfterViewInit { ngOnChanges() { if (this.ref?._menuRef && this._container) { - this.ref._menuRef.updateBackdrop(this.hasBackdrop); + this.ref._menuRef.updateBackdrop(this.ref._isItemSubMenuTrigger() ? false : this.hasBackdrop); this._updatePlacement(); } } @@ -201,16 +235,12 @@ export class LyMenu implements OnChanges, OnInit, AfterViewInit { if (!this.ref) { throw new Error('LyMenu: require @Input() ref'); } - // if (!this.placement && !this.xPosition && !this.yPosition) { - // this.xPosition = DEFAULT_XPOSITION; - // this.placement = DEFAULT_PLACEMENT; - // } } ngAfterViewInit() { if (this.ref._menuRef) { this.ref._menuRef.onResizeScroll = this._updatePlacement.bind(this); - this.ref._menuRef.updateBackdrop(this.hasBackdrop); + this.ref._menuRef.updateBackdrop(this.ref._isItemSubMenuTrigger() ? false : this.hasBackdrop); } this._updatePlacement(); this.ref.menuOpened.emit(); @@ -229,13 +259,19 @@ export class LyMenu implements OnChanges, OnInit, AfterViewInit { const position = this.placement ? new Positioning(this.placement, this.xPosition, this.yPosition, this.ref._getHostElement(), el, this._theme.variables) - : new LyOverlayPosition(this._theme, this._viewportRuler, this.ref._getHostElement(), el) - .setXAnchor(this.xAnchor) - .setYAnchor(this.yAnchor) - .setXAxis(this.xAxis) - .setYAxis(this.yAxis) - .setFlip(true) - .build(); + : !this.ref._isItemSubMenuTrigger() + ? new LyOverlayPosition(this._theme, this._viewportRuler, this.ref._getHostElement(), el) + .setXAnchor(this.xAnchor) + .setYAnchor(this.yAnchor) + .setXAxis(this.xAxis) + .setYAxis(this.yAxis) + .setFlip(true) + .build() + : new LyOverlayPosition(this._theme, this._viewportRuler, this.ref._getHostElement(), el) + .setXAnchor(XPosition.after) + .setYAnchor(YPosition.above) + .setFlip(true) + .build(); if (position instanceof Positioning) { // set position deprecated @@ -256,18 +292,34 @@ export class LyMenu implements OnChanges, OnInit, AfterViewInit { } + @HostBinding('@transformMenuLeave') transformMenuLeave: unknown; + @HostListener('@transformMenuLeave.start', ['$event']) _onAnimationStart(event: AnimationEvent) { + this._isAnimating = true; + if (event.triggerName === 'transformMenuLeave' && event.toState === 'void') { + this._isDestroying = true; + } + } + @HostListener('@transformMenuLeave.done', ['$event']) _onAnimationDone(event: AnimationEvent) { + this._animationDone.next(event); + this._isAnimating = false; + if (event.toState === 'void' && event.triggerName === 'transformMenuLeave') { + this.ref.destroy(this._menuRef); + } + } + + static ngAcceptInputType_hasBackdrop: BooleanInput; } @Directive({ - selector: '[ly-menu-item]' + selector: '[ly-menu-item]', + host: { + '(click)': '_handleClick()', + '(mouseenter)': '_handleMouseEnter()' + } }) export class LyMenuItem { - @HostListener('click') _click() { - if (this._menu.ref && this._menu.ref._menuRef) { - this._menu.ref.closeMenu(); - } - } + constructor( @Optional() private _menu: LyMenu, el: ElementRef, @@ -275,26 +327,75 @@ export class LyMenuItem { ) { renderer.addClass(el.nativeElement, _menu.classes.item); } + + _handleClick() { + if (this._menu.ref && this._menu.ref._menuRef) { + if (!this._getItemSubMenuTrigger()) { + let currentTrigger = this._menu.ref; + while (currentTrigger) { + currentTrigger.closeMenu(); + currentTrigger = currentTrigger._menu?.ref; + } + } + } + } + + _handleMouseEnter() { + const itemSubMenuTrigger = this._getItemSubMenuTrigger(); + if (itemSubMenuTrigger && !this._menu._isDestroying) { + if (this._menu._isAnimating) { + this._menu._animationDone + .pipe(take(1), delay(0, asapScheduler)) + .subscribe(() => { + itemSubMenuTrigger.openMenu(); + this._closeOtherMenus(); + }); + } else { + itemSubMenuTrigger.openMenu(); + this._closeOtherMenus(); + } + } else { + this._closeOtherMenus(); + } + } + + /** Except for this, close all menus */ + private _closeOtherMenus() { + this._menu.menuItems!.forEach(menuItem => { + if (menuItem !== this) { + menuItem._getItemSubMenuTrigger()?.closeMenu(); + } + }); + } + + _setItemSubMenuTrigger(menuTrigger: LyMenuTriggerFor) { + this._itemSubMenuTrigger = menuTrigger; + } + _getItemSubMenuTrigger() { + return this._itemSubMenuTrigger; + } + private _itemSubMenuTrigger?: LyMenuTriggerFor; } + @Directive({ selector: '[lyMenuTriggerFor]', host: { - '(click)': '_handleClick()', - '(mouseenter)': '_handleMouseEnter()', - '(mouseleave)': '_handleMouseLeave()' + '(click)': '_handleClick()' }, - exportAs: 'lyMenuTriggerFor' + exportAs: 'lyMenuTriggerFor', + providers: [ + StyleRenderer + ] }) export class LyMenuTriggerFor implements OnDestroy { + readonly classes = this.sRenderer.renderSheet(STYLES); + /** Current menuRef */ _menuRef?: OverlayFactory | null; private _menuOpen = false; private _destroying: boolean; - /** Stream that emits when the menu item is hovered. */ - readonly _hovered: Subject = new Subject(); - /** Whether the menu is open. */ get menuOpen() { return this._menuOpen; @@ -312,13 +413,24 @@ export class LyMenuTriggerFor implements OnDestroy { } private _openOnHover: boolean = true; + /** Data to be passed to the menu. */ + @Input('lyMenuTriggerData') menuData: any; + @Output() readonly menuOpened = new EventEmitter(); @Output() readonly menuClosed = new EventEmitter(); constructor( private elementRef: ElementRef, - private overlay: LyOverlay - ) { } + private overlay: LyOverlay, + @Optional() private _menuItem: LyMenuItem, + readonly sRenderer: StyleRenderer, + @Optional() public _menu: LyMenu + ) { + if (this._isItemSubMenuTrigger()) { + _menuItem._setItemSubMenuTrigger(this); + sRenderer.addClass(this.classes.itemSubMenuTrigger); + } + } ngOnDestroy() { // Not force destruction if it is already being destroyed @@ -328,22 +440,17 @@ export class LyMenuTriggerFor implements OnDestroy { } _handleClick() { - this.toggleMenu(); - } - - _handleMouseEnter() { - this._hovered.next(true); - } - - _handleMouseLeave() { - this._hovered.next(false); + if (!this._isItemSubMenuTrigger()) { + this.toggleMenu(); + } } /** Opens the menu */ openMenu() { if (!this._menuRef) { this._menuRef = this.overlay.create(this.lyMenuTriggerFor, { - $implicit: this + $implicit: this, + data: this.menuData }, { styles: { top: 0, @@ -373,20 +480,18 @@ export class LyMenuTriggerFor implements OnDestroy { /** @docs-private */ detach() { if (this._menuRef) { - this._destroying = true; this._menuRef.detach(); + this._menuRef = null; + this._destroying = true; } } /** @docs-private */ - destroy() { - if (this._menuRef) { - this.menuClosed.emit(null!); - this._menuRef.remove(); - this._menuRef = null; - this._destroying = false; - Promise.resolve(null).then(() => this._menuOpen = false); - } + destroy(menuRef: OverlayFactory) { + this.menuClosed.emit(null!); + menuRef.remove(); + this._destroying = false; + Promise.resolve(null).then(() => this._menuOpen = false); } _getHostElement() { @@ -397,4 +502,11 @@ export class LyMenuTriggerFor implements OnDestroy { this._menuOpen = true; } + /** + * @docs-private + */ + _isItemSubMenuTrigger() { + return !!this._menuItem; + } + } diff --git a/src/lib/src/dom/index.ts b/src/lib/src/dom/index.ts index 28abc8e61..faecb23dd 100644 --- a/src/lib/src/dom/index.ts +++ b/src/lib/src/dom/index.ts @@ -6,3 +6,4 @@ export * from './overlay-config'; export * from './overlay-factory'; export * from './overlay-injector'; export * from './overlay-styles'; +export * from './overlay-backdrop'; diff --git a/src/lib/src/dom/overlay-factory.ts b/src/lib/src/dom/overlay-factory.ts index 677a053fc..a0788c29d 100644 --- a/src/lib/src/dom/overlay-factory.ts +++ b/src/lib/src/dom/overlay-factory.ts @@ -18,6 +18,10 @@ export class OverlayFactory { /** Function that will be called on scroll or resize event */ onResizeScroll: (() => void) | null; + get injector(): Injector | undefined { + return this._newInjector; + } + get containerElement() { return this._el as HTMLDivElement; } diff --git a/src/lib/src/theme/style.directive.ts b/src/lib/src/theme/style.directive.ts index dfbed58f5..d2b144798 100644 --- a/src/lib/src/theme/style.directive.ts +++ b/src/lib/src/theme/style.directive.ts @@ -18,7 +18,6 @@ const STYLE_PRIORITY = -0.5; * [height], [maxHeight], [minHeight], * Others * [lyStyle] - * [width] */ @Directive({ selector: `[lyStyle], diff --git a/src/lib/themes/minima/dark.ts b/src/lib/themes/minima/dark.ts index 405389aa2..4dfd06a3d 100644 --- a/src/lib/themes/minima/dark.ts +++ b/src/lib/themes/minima/dark.ts @@ -5,6 +5,7 @@ import { LyFieldTheme } from '@alyle/ui/field'; import { LyTooltipTheme } from '@alyle/ui/tooltip'; import { LySnackBarTheme } from '@alyle/ui/snack-bar'; import { Injectable } from '@angular/core'; +import { LyMenuTheme } from '@alyle/ui/menu'; const contrast = new Color(0xffffff); const shadow = new Color(0, 0, 0, 1); @@ -92,4 +93,14 @@ export class MinimaDark extends MinimaBase { color: ${new Color(0, 0, 0, .87)} }`) }; + + menu: LyMenuTheme = { + root: new StyleCollection( + __ => lyl `{ + ${__.itemSubMenuTrigger}:after { + color: ${color(0xffffff)} + } + }` + ) + }; } diff --git a/src/lib/themes/minima/light.ts b/src/lib/themes/minima/light.ts index 44a16644e..7f269109b 100644 --- a/src/lib/themes/minima/light.ts +++ b/src/lib/themes/minima/light.ts @@ -1,9 +1,10 @@ import { ThemeConfig, shadowBuilder, lyl, StyleCollection, mergeThemes } from '@alyle/ui'; -import { Color } from '@alyle/ui/color'; +import { Color, color } from '@alyle/ui/color'; import { MinimaBase } from './base'; import { LyFieldTheme } from '@alyle/ui/field'; import { LyTooltipTheme } from '@alyle/ui/tooltip'; import { LySnackBarTheme } from '@alyle/ui/snack-bar'; +import { LyMenuTheme } from '@alyle/ui/menu'; import { Injectable } from '@angular/core'; const contrast = new Color(0xffffff); @@ -96,4 +97,14 @@ export class MinimaLight extends MinimaBase implements ThemeConfig { color: ${new Color(0xffffff)} }`) }; + + menu: LyMenuTheme = { + root: new StyleCollection( + __ => lyl `{ + ${__.itemSubMenuTrigger}:after { + color: ${color(0, 0, 0, 0.54)} + } + }` + ) + }; }