@alyle/ui
Version:
Minimal Design, a set of components for Angular
513 lines (508 loc) • 23.6 kB
JavaScript
import * as i0 from '@angular/core';
import { HostListener, HostBinding, Input, forwardRef, ContentChildren, ViewChild, Component, Optional, Directive, EventEmitter, Output, NgModule } from '@angular/core';
import * as i1 from '@alyle/ui';
import { shadowBuilder, StyleCollection, Positioning, LyOverlayPosition, XPosition, YPosition, StyleRenderer, LyCommonModule, LyOverlayModule } from '@alyle/ui';
import { trigger, transition, group, style, animate } from '@angular/animations';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { Subject, asapScheduler } from 'rxjs';
import { take, delay, debounceTime } from 'rxjs/operators';
import * as i2 from '@angular/cdk/scrolling';
import { FormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
const STYLE_PRIORITY = -1;
const STYLES = (theme, ref) => {
const menu = ref.selectorsOf(STYLES);
const { after } = theme;
return {
$name: LyMenu.и,
$priority: STYLE_PRIORITY,
root: () => (theme.menu?.root
&& (theme.menu.root instanceof StyleCollection
? theme.menu.root.setTransformer(fn => fn(menu)).css
: theme.menu.root(menu))),
container: (_className) => `${_className}{background:${theme.background.primary.default};border-radius:2px;box-shadow:${shadowBuilder(4)};display:block;padding-top:8px;padding-bottom:8px;transform-origin:inherit;pointer-events:all;overflow:auto;max-height:inherit;max-width:inherit;box-sizing:border-box;}`,
item: (_className) => `${_className}{display:flex;min-height:48px;border-radius:0;width:100%;justify-content:flex-start;font-weight:400;}${_className} ly-icon{margin-${after}:16px;}`,
itemSubMenuTrigger: () => (_className) => `${_className}{padding-${after}:32px;}${_className}::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('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('transformMenuLeave', [
transition('* => void', animate('100ms 25ms linear', style({ opacity: 0 })))
])
];
/** Menu container */
class LyMenu {
/** Menu Trigger */
set ref(value) {
this._ref = value;
this._menuRef = value._menuRef;
}
get ref() {
return this._ref;
}
/** Whether the menu has a backdrop. */
get hasBackdrop() {
return this._hasBackdrop;
}
set hasBackdrop(value) {
this._hasBackdrop = coerceBooleanProperty(value);
}
constructor(_theme, _el, _renderer, _viewportRuler, sRenderer) {
this._theme = _theme;
this._el = _el;
this._renderer = _renderer;
this._viewportRuler = _viewportRuler;
this.sRenderer = sRenderer;
/**
* styles
* @docs-private
*/
this.classes = this.sRenderer.renderSheet(STYLES, true);
/** Emits whenever an animation on the menu completes. */
this._animationDone = new Subject();
this._hasBackdrop = true;
}
/** @docs-private */
static { this.и = 'LyMenu'; }
ngOnChanges() {
if (this.ref?._menuRef && this._container) {
// Update backdrop
this.ref._menuRef.updateBackdrop(this.ref._isItemSubMenuTrigger() ? false : this.hasBackdrop);
this._updatePlacement();
this._checkBackdropAndOpenOnHover();
}
}
ngOnInit() {
if (!this.ref) {
throw new Error('LyMenu: require @Input() ref');
}
}
ngAfterViewInit() {
if (this.ref._menuRef) {
this.ref._menuRef.onResizeScroll = this._updatePlacement.bind(this);
this.ref._menuRef.updateBackdrop(this.ref._isItemSubMenuTrigger() ? false : this.hasBackdrop);
this._checkBackdropAndOpenOnHover();
}
this._updatePlacement();
this.ref.menuOpened.emit();
Promise.resolve(null).then(() => {
this.ref._setMenuOpenToTrue();
});
const hostTrigger = this._getHostMenuTrigger();
hostTrigger._menuDetached
.pipe(take(1))
.subscribe(() => this._ref.closeMenu());
this._addOpenOnHover();
}
ngOnDestroy() {
this._removeOpenOnHoverListeners();
}
_checkBackdropAndOpenOnHover() {
const hostTrigger = this._getHostMenuTrigger();
if (this.hasBackdrop && hostTrigger._menuOpenOnHoverRef?.openOnHover) {
throw new Error(`${LyMenu.и}: Can't use [hasBackdrop] with [openOnHover] at the same time, set [hasBackdrop] to false to use [openOnHover]`);
}
}
_getHostMenuTrigger() {
let menuTrigger = this.ref;
while (menuTrigger._menu?.ref) {
menuTrigger = menuTrigger._menu.ref;
}
return menuTrigger;
}
_addOpenOnHover() {
const hostTrigger = this._getHostMenuTrigger();
if (hostTrigger._menuOpenOnHoverRef?.openOnHover && !this._mouseenterListen && !this._mouseleaveListen) {
hostTrigger._menuOpenOnHoverRef._handleMouseEnterOrLeave(true);
this._mouseenterListen = this._renderer
.listen(this._el.nativeElement, 'mouseenter', () => hostTrigger._menuOpenOnHoverRef._handleMouseEnterOrLeave(true));
this._mouseleaveListen = this._renderer
.listen(this._el.nativeElement, 'mouseleave', () => hostTrigger._menuOpenOnHoverRef._handleMouseEnterOrLeave(false));
}
}
/** Remove listeners */
_removeOpenOnHoverListeners() {
if (this._mouseenterListen) {
this._mouseenterListen();
}
if (this._mouseleaveListen) {
this._mouseleaveListen();
}
}
/** Update Menu Position */
_updatePlacement() {
const el = this.ref._menuRef?.containerElement;
const container = this._container?.nativeElement;
// Do not update when not available
if (!el || !container) {
return;
}
// reset height & width
this._renderer.setStyle(container, 'height', 'initial');
this._renderer.setStyle(container, 'width', 'initial');
const position = this.placement
? new Positioning(this.placement, this.xPosition, this.yPosition, this.ref._getHostElement(), el, this._theme.variables)
: !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
this._renderer.setStyle(el, 'transform', `translate3d(${position.x}px, ${position.y}px, 0)`);
this._renderer.setStyle(this._el.nativeElement, 'transform-origin', `${position.ox} ${position.oy} 0`);
// set height & width deprecated
this._renderer.setStyle(container, 'height', position.height === 'initial' ? '100%' : position.height);
this._renderer.setStyle(container, 'width', position.width === 'initial' ? '100%' : position.width);
}
else {
// set position
this._renderer.setStyle(el, 'left', `${position.x}px`);
this._renderer.setStyle(el, 'top', `${position.y}px`);
this._renderer.setStyle(container, 'width', position.width ? `${position.width}px` : '100%');
this._renderer.setStyle(container, 'height', position.height ? `${position.height}px` : '100%');
this._renderer.setStyle(this._el.nativeElement, 'transform-origin', `${position.xo}px ${position.yo}px 0`);
}
}
_onAnimationStart(event) {
this._isAnimating = true;
if (event.triggerName === 'transformMenuLeave' && event.toState === 'void') {
this._isDestroying = true;
}
}
_onAnimationDone(event) {
this._animationDone.next(event);
this._isAnimating = false;
if (event.toState === 'void' && event.triggerName === 'transformMenuLeave') {
this.ref.destroy(this._menuRef);
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.10", ngImport: i0, type: LyMenu, deps: [{ token: i1.LyTheme2 }, { token: i0.ElementRef }, { token: i0.Renderer2 }, { token: i2.ViewportRuler }, { token: i1.StyleRenderer }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.10", type: LyMenu, isStandalone: false, selector: "ly-menu", inputs: { ref: "ref", hasBackdrop: "hasBackdrop", xAnchor: "xAnchor", yAnchor: "yAnchor", xAxis: "xAxis", yAxis: "yAxis", placement: "placement", xPosition: "xPosition", yPosition: "yPosition" }, host: { listeners: { "@transformMenuLeave.start": "_onAnimationStart($event)", "@transformMenuLeave.done": "_onAnimationDone($event)" }, properties: { "@transformMenuLeave": "this.transformMenuLeave" } }, providers: [
StyleRenderer
], queries: [{ propertyName: "menuItems", predicate: i0.forwardRef(() => LyMenuItem) }], viewQueries: [{ propertyName: "_container", first: true, predicate: ["container"], descendants: true }], exportAs: ["lyMenu"], usesOnChanges: true, ngImport: i0, template: "<div #container\n [class]=\"classes.container\"\n [@transformMenu]=\"'enter'\"\n (@transformMenu.start)=\"_onAnimationStart($event)\"\n (@transformMenu.done)=\"_onAnimationDone($event)\"\n>\n <ng-content></ng-content>\n</div>", animations: [...ANIMATIONS] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.10", ngImport: i0, type: LyMenu, decorators: [{
type: Component,
args: [{ selector: 'ly-menu', animations: [...ANIMATIONS], exportAs: 'lyMenu', providers: [
StyleRenderer
], standalone: false, template: "<div #container\n [class]=\"classes.container\"\n [@transformMenu]=\"'enter'\"\n (@transformMenu.start)=\"_onAnimationStart($event)\"\n (@transformMenu.done)=\"_onAnimationDone($event)\"\n>\n <ng-content></ng-content>\n</div>" }]
}], ctorParameters: () => [{ type: i1.LyTheme2 }, { type: i0.ElementRef }, { type: i0.Renderer2 }, { type: i2.ViewportRuler }, { type: i1.StyleRenderer }], propDecorators: { ref: [{
type: Input
}], hasBackdrop: [{
type: Input
}], _container: [{
type: ViewChild,
args: ['container']
}], menuItems: [{
type: ContentChildren,
args: [forwardRef(() => LyMenuItem)]
}], xAnchor: [{
type: Input
}], yAnchor: [{
type: Input
}], xAxis: [{
type: Input
}], yAxis: [{
type: Input
}], placement: [{
type: Input
}], xPosition: [{
type: Input
}], yPosition: [{
type: Input
}], transformMenuLeave: [{
type: HostBinding,
args: ['@transformMenuLeave']
}], _onAnimationStart: [{
type: HostListener,
args: ['@transformMenuLeave.start', ['$event']]
}], _onAnimationDone: [{
type: HostListener,
args: ['@transformMenuLeave.done', ['$event']]
}] } });
class LyMenuItem {
constructor(_menu, el, renderer) {
this._menu = _menu;
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 */
_closeOtherMenus() {
this._menu.menuItems.forEach(menuItem => {
if (menuItem !== this) {
menuItem._getItemSubMenuTrigger()?.closeMenu();
}
});
}
_setItemSubMenuTrigger(menuTrigger) {
this._itemSubMenuTrigger = menuTrigger;
}
_getItemSubMenuTrigger() {
return this._itemSubMenuTrigger;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.10", ngImport: i0, type: LyMenuItem, deps: [{ token: LyMenu, optional: true }, { token: i0.ElementRef }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.10", type: LyMenuItem, isStandalone: false, selector: "[ly-menu-item]", host: { listeners: { "click": "_handleClick()", "mouseenter": "_handleMouseEnter()" } }, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.10", ngImport: i0, type: LyMenuItem, decorators: [{
type: Directive,
args: [{
selector: '[ly-menu-item]',
host: {
'(click)': '_handleClick()',
'(mouseenter)': '_handleMouseEnter()'
},
standalone: false
}]
}], ctorParameters: () => [{ type: LyMenu, decorators: [{
type: Optional
}] }, { type: i0.ElementRef }, { type: i0.Renderer2 }] });
class LyMenuTriggerFor {
/** Whether the menu is open. */
get menuOpen() {
return this._menuOpen;
}
constructor(elementRef, overlay, _menuItem, sRenderer, _menu) {
this.elementRef = elementRef;
this.overlay = overlay;
this._menuItem = _menuItem;
this.sRenderer = sRenderer;
this._menu = _menu;
this.classes = this.sRenderer.renderSheet(STYLES);
this._menuOpen = false;
this._menuDetached = new Subject();
this.menuOpened = new EventEmitter();
this.menuClosed = new EventEmitter();
if (this._isItemSubMenuTrigger()) {
_menuItem._setItemSubMenuTrigger(this);
sRenderer.addClass(this.classes.itemSubMenuTrigger);
}
}
ngOnDestroy() {
// Not force destruction if it is already being destroyed
if (!this._destroying) {
this.closeMenu();
}
this._menuDetached.complete();
}
_handleClick() {
if (!this._isItemSubMenuTrigger()) {
this.toggleMenu();
}
}
/** Opens the menu */
openMenu() {
if (!this._menuRef) {
this._menuRef = this.overlay.create(this.lyMenuTriggerFor, {
$implicit: this,
data: this.menuData
}, {
styles: {
top: 0,
left: 0,
pointerEvents: null
},
fnDestroy: this.detach.bind(this),
hasBackdrop: false
});
}
}
/** Closes the menu */
closeMenu() {
this.detach();
}
/** Toggle menu */
toggleMenu() {
if (this._menuRef) {
this.closeMenu();
}
else {
this.openMenu();
}
}
/** @docs-private */
detach() {
if (this._menuRef) {
this._menuRef.detach();
this._menuRef = null;
this._destroying = true;
this._menuDetached.next();
}
}
/** @docs-private */
destroy(menuRef) {
this.menuClosed.emit(null);
menuRef.remove();
this._destroying = false;
Promise.resolve(null).then(() => this._menuOpen = false);
}
_getHostElement() {
return this.elementRef.nativeElement;
}
_setMenuOpenToTrue() {
this._menuOpen = true;
}
/**
* @docs-private
*/
_isItemSubMenuTrigger() {
return !!this._menuItem;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.10", ngImport: i0, type: LyMenuTriggerFor, deps: [{ token: i0.ElementRef }, { token: i1.LyOverlay }, { token: LyMenuItem, optional: true }, { token: i1.StyleRenderer }, { token: LyMenu, optional: true }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.10", type: LyMenuTriggerFor, isStandalone: false, selector: "[lyMenuTriggerFor]", inputs: { lyMenuTriggerFor: "lyMenuTriggerFor", menuData: ["lyMenuTriggerData", "menuData"] }, outputs: { menuOpened: "menuOpened", menuClosed: "menuClosed" }, host: { listeners: { "click": "_handleClick()" } }, providers: [
StyleRenderer
], exportAs: ["lyMenuTriggerFor"], ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.10", ngImport: i0, type: LyMenuTriggerFor, decorators: [{
type: Directive,
args: [{
selector: '[lyMenuTriggerFor]',
host: {
'(click)': '_handleClick()'
},
exportAs: 'lyMenuTriggerFor',
providers: [
StyleRenderer
],
standalone: false
}]
}], ctorParameters: () => [{ type: i0.ElementRef }, { type: i1.LyOverlay }, { type: LyMenuItem, decorators: [{
type: Optional
}] }, { type: i1.StyleRenderer }, { type: LyMenu, decorators: [{
type: Optional
}] }], propDecorators: { lyMenuTriggerFor: [{
type: Input
}], menuData: [{
type: Input,
args: ['lyMenuTriggerData']
}], menuOpened: [{
type: Output
}], menuClosed: [{
type: Output
}] } });
class LyMenuOpenOnHover {
/** Whether menu should open on hover. */
get openOnHover() {
return this._openOnHover;
}
set openOnHover(value) {
this._openOnHover = coerceBooleanProperty(value);
Promise.resolve(null)
.then(() => this._openOnHover
? this._trigger._menuOpenOnHoverRef = this
: delete this._trigger._menuOpenOnHoverRef);
}
constructor(_trigger) {
this._trigger = _trigger;
this._events = new Subject();
this._openOnHover = true;
_trigger._menuOpenOnHoverRef = this;
this._events
.pipe(debounceTime(200))
.subscribe(enterOrLeave => {
if (this.openOnHover) {
if (enterOrLeave) {
_trigger.openMenu();
}
else {
_trigger.closeMenu();
}
}
});
}
ngOnDestroy() {
this._events.complete();
}
/** Handle mouseenter or mouseleave */
_handleMouseEnterOrLeave(leaveOrEnter) {
if (this.openOnHover) {
if (leaveOrEnter) {
this._trigger.openMenu();
}
this._events.next(leaveOrEnter);
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.10", ngImport: i0, type: LyMenuOpenOnHover, deps: [{ token: LyMenuTriggerFor }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.10", type: LyMenuOpenOnHover, isStandalone: false, selector: "[lyMenuTriggerFor][openOnHover]", inputs: { openOnHover: "openOnHover" }, host: { listeners: { "mouseenter": "_handleMouseEnterOrLeave(true)", "mouseleave": "_handleMouseEnterOrLeave(false)" } }, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.10", ngImport: i0, type: LyMenuOpenOnHover, decorators: [{
type: Directive,
args: [{
selector: '[lyMenuTriggerFor][openOnHover]',
host: {
'(mouseenter)': '_handleMouseEnterOrLeave(true)',
'(mouseleave)': '_handleMouseEnterOrLeave(false)'
},
standalone: false
}]
}], ctorParameters: () => [{ type: LyMenuTriggerFor }], propDecorators: { openOnHover: [{
type: Input
}] } });
class LyMenuModule {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.10", ngImport: i0, type: LyMenuModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.10", ngImport: i0, type: LyMenuModule, declarations: [LyMenu, LyMenuItem, LyMenuTriggerFor, LyMenuOpenOnHover], imports: [CommonModule, FormsModule, LyCommonModule, LyOverlayModule], exports: [LyMenu, LyMenuItem, LyMenuTriggerFor, LyMenuOpenOnHover] }); }
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.10", ngImport: i0, type: LyMenuModule, imports: [CommonModule, FormsModule, LyCommonModule, LyOverlayModule] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.10", ngImport: i0, type: LyMenuModule, decorators: [{
type: NgModule,
args: [{
imports: [CommonModule, FormsModule, LyCommonModule, LyOverlayModule],
exports: [LyMenu, LyMenuItem, LyMenuTriggerFor, LyMenuOpenOnHover],
declarations: [LyMenu, LyMenuItem, LyMenuTriggerFor, LyMenuOpenOnHover],
}]
}] });
/**
* Generated bundle index. Do not edit.
*/
export { LyMenu, LyMenuItem, LyMenuModule, LyMenuOpenOnHover, LyMenuTriggerFor, STYLES };
//# sourceMappingURL=alyle-ui-menu.mjs.map