UNPKG

@alyle/ui

Version:

Minimal Design, a set of components for Angular

513 lines (508 loc) 23.6 kB
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