carbon-components-angular
Version:
Next generation components
365 lines (359 loc) • 16.2 kB
JavaScript
import { __awaiter } from 'tslib';
import * as i0 from '@angular/core';
import { EventEmitter, Directive, Input, Output, HostBinding, Component, ViewChild, NgModule } from '@angular/core';
import { autoUpdate, computePosition, offset, flip, arrow } from '@floating-ui/dom';
import * as i1 from '@angular/common';
import { CommonModule } from '@angular/common';
class PopoverContainer {
constructor(elementRef, ngZone, renderer, changeDetectorRef) {
this.elementRef = elementRef;
this.ngZone = ngZone;
this.renderer = renderer;
this.changeDetectorRef = changeDetectorRef;
this._align = "bottom";
this.alignmentClassPrefix = "cds--popover--";
/**
* Emits an event when the dialog is closed
*/
this.onClose = new EventEmitter();
/**
* Emits an event when the dialog is opened
*/
this.onOpen = new EventEmitter();
/**
* Emits an event when the state of `isOpen` changes. Allows `isOpen` to be double bound
*/
this.isOpenChange = new EventEmitter();
/**
* Show caret at the alignment position
*/
this.caret = true;
/**
* Enable drop shadow around the popover container
*/
this.dropShadow = true;
/**
* Enable high contrast for popover container
*/
this.highContrast = false;
/**
* **Experimental**: Use floating-ui to position the tooltip
* This is not toggleable - should be assigned once
*/
this.autoAlign = false;
this.containerClass = true;
this.isOpen = false;
}
/**
* Set alignment of popover
* As of v5, `oldPlacements` are now deprecated in favor of Placements
*
* When `autoAlign` is set to `true`, alignment may change for best placement
*/
set align(alignment) {
// If alignment is not passed, the default value will be `undefined`.
if (!alignment) {
return;
}
const previousAlignment = this._align;
switch (alignment) {
case "top-left":
this._align = "top-start";
break;
case "top-right":
this._align = "top-end";
break;
case "bottom-left":
this._align = "bottom-start";
break;
case "bottom-right":
this._align = "bottom-end";
break;
case "left-top":
this._align = "left-start";
break;
case "left-bottom":
this._align = "left-end";
break;
case "right-top":
this._align = "right-start";
break;
case "right-bottom":
this._align = "right-end";
break;
default:
this._align = alignment;
break;
}
this.updateAlignmentClass(this._align, previousAlignment);
}
/**
* Handles emitting open/close event
* @param open - Is the popover container open
* @param event - Event
*/
handleChange(open, event) {
// We only emit the event when parameter has an event to keep existing behavior
if ((this.isOpen !== open) && event) {
this.isOpenChange.emit(open);
}
if (open) {
if (event) {
this.onOpen.emit(event);
}
// when auto alignment is enabled, use auto update to set the placement for the element
if (this.autoAlign) {
if (this.caretRef) {
// Get caret offset/height property
// Getting computed styles once every open, otherwise expensive.
const computedStyle = getComputedStyle(this.caretRef);
const offset = computedStyle.getPropertyValue("--cds-popover-offset");
const height = computedStyle.getPropertyValue("--cds-popover-caret-height");
this.caretOffset = ((offset === null || offset === void 0 ? void 0 : offset.includes("px")) ? Number(offset.split("px", 1)[0]) : Number(offset.split("rem", 1)[0]) * 16) || 10;
this.caretHeight = ((height === null || height === void 0 ? void 0 : height.includes("px")) ? Number(height.split("px", 1)[0]) : Number(height.split("rem", 1)[0]) * 16) || 6;
}
if (this.elementRef.nativeElement && this.popoverContentRef) {
this.unmountFloatingElement = autoUpdate(this.elementRef.nativeElement, this.popoverContentRef, this.recomputePosition.bind(this));
}
}
}
else {
this.cleanUp();
if (event) {
this.onClose.emit(event);
}
}
this.isOpen = open;
this.changeDetectorRef.markForCheck();
}
roundByDPR(value) {
const dpr = window.devicePixelRatio || 1;
return Math.round(value * dpr) / dpr;
}
/**
* Compute position of tooltip when autoAlign is enabled
*/
recomputePosition() {
// 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, middlewareData } = yield computePosition(this.elementRef.nativeElement, this.popoverContentRef, {
placement: this._align,
strategy: "fixed",
middleware: [
offset(this.caretOffset),
flip({ fallbackAxisSideDirection: "start" }),
arrow({ element: this.caretRef })
]
});
const previousAlignment = this._align;
this._align = placement;
this.updateAlignmentClass(this._align, previousAlignment);
// Using CSSOM to manipulate CSS to avoid content security policy inline-src
// https://github.com/w3c/webappsec-csp/issues/212
Object.assign(this.popoverContentRef.style, {
position: "fixed",
top: "0",
left: "0",
// Using transform instead of top/left position to improve performance
transform: `translate(${this.roundByDPR(x)}px,${this.roundByDPR(y)}px)`
});
if (middlewareData.arrow) {
const { x: arrowX, y: arrowY } = middlewareData.arrow;
const staticSide = {
top: "bottom",
right: "left",
bottom: "top",
left: "right"
}[placement.split("-")[0]];
this.caretRef.style.left = arrowX != null ? `${arrowX}px` : "";
this.caretRef.style.top = arrowY != null ? `${arrowY}px` : "";
this.caretRef.style.right = "";
this.caretRef.style.bottom = "";
if (staticSide) {
this.caretRef.style[staticSide] = `${-this.caretHeight}px`;
}
}
}));
}
/**
* Close the popover and reopen it with updated values without emitting an event
* @param changes
*/
ngOnChanges(changes) {
// Close and reopen the popover, handle alignment/programmatic open/close
const originalState = this.isOpen;
this.handleChange(false);
// Ignore first change since content is not initialized
if (changes.autoAlign && !changes.autoAlign.firstChange) {
// Reset the inline styles
this.popoverContentRef = this.elementRef.nativeElement.querySelector(".cds--popover-content");
this.popoverContentRef.setAttribute("style", "");
this.caretRef = this.elementRef.nativeElement.querySelector("span.cds--popover-caret");
}
this.handleChange(originalState);
}
/**
* Handle initialization of element
*/
ngAfterViewInit() {
this.initializeReferences();
}
initializeReferences() {
this.updateAlignmentClass(this._align);
// Initialize html references since they will not change and are required for popover components
this.popoverContentRef = this.elementRef.nativeElement.querySelector(".cds--popover-content");
this.caretRef = this.elementRef.nativeElement.querySelector("span.cds--popover-caret");
// Handle initial isOpen
this.handleChange(this.isOpen);
}
/**
* Clean up
*/
ngOnDestroy() {
this.cleanUp();
}
/**
* Clean up `autoUpdate` if auto alignment is enabled
*/
cleanUp() {
if (this.unmountFloatingElement) {
this.unmountFloatingElement();
}
this.unmountFloatingElement = undefined;
}
/**
* Replace existing previous alignment class with new
* @param previousAlignment
*/
updateAlignmentClass(newAlignment, previousAlignment) {
if (this.elementRef.nativeElement && previousAlignment !== newAlignment) {
const regexp = new RegExp("right|top|left|bottom");
// Since we are constantly switching, it's safer to delete all matching class names
this.elementRef.nativeElement.classList.forEach(className => {
if (regexp.test(className)) {
this.renderer.removeClass(this.elementRef.nativeElement, `${className}`);
}
});
this.renderer.addClass(this.elementRef.nativeElement, `${this.alignmentClassPrefix}${newAlignment}`);
}
}
}
PopoverContainer.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: PopoverContainer, deps: [{ token: i0.ElementRef }, { token: i0.NgZone }, { token: i0.Renderer2 }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Directive });
PopoverContainer.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.3.0", type: PopoverContainer, selector: "[cdsPopover], [ibmPopover]", inputs: { align: "align", caret: "caret", dropShadow: "dropShadow", highContrast: "highContrast", autoAlign: "autoAlign", isOpen: "isOpen" }, outputs: { onClose: "onClose", onOpen: "onOpen", isOpenChange: "isOpenChange" }, host: { properties: { "class.cds--popover--caret": "this.caret", "class.cds--popover--drop-shadow": "this.dropShadow", "class.cds--popover--high-contrast": "this.highContrast", "class.cds--popover--auto-align": "this.autoAlign", "class.cds--popover-container": "this.containerClass", "class.cds--popover--open": "this.isOpen" } }, usesOnChanges: true, ngImport: i0 });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: PopoverContainer, decorators: [{
type: Directive,
args: [{
selector: "[cdsPopover], [ibmPopover]"
}]
}], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: i0.NgZone }, { type: i0.Renderer2 }, { type: i0.ChangeDetectorRef }]; }, propDecorators: { align: [{
type: Input
}], onClose: [{
type: Output
}], onOpen: [{
type: Output
}], isOpenChange: [{
type: Output
}], caret: [{
type: HostBinding,
args: ["class.cds--popover--caret"]
}, {
type: Input
}], dropShadow: [{
type: HostBinding,
args: ["class.cds--popover--drop-shadow"]
}, {
type: Input
}], highContrast: [{
type: HostBinding,
args: ["class.cds--popover--high-contrast"]
}, {
type: Input
}], autoAlign: [{
type: HostBinding,
args: ["class.cds--popover--auto-align"]
}, {
type: Input
}], containerClass: [{
type: HostBinding,
args: ["class.cds--popover-container"]
}], isOpen: [{
type: Input
}, {
type: HostBinding,
args: ["class.cds--popover--open"]
}] } });
/**
* [See demo](../../?path=/story/components-popover--basic)
*/
class PopoverContent {
constructor(changeDetectorRef) {
this.changeDetectorRef = changeDetectorRef;
this.popoverClass = true;
this.autoAlign = false;
}
ngAfterViewInit() {
if (this.popoverContent) {
// Check we are in a popover with autoAlign enabled
this.autoAlign = !!this.popoverContent.nativeElement.closest(".cds--popover--auto-align");
// Run change detection manually to resolve ExpressionHasChanged
this.changeDetectorRef.detectChanges();
}
}
}
PopoverContent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: PopoverContent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
PopoverContent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.3.0", type: PopoverContent, selector: "cds-popover-content, ibm-popover-content", host: { properties: { "class.cds--popover": "this.popoverClass" } }, viewQueries: [{ propertyName: "popoverContent", first: true, predicate: ["content"], descendants: true }], ngImport: i0, template: `
<span class="cds--popover-content" #content>
<div>
<ng-content></ng-content>
</div>
<span *ngIf="autoAlign" class="cds--popover-caret cds--popover--auto-align"></span>
</span>
<span *ngIf="!autoAlign" class="cds--popover-caret"></span>
`, isInline: true, dependencies: [{ kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: PopoverContent, decorators: [{
type: Component,
args: [{
selector: "cds-popover-content, ibm-popover-content",
template: `
<span class="cds--popover-content" #content>
<div>
<ng-content></ng-content>
</div>
<span *ngIf="autoAlign" class="cds--popover-caret cds--popover--auto-align"></span>
</span>
<span *ngIf="!autoAlign" class="cds--popover-caret"></span>
`
}]
}], ctorParameters: function () { return [{ type: i0.ChangeDetectorRef }]; }, propDecorators: { popoverClass: [{
type: HostBinding,
args: ["class.cds--popover"]
}], popoverContent: [{
type: ViewChild,
args: ["content"]
}] } });
class PopoverModule {
}
PopoverModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: PopoverModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
PopoverModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "14.3.0", ngImport: i0, type: PopoverModule, declarations: [PopoverContainer,
PopoverContent], imports: [CommonModule], exports: [PopoverContainer,
PopoverContent] });
PopoverModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: PopoverModule, imports: [CommonModule] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: PopoverModule, decorators: [{
type: NgModule,
args: [{
declarations: [
PopoverContainer,
PopoverContent
],
exports: [
PopoverContainer,
PopoverContent
],
imports: [CommonModule]
}]
}] });
/**
* Generated bundle index. Do not edit.
*/
export { PopoverContainer, PopoverContent, PopoverModule };
//# sourceMappingURL=carbon-components-angular-popover.mjs.map