UNPKG

carbon-components-angular

Version:
303 lines (298 loc) 13.2 kB
import { __awaiter } from 'tslib'; import * as i0 from '@angular/core'; import { Component, ChangeDetectionStrategy, Input, ContentChildren, HostBinding, ViewChild, NgModule } from '@angular/core'; import { autoUpdate, computePosition, flip } from '@floating-ui/dom'; import * as i4 from 'carbon-components-angular/context-menu'; import { ContextMenuItemComponent, ContextMenuModule } from 'carbon-components-angular/context-menu'; import * as i1 from '@angular/common'; import { CommonModule } from '@angular/common'; import * as i2 from 'carbon-components-angular/button'; import { ButtonModule } from 'carbon-components-angular/button'; import * as i3 from 'carbon-components-angular/icon'; import { IconModule } from 'carbon-components-angular/icon'; class MenuButtonComponent { constructor(ngZone, renderer, hostElement, viewContainerRef, changeDetectorRef) { this.ngZone = ngZone; this.renderer = renderer; this.hostElement = hostElement; this.viewContainerRef = viewContainerRef; this.changeDetectorRef = changeDetectorRef; this.menuId = `menu-button-${MenuButtonComponent.menuButtonCounter++}`; this.containerClass = true; this.kind = "primary"; this.size = "lg"; this.menuAlignment = "bottom"; this.buttonTabIndex = "0"; this.disabled = false; this.open = false; this.documentClick = this.handleFocusOut.bind(this); this.subscriptions = []; this._alignment = "bottom"; } // Listen for click & determine if menu should close set projectedMenuItems(itemList) { // Reset in case user dynamically updates menu item this.subscriptions.forEach((sub) => sub === null || sub === void 0 ? void 0 : sub.unsubscribe()); this.subscriptions = []; itemList.forEach((item) => { this.subscriptions.push(item.itemClick.subscribe((clickEvent) => this.handleMenuItemClick(clickEvent))); }); } /** * In case user updates alignment, store initial value. * This allows us to test user passed alignment on each open */ ngOnChanges(changes) { if (changes.menuAlignment) { this._alignment = changes.menuAlignment.currentValue; } } /** * If user has passed in true for open, we dynamically open the menu */ ngAfterViewInit() { if (this.open) { this.open = !this.open; this.toggleMenu(); } } /** * Clean up Floating-ui & subscriptions */ ngOnDestroy() { this.cleanUp(); this.subscriptions.forEach((sub) => sub.unsubscribe()); } /** * As of now, menu button does not support nexted menu, on button click it should close */ handleMenuItemClick(event) { // If event is not type radio/checkbox, we close the menu if (!event.type) { this.toggleMenu(); } } /** * On body click, close the menu * @param event */ handleFocusOut(event) { if (!this.hostElement.nativeElement.contains(event.target)) { this.toggleMenu(); } } /** * Clean up `autoUpdate` if auto alignment is enabled */ cleanUp() { document.removeEventListener("click", this.documentClick); if (this.unmountFloatingElement) { this.menuRef.remove(); this.viewContainerRef.clear(); this.unmountFloatingElement(); } this.unmountFloatingElement = undefined; // On all instances of menu closing, make sure icon direction is correct this.changeDetectorRef.markForCheck(); } /** * Handles emitting open/close event */ toggleMenu() { this.open = !this.open; if (this.open) { // Render the template & append to view const view = this.viewContainerRef.createEmbeddedView(this.menuTemplate); this.menuRef = document.body.appendChild(view.rootNodes[0]); // Assign button width to the menu ref to align with button Object.assign(this.menuRef.style, { width: `${this.referenceElement.nativeElement.clientWidth}px`, top: "0", left: "0" }); // Reset & test alignment every open this.menuAlignment = this._alignment; document.addEventListener("click", this.documentClick); // Listen for events such as scrolling to keep menu aligned this.unmountFloatingElement = autoUpdate(this.referenceElement.nativeElement, this.menuRef, this.recomputePosition.bind(this)); } else { this.cleanUp(); } } roundByDPR(value) { const dpr = window.devicePixelRatio || 1; return Math.round(value * dpr) / dpr; } /** * Compute position of menu */ recomputePosition() { if (this.menuTemplate && this.referenceElement) { // Run outside of angular zone to avoid unnecessary change detection and rely on floating-ui this.ngZone.runOutsideAngular(() => __awaiter(this, void 0, void 0, function* () { const { x, y, placement } = yield computePosition(this.referenceElement.nativeElement, this.menuRef, { placement: this.menuAlignment, strategy: "fixed", middleware: [ flip({ crossAxis: false }) ] }); this.menuAlignment = placement; // Using CSSOM to manipulate CSS to avoid content security policy inline-src // https://github.com/w3c/webappsec-csp/issues/212 Object.assign(this.menuRef.style, { position: "fixed", // Using transform instead of top/left position to improve performance transform: `translate(${this.roundByDPR(x)}px,${this.roundByDPR(y)}px)` }); this.changeDetectorRef.markForCheck(); })); } } } MenuButtonComponent.menuButtonCounter = 0; MenuButtonComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: MenuButtonComponent, deps: [{ token: i0.NgZone }, { token: i0.Renderer2 }, { token: i0.ElementRef }, { token: i0.ViewContainerRef }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); MenuButtonComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.3.0", type: MenuButtonComponent, selector: "cds-menu-button", inputs: { menuId: "menuId", kind: "kind", size: "size", menuAlignment: "menuAlignment", buttonTabIndex: "buttonTabIndex", disabled: "disabled", open: "open", label: "label" }, host: { properties: { "class.cds--menu-button__container": "this.containerClass" } }, queries: [{ propertyName: "projectedMenuItems", predicate: ContextMenuItemComponent }], viewQueries: [{ propertyName: "referenceElement", first: true, predicate: ["reference"], descendants: true, static: true }, { propertyName: "menuTemplate", first: true, predicate: ["menuTemplate"], descendants: true }], usesOnChanges: true, ngImport: i0, template: ` <button #reference class="cds--menu-button__trigger" [ngClass]="{'cds--menu-button__trigger--open': open}" [cdsButton]="kind" [size]="size" [attr.tabindex]="buttonTabIndex" [disabled]="disabled" type="button" [attr.aria-haspopup]="true" [attr.aria-expanded]="open" [attr.aria-controls]="open ? menuId : undefined" (click)="toggleMenu()"> {{label}} <svg cdsIcon="chevron--down" size="16" class="cds--btn__icon"> </svg> </button> <ng-template #menuTemplate> <cds-menu mode="basic" [size]="size" [open]="open" [attr.id]="menuId" [ngClass]="{ 'cds--menu-button__bottom': this.menuAlignment === 'bottom', 'cds--menu-button__bottom-start': this.menuAlignment === 'bottom-start', 'cds--menu-button__bottom-end': this.menuAlignment === 'bottom-end', 'cds--menu-top': this.menuAlignment === 'top', 'cds--menu-top-start': this.menuAlignment === 'top-start', 'cds--menu-top-end': this.menuAlignment === 'top-end' }"> <ng-content select="cds-menu-item, cds-menu-divider"></ng-content> </cds-menu> </ng-template> `, isInline: true, dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.Button, selector: "[cdsButton], [ibmButton]", inputs: ["ibmButton", "cdsButton", "size", "skeleton", "iconOnly", "isExpressive"] }, { kind: "directive", type: i3.IconDirective, selector: "[cdsIcon], [ibmIcon]", inputs: ["ibmIcon", "cdsIcon", "size", "title", "ariaLabel", "ariaLabelledBy", "ariaHidden", "isFocusable"] }, { kind: "component", type: i4.ContextMenuComponent, selector: "cds-menu, cds-context-menu, ibm-context-menu", inputs: ["open", "position", "size"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: MenuButtonComponent, decorators: [{ type: Component, args: [{ selector: "cds-menu-button", template: ` <button #reference class="cds--menu-button__trigger" [ngClass]="{'cds--menu-button__trigger--open': open}" [cdsButton]="kind" [size]="size" [attr.tabindex]="buttonTabIndex" [disabled]="disabled" type="button" [attr.aria-haspopup]="true" [attr.aria-expanded]="open" [attr.aria-controls]="open ? menuId : undefined" (click)="toggleMenu()"> {{label}} <svg cdsIcon="chevron--down" size="16" class="cds--btn__icon"> </svg> </button> <ng-template #menuTemplate> <cds-menu mode="basic" [size]="size" [open]="open" [attr.id]="menuId" [ngClass]="{ 'cds--menu-button__bottom': this.menuAlignment === 'bottom', 'cds--menu-button__bottom-start': this.menuAlignment === 'bottom-start', 'cds--menu-button__bottom-end': this.menuAlignment === 'bottom-end', 'cds--menu-top': this.menuAlignment === 'top', 'cds--menu-top-start': this.menuAlignment === 'top-start', 'cds--menu-top-end': this.menuAlignment === 'top-end' }"> <ng-content select="cds-menu-item, cds-menu-divider"></ng-content> </cds-menu> </ng-template> `, changeDetection: ChangeDetectionStrategy.OnPush }] }], ctorParameters: function () { return [{ type: i0.NgZone }, { type: i0.Renderer2 }, { type: i0.ElementRef }, { type: i0.ViewContainerRef }, { type: i0.ChangeDetectorRef }]; }, propDecorators: { menuId: [{ type: Input }], projectedMenuItems: [{ type: ContentChildren, args: [ContextMenuItemComponent] }], containerClass: [{ type: HostBinding, args: ["class.cds--menu-button__container"] }], kind: [{ type: Input }], size: [{ type: Input }], menuAlignment: [{ type: Input }], buttonTabIndex: [{ type: Input }], disabled: [{ type: Input }], open: [{ type: Input }], label: [{ type: Input }], referenceElement: [{ type: ViewChild, args: ["reference", { static: true }] }], menuTemplate: [{ type: ViewChild, args: ["menuTemplate"] }] } }); class MenuButtonModule { } MenuButtonModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: MenuButtonModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); MenuButtonModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "14.3.0", ngImport: i0, type: MenuButtonModule, declarations: [MenuButtonComponent], imports: [CommonModule, ButtonModule, IconModule, ContextMenuModule], exports: [MenuButtonComponent] }); MenuButtonModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: MenuButtonModule, imports: [CommonModule, ButtonModule, IconModule, ContextMenuModule] }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: MenuButtonModule, decorators: [{ type: NgModule, args: [{ imports: [ CommonModule, ButtonModule, IconModule, ContextMenuModule ], exports: [MenuButtonComponent], declarations: [MenuButtonComponent], providers: [] }] }] }); /** * Generated bundle index. Do not edit. */ export { MenuButtonComponent, MenuButtonModule }; //# sourceMappingURL=carbon-components-angular-menu-button.mjs.map