carbon-components-angular
Version:
Next generation components
339 lines (332 loc) • 14.4 kB
JavaScript
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