UNPKG

carbon-components-angular

Version:
365 lines (359 loc) 16.2 kB
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