UNPKG

@obliczeniowo/elementary

Version:
246 lines (239 loc) 13.4 kB
import * as i0 from '@angular/core'; import { Injectable, EventEmitter, HostBinding, HostListener, Output, Input, Component, ViewChild, NgModule } from '@angular/core'; import * as i1 from '@angular/common'; import { CommonModule } from '@angular/common'; import { DomPortalOutlet, DomPortal } from '@angular/cdk/portal'; import { Subject } from 'rxjs'; import * as i2$1 from '@obliczeniowo/elementary/resize-window'; import * as i2 from '@obliczeniowo/elementary/icons'; import { IconsModule } from '@obliczeniowo/elementary/icons'; import * as i3 from '@obliczeniowo/elementary/text-pipes'; import { TextPipesModule } from '@obliczeniowo/elementary/text-pipes'; import { OverlayModule } from '@angular/cdk/overlay'; class OblMenuTeleportService { appRef; injector; portalOutlet = new Map(); constructor(appRef, injector) { this.appRef = appRef; this.injector = injector; } registerPortalOutlet(outletElement) { const id = Math.random(); this.portalOutlet.set(id, new DomPortalOutlet(outletElement, this.appRef, this.injector)); return id; } unregisterPortalOutlet(id) { const portal = this.portalOutlet.get(id); portal?.dispose(); this.portalOutlet.delete(id); } teleport(id, portal) { if (!this.portalOutlet.get(id)?.hasAttached()) { this.portalOutlet.get(id)?.attach(portal); } } finishTeleportation(id) { this.portalOutlet.get(id)?.detach(); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: OblMenuTeleportService, deps: [{ token: i0.ApplicationRef }, { token: i0.Injector }], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: OblMenuTeleportService, providedIn: 'root' }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: OblMenuTeleportService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [{ type: i0.ApplicationRef }, { type: i0.Injector }] }); class MenuItemComponent { item; translations = {}; noIcon = false; clicked = new EventEmitter(); onClick() { if (!this.item.disabled) { this.clicked.emit(this.item); } } get disabled() { return this.item.disabled; } ngOnChanges(changes) { if (changes.item) { this.noIcon = !this.item.icon; } } stringIcon() { return typeof this.item.icon === 'string'; } iconTemplate() { return this.item.icon; } iconString() { return this.item.icon; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: MenuItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.4", type: MenuItemComponent, isStandalone: false, selector: "obl-menu-item", inputs: { item: "item", translations: "translations" }, outputs: { clicked: "clicked" }, host: { listeners: { "click": "onClick()" }, properties: { "class.no-icon": "this.noIcon", "class.disabled": "this.disabled" } }, usesOnChanges: true, ngImport: i0, template: "@if (item.icon) {\n <ng-container *ngTemplateOutlet=\"stringIcon() ? iconsRef : iconTemplate(); context: { $implicit: item }\"></ng-container>\n}\n\n<div>\n {{ item.text | oblTranslation : translations }}\n</div>\n\n<ng-template #iconsRef>\n <obl-icon [name]=\"iconString()\"></obl-icon>\n</ng-template>\n\n", styles: [":host{display:flex;padding:5px;background-color:var(--menu-background, #d8d8d8);color:var(--default-text-color);cursor:pointer}:host:hover{background-color:var(--menu-hover, #ecebeb)}:host.no-icon div{padding-left:24px}:host.disabled{color:var(--default-disabled-color)}:host.disabled obl-icon{--obl-default-icon-color: var(--default-disabled-color)}obl-icon{margin-right:5px}\n"], dependencies: [{ kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: i2.IconComponent, selector: "obl-icon", inputs: ["name", "width"] }, { kind: "pipe", type: i3.TranslationPipe, name: "oblTranslation" }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: MenuItemComponent, decorators: [{ type: Component, args: [{ selector: 'obl-menu-item', standalone: false, template: "@if (item.icon) {\n <ng-container *ngTemplateOutlet=\"stringIcon() ? iconsRef : iconTemplate(); context: { $implicit: item }\"></ng-container>\n}\n\n<div>\n {{ item.text | oblTranslation : translations }}\n</div>\n\n<ng-template #iconsRef>\n <obl-icon [name]=\"iconString()\"></obl-icon>\n</ng-template>\n\n", styles: [":host{display:flex;padding:5px;background-color:var(--menu-background, #d8d8d8);color:var(--default-text-color);cursor:pointer}:host:hover{background-color:var(--menu-hover, #ecebeb)}:host.no-icon div{padding-left:24px}:host.disabled{color:var(--default-disabled-color)}:host.disabled obl-icon{--obl-default-icon-color: var(--default-disabled-color)}obl-icon{margin-right:5px}\n"] }] }], propDecorators: { item: [{ type: Input, args: [{ required: true }] }], translations: [{ type: Input }], noIcon: [{ type: HostBinding, args: ['class.no-icon'] }], clicked: [{ type: Output }], onClick: [{ type: HostListener, args: ['click'] }], disabled: [{ type: HostBinding, args: ['class.disabled'] }] } }); class MenuComponent { menuTeleportService; resize; elementRef; _opened = false; /** * Open/close menu. When set to false emits extra command 'on-close' only by using command Subject */ set opened(opened) { this._opened = opened; if (!this.opened) { this.command.next('on-close'); } } get opened() { return this._opened; } // target element (by default document.body) target = document.body; // items menu to display items = []; /** * translations for item.text from {@link items} */ translations = {}; /** * emit command when active element was clicked */ clicked = new EventEmitter(); command = new Subject(); parent; position = { x: 0, y: 0 }; size = { width: 0, height: 0 }; id; set content(elemRef) { this.menuTeleportService.teleport(this.id, new DomPortal(elemRef)); } checkParent(target) { if (!this.target) { return false; } do { if (target && this.target === target) { return false; } target = target?.parentElement; } while (target); return true; } targetClicked = (event) => { if (this.checkParent(event.target)) { if (!this.opened) { return; } } this.opened = !!this.items.length && !this.opened; this.position.x = event.pageX; this.position.y = event.pageY; if (this.size.width + this.position.x > this.resize.size.width - 15) { this.position.x = this.resize.size.width - this.size.width - 15; } event.preventDefault(); }; constructor(menuTeleportService, resize, elementRef) { this.menuTeleportService = menuTeleportService; this.resize = resize; this.elementRef = elementRef; this.parent = document.createElement('div'); document.body.appendChild(this.parent); this.parent.classList.add('obl-menu'); this.id = this.menuTeleportService.registerPortalOutlet(this.parent); this.target.addEventListener('contextmenu', this.targetClicked); } ngOnDestroy() { this.menuTeleportService.finishTeleportation(this.id); this.menuTeleportService.unregisterPortalOutlet(this.id); } ngAfterViewChecked() { const dom = document.querySelector('#obl-menu .menu-content')?.getBoundingClientRect(); this.size = { width: dom?.width || 0, height: dom?.height || 0 }; } itemClicked(item) { this.clicked.emit(item.command); this.command.next(item.command); this.opened = false; this.target.removeEventListener('click', this.targetClicked); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: MenuComponent, deps: [{ token: OblMenuTeleportService }, { token: i2$1.OblResizeWindowService }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.4", type: MenuComponent, isStandalone: false, selector: "obl-menu", inputs: { opened: "opened", target: "target", items: "items", translations: "translations" }, outputs: { clicked: "clicked" }, viewQueries: [{ propertyName: "content", first: true, predicate: ["content"], descendants: true }], ngImport: i0, template: "<div #content>\n @if (opened) {\n <div class=\"background\" (click)=\"opened = false\"></div>\n <div\n class=\"menu-content\"\n [style.transform]=\"'translate(' + position.x + 'px,' + position.y + 'px)'\"\n >\n @for(item of items; track item.id) {\n <obl-menu-item\n [item]=\"item\"\n [translations]=\"translations\"\n (clicked)=\"itemClicked($event)\"\n >\n </obl-menu-item>\n }\n </div>\n }\n</div>\n", styles: ["::ng-deep .obl-menu{position:absolute;inset:0;pointer-events:none}.background{position:fixed;inset:0;background-color:#0000;pointer-events:all}:host{max-width:200}.menu-content{width:fit-content;max-width:300px;pointer-events:all}\n"], dependencies: [{ kind: "component", type: MenuItemComponent, selector: "obl-menu-item", inputs: ["item", "translations"], outputs: ["clicked"] }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: MenuComponent, decorators: [{ type: Component, args: [{ selector: 'obl-menu', standalone: false, template: "<div #content>\n @if (opened) {\n <div class=\"background\" (click)=\"opened = false\"></div>\n <div\n class=\"menu-content\"\n [style.transform]=\"'translate(' + position.x + 'px,' + position.y + 'px)'\"\n >\n @for(item of items; track item.id) {\n <obl-menu-item\n [item]=\"item\"\n [translations]=\"translations\"\n (clicked)=\"itemClicked($event)\"\n >\n </obl-menu-item>\n }\n </div>\n }\n</div>\n", styles: ["::ng-deep .obl-menu{position:absolute;inset:0;pointer-events:none}.background{position:fixed;inset:0;background-color:#0000;pointer-events:all}:host{max-width:200}.menu-content{width:fit-content;max-width:300px;pointer-events:all}\n"] }] }], ctorParameters: () => [{ type: OblMenuTeleportService }, { type: i2$1.OblResizeWindowService }, { type: i0.ElementRef }], propDecorators: { opened: [{ type: Input }], target: [{ type: Input }], items: [{ type: Input }], translations: [{ type: Input }], clicked: [{ type: Output }], content: [{ type: ViewChild, args: ['content'] }] } }); class MenuModule { static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: MenuModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.0.4", ngImport: i0, type: MenuModule, declarations: [MenuComponent, MenuItemComponent], imports: [CommonModule, OverlayModule, IconsModule, TextPipesModule], exports: [MenuComponent] }); static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: MenuModule, imports: [CommonModule, OverlayModule, IconsModule, TextPipesModule] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: MenuModule, decorators: [{ type: NgModule, args: [{ declarations: [ MenuComponent, MenuItemComponent ], imports: [ CommonModule, OverlayModule, IconsModule, TextPipesModule ], exports: [ MenuComponent ] }] }] }); /** * Generated bundle index. Do not edit. */ export { MenuComponent, MenuModule }; //# sourceMappingURL=obliczeniowo-elementary-menu.mjs.map