carbon-components-angular
Version:
Next generation components
303 lines (298 loc) • 13.2 kB
JavaScript
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