UNPKG

carbon-components-angular

Version:
339 lines (332 loc) 14.4 kB
import { __awaiter } from 'tslib'; import * as i0 from '@angular/core'; import { EventEmitter, Component, ChangeDetectionStrategy, Input, ContentChildren, HostBinding, Output, ViewChild, NgModule } from '@angular/core'; import { autoUpdate, computePosition, flip } from '@floating-ui/dom'; import * as i3 from 'carbon-components-angular/context-menu'; import { ContextMenuItemComponent, ContextMenuModule } from 'carbon-components-angular/context-menu'; import * as i1 from 'carbon-components-angular/button'; import { ButtonModule } from 'carbon-components-angular/button'; import * as i2 from 'carbon-components-angular/icon'; import { IconModule } from 'carbon-components-angular/icon'; import { CommonModule } from '@angular/common'; class ComboButtonComponent { constructor(ngZone, renderer, hostElement, viewContainerRef, changeDetectorRef) { this.ngZone = ngZone; this.renderer = renderer; this.hostElement = hostElement; this.viewContainerRef = viewContainerRef; this.changeDetectorRef = changeDetectorRef; this.comboId = `combo-button-${ComboButtonComponent.comboButtonCounter++}`; this.size = "lg"; this.disabled = false; this.menuAlignment = "bottom"; this.tooltipAutoAlign = false; this.tooltipPlacement = "bottom"; this.open = false; this.actionClick = new EventEmitter(); this.comboButtonContainer = true; 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))); }); } get sizeLg() { return this.size === "lg"; } get sizeMd() { return this.size === "md"; } get sizeSm() { return this.size === "sm"; } get ariaOwns() { return this.open ? this.comboId : undefined; } /** * 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(); } /** * On action click, notify user * If the menu is open, close the menu * @param event */ onActionClick(event) { if (this.open) { this.toggleMenu(); } this.actionClick.emit(event); } /** * 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.hostElement.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.hostElement.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.hostElement) { // 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.hostElement.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(); })); } } } ComboButtonComponent.comboButtonCounter = 0; ComboButtonComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: ComboButtonComponent, deps: [{ token: i0.NgZone }, { token: i0.Renderer2 }, { token: i0.ElementRef }, { token: i0.ViewContainerRef }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); ComboButtonComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.3.0", type: ComboButtonComponent, selector: "cds-combo-button", inputs: { comboId: "comboId", size: "size", label: "label", disabled: "disabled", menuAlignment: "menuAlignment", description: "description", tooltipAutoAlign: "tooltipAutoAlign", tooltipPlacement: "tooltipPlacement", open: "open" }, outputs: { actionClick: "actionClick" }, host: { properties: { "class.cds--combo-button__container--open": "this.open", "class.cds--combo-button__container": "this.comboButtonContainer", "class.cds--combo-button__container--lg": "this.sizeLg", "class.cds--combo-button__container--md": "this.sizeMd", "class.cds--combo-button__container--sm": "this.sizeSm", "attr.aria-owns": "this.ariaOwns" } }, queries: [{ propertyName: "projectedMenuItems", predicate: ContextMenuItemComponent }], viewQueries: [{ propertyName: "menuTemplate", first: true, predicate: ["menuTemplate"], descendants: true }], usesOnChanges: true, ngImport: i0, template: ` <div class="cds--combo-button__primary-action" [attr.aria-owns]="open ? comboId : undefined"> <button cdsButton="primary" [size]="size" [attr.title]="label" [disabled]="disabled" type="button" (click)="onActionClick($event)"> {{label}} </button> </div> <cds-icon-button [buttonNgClass]="{ 'cds--combo-button__trigger': true }" [buttonAttributes]="{ 'aria-haspopup': true, 'aria-expanded': open, 'aria-controls': open ? comboId : undefined }" [size]="size" [description]="description" [disabled]="disabled" [autoAlign]="tooltipAutoAlign" [align]="tooltipPlacement" (click)="toggleMenu()"> <svg cdsIcon="chevron--down" size="16"> </svg> </cds-icon-button> <ng-template #menuTemplate> <cds-menu mode="basic" [size]="size" [open]="open" [attr.id]="comboId"> <ng-content select="cds-menu-item, cds-menu-divider"></ng-content> </cds-menu> </ng-template> `, isInline: true, dependencies: [{ kind: "directive", type: i1.Button, selector: "[cdsButton], [ibmButton]", inputs: ["ibmButton", "cdsButton", "size", "skeleton", "iconOnly", "isExpressive"] }, { kind: "component", type: i1.IconButton, selector: "cds-icon-button, ibm-icon-button", inputs: ["buttonNgClass", "buttonAttributes", "buttonId", "kind", "size", "type", "isExpressive", "disabled", "description", "showTooltipWhenDisabled"], outputs: ["click", "focus", "blur", "tooltipClick"] }, { kind: "directive", type: i2.IconDirective, selector: "[cdsIcon], [ibmIcon]", inputs: ["ibmIcon", "cdsIcon", "size", "title", "ariaLabel", "ariaLabelledBy", "ariaHidden", "isFocusable"] }, { kind: "component", type: i3.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: ComboButtonComponent, decorators: [{ type: Component, args: [{ selector: "cds-combo-button", template: ` <div class="cds--combo-button__primary-action" [attr.aria-owns]="open ? comboId : undefined"> <button cdsButton="primary" [size]="size" [attr.title]="label" [disabled]="disabled" type="button" (click)="onActionClick($event)"> {{label}} </button> </div> <cds-icon-button [buttonNgClass]="{ 'cds--combo-button__trigger': true }" [buttonAttributes]="{ 'aria-haspopup': true, 'aria-expanded': open, 'aria-controls': open ? comboId : undefined }" [size]="size" [description]="description" [disabled]="disabled" [autoAlign]="tooltipAutoAlign" [align]="tooltipPlacement" (click)="toggleMenu()"> <svg cdsIcon="chevron--down" size="16"> </svg> </cds-icon-button> <ng-template #menuTemplate> <cds-menu mode="basic" [size]="size" [open]="open" [attr.id]="comboId"> <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: { comboId: [{ type: Input }], projectedMenuItems: [{ type: ContentChildren, args: [ContextMenuItemComponent] }], size: [{ type: Input }], label: [{ type: Input }], disabled: [{ type: Input }], menuAlignment: [{ type: Input }], description: [{ type: Input }], tooltipAutoAlign: [{ type: Input }], tooltipPlacement: [{ type: Input }], open: [{ type: Input }, { type: HostBinding, args: ["class.cds--combo-button__container--open"] }], actionClick: [{ type: Output }], comboButtonContainer: [{ type: HostBinding, args: ["class.cds--combo-button__container"] }], sizeLg: [{ type: HostBinding, args: ["class.cds--combo-button__container--lg"] }], sizeMd: [{ type: HostBinding, args: ["class.cds--combo-button__container--md"] }], sizeSm: [{ type: HostBinding, args: ["class.cds--combo-button__container--sm"] }], ariaOwns: [{ type: HostBinding, args: ["attr.aria-owns"] }], menuTemplate: [{ type: ViewChild, args: ["menuTemplate"] }] } }); class ComboButtonModule { } ComboButtonModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: ComboButtonModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); ComboButtonModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "14.3.0", ngImport: i0, type: ComboButtonModule, declarations: [ComboButtonComponent], imports: [CommonModule, ButtonModule, IconModule, ContextMenuModule], exports: [ComboButtonComponent] }); ComboButtonModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: ComboButtonModule, imports: [CommonModule, ButtonModule, IconModule, ContextMenuModule] }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: ComboButtonModule, decorators: [{ type: NgModule, args: [{ imports: [ CommonModule, ButtonModule, IconModule, ContextMenuModule ], exports: [ComboButtonComponent], declarations: [ComboButtonComponent] }] }] }); /** * Generated bundle index. Do not edit. */ export { ComboButtonComponent, ComboButtonModule }; //# sourceMappingURL=carbon-components-angular-combo-button.mjs.map