UNPKG

carbon-components-angular

Version:
519 lines (507 loc) 21 kB
import * as i0 from '@angular/core'; import { Directive, HostBinding, EventEmitter, Component, ChangeDetectionStrategy, Input, Output, ViewChild, HostListener, NgModule } from '@angular/core'; import { PopoverContainer, PopoverModule } from 'carbon-components-angular/popover'; 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'; /** * Host for actions inside a `<cds-ai-label>` popover. Applies `cds--toggletip-actions` * and `cds--ai-label-actions`. * * ```html * <cds-ai-label> * <div cdsAILabelContent> * <p>Explanation text</p> * <div cdsAILabelActions> * <button cdsButton="ghost" size="sm">View details</button> * </div> * </div> * </cds-ai-label> * ``` */ class AILabelActions { constructor() { this.toggletipActions = true; this.aiLabelActions = true; } } AILabelActions.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: AILabelActions, deps: [], target: i0.ɵɵFactoryTarget.Directive }); AILabelActions.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.3.0", type: AILabelActions, selector: "[cdsAILabelActions], [ibmAILabelActions]", host: { properties: { "class.cds--toggletip-actions": "this.toggletipActions", "class.cds--ai-label-actions": "this.aiLabelActions" } }, ngImport: i0 }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: AILabelActions, decorators: [{ type: Directive, args: [{ selector: "[cdsAILabelActions], [ibmAILabelActions]" }] }], propDecorators: { toggletipActions: [{ type: HostBinding, args: ["class.cds--toggletip-actions"] }], aiLabelActions: [{ type: HostBinding, args: ["class.cds--ai-label-actions"] }] } }); /** * Optional marker for the main body region inside `<cds-ai-label>`. Implementing this for semantics only. * The structure also matches that of toggletip, in future we may need this. * * ```html * <cds-ai-label size="md"> * <div cdsAILabelContent> * <p>AI Explained</p> * </div> * <div cdsAILabelActions> * <button cdsButton="ghost" size="sm">View details</button> * </div> * </cds-ai-label> * ``` */ class AILabelContent { } AILabelContent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: AILabelContent, deps: [], target: i0.ɵɵFactoryTarget.Directive }); AILabelContent.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.3.0", type: AILabelContent, selector: "[cdsAILabelContent], [ibmAILabelContent]", ngImport: i0 }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: AILabelContent, decorators: [{ type: Directive, args: [{ selector: "[cdsAILabelContent], [ibmAILabelContent]" }] }] }); /** * Popover/toggletip behavior for `cds-ai-label`, applied to an inner wrapper so * `cds--popover-*` classes are not merged onto the `cds--ai-label` host. */ class AILabelPopoverDirective extends PopoverContainer { constructor(elementRef, ngZone, renderer, changeDetectorRef) { super(elementRef, ngZone, renderer, changeDetectorRef); this.elementRef = elementRef; this.ngZone = ngZone; this.renderer = renderer; this.changeDetectorRef = changeDetectorRef; this.highContrast = true; this.dropShadow = false; } initializeReferences() { this.updateAlignmentClass(this._align); this.bindPopoverRefs(); this.handleChange(this.isOpen); } ngOnChanges(changes) { const originalState = this.isOpen; this.handleChange(false); if (changes.autoAlign && !changes.autoAlign.firstChange) { this.popoverContentRef?.setAttribute("style", ""); this.bindPopoverRefs(); } this.handleChange(originalState); } bindPopoverRefs() { const host = this.elementRef.nativeElement; const panel = host.querySelector(":scope > span.cds--popover"); if (!panel) { return; } this.popoverContentRef = panel.querySelector(":scope > span.cds--popover-content"); this.caretRef = this.resolveCaretRef(panel); } resolveCaretRef(panel) { if (this.autoAlign) { return panel.querySelector("span.cds--popover-content > span.cds--popover-caret.cds--popover--auto-align"); } return panel.querySelector(":scope > span.cds--popover-caret"); } } AILabelPopoverDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: AILabelPopoverDirective, deps: [{ token: i0.ElementRef }, { token: i0.NgZone }, { token: i0.Renderer2 }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Directive }); AILabelPopoverDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.3.0", type: AILabelPopoverDirective, selector: "[cdsAILabelPopover]", usesInheritance: true, usesOnChanges: true, ngImport: i0 }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: AILabelPopoverDirective, decorators: [{ type: Directive, args: [{ selector: "[cdsAILabelPopover]" }] }], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: i0.NgZone }, { type: i0.Renderer2 }, { type: i0.ChangeDetectorRef }]; } }); /** * AI-branded toggletip control (`cds-ai-label`). Renders an "AI" badge that opens a * popover; projected content and optional actions use `ng-content`. * * Get started with importing the module: * * ```typescript * import { AILabelModule } from 'carbon-components-angular'; * ``` * * ```html * <cds-ai-label size="md"> * <div> * <p>AI Explained</p> * <h2>84%</h2> * <p>Confidence score</p> * </div> * <div cdsAILabelActions> * <button cdsButton="ghost" size="sm">View details</button> * </div> * </cds-ai-label> * ``` * * `[cdsAILabelActions]` adds `cds--toggletip-actions` and `cds--ai-label-actions` * to its host. Place it as a **sibling** of the body content, both direct * children of `<cds-ai-label>`. `[cdsAILabelContent]` is an optional marker; the * `cds--ai-label-content` / `cds--toggletip-content` classes come from this * component’s template. * * [See demo](../../?path=/story/components-ai-label--default) */ class AILabelComponent { constructor(elementRef) { this.elementRef = elementRef; this.aiLabelClass = true; /** * Show caret at the alignment position. */ this.caret = true; /** * Enable drop shadow around the popover container. */ this.dropShadow = false; /** * Enable high contrast for popover container. */ this.highContrast = true; /** * **Experimental**: Use floating-ui to position the tooltip. */ this.autoAlign = false; /** * Whether the callout is open. */ this.isOpen = false; /** * Emits when the callout is closed. */ this.onClose = new EventEmitter(); /** * Emits when the callout is opened. */ this.onOpen = new EventEmitter(); /** * Emits when `isOpen` changes (two-way binding). */ this.isOpenChange = new EventEmitter(); /** * Unique id used to associate the trigger button with the popover panel * via `aria-controls` / `id`. */ this.id = `ai-label-${AILabelComponent.labelCounter++}`; /** * Text inside the AI badge. */ this.aiText = "AI"; /** * Set badge shape: `"default"` (circular) or `"inline"` (pill, optional `textLabel`). */ this.kind = "default"; /** * Set badge size */ this.size = "xs"; /** * When `true`, shows the revert icon instead of the badge (AI-generated value * is active and can be reverted). */ this.revertActive = false; /** * Accessible label / tooltip for the revert icon button. */ this.revertLabel = "Revert to AI input"; /** * `aria-label` for the AI badge trigger (combined with `aiText` in `computedAriaLabel`). */ this.ariaLabel = "Show information"; /** * Emitted when the revert icon is clicked. */ this.revertClick = new EventEmitter(); this.documentClick = this.handleOutsideClick.bind(this); } get revertClass() { return this.revertActive; } /** * Horizontal shift along the alignment axis when `autoAlign` is on, matching * React `AILabel` (`alignmentAxisOffset={isSmallIcon ? -24 : 0}` on `Toggletip`). */ get alignmentAxisOffset() { return ["mini", "2xs", "xs"].includes(this.size) ? -24 : 0; } onPopoverIsOpenChange(open) { this.isOpen = open; this.isOpenChange.emit(open); } get triggerClasses() { return { "cds--toggletip-button": true, "cds--ai-label__button": true, [`cds--ai-label__button--${this.size}`]: true, [`cds--ai-label__button--${this.kind}`]: true, "cds--ai-label__button--inline-with-content": this.kind === "inline" && !!this.textLabel }; } /** * Trigger `aria-label`: `"${aiText} ${ariaLabel}"`, or * `"${aiText} ${textLabel}"` when `kind` is `"inline"` and `textLabel` is set. */ get computedAriaLabel() { const suffix = (this.kind === "inline" && this.textLabel) ? this.textLabel : this.ariaLabel; return `${this.aiText} ${suffix}`; } ngAfterViewInit() { if (this.isOpen) { document.addEventListener("click", this.documentClick); } } ngOnChanges(changes) { if (changes.revertActive && !changes.revertActive.firstChange && changes.revertActive.currentValue) { this.isOpen = false; document.removeEventListener("click", this.documentClick); } } ngOnDestroy() { document.removeEventListener("click", this.documentClick); } onTriggerClick(event) { const opening = !this.isOpen; if (opening) { document.addEventListener("click", this.documentClick); } else { document.removeEventListener("click", this.documentClick); } this.aiLabelPopover?.handleChange(opening, event); } onRevertButtonClick(event) { this.revertClick.emit(event); } hostkeys(event) { if (this.isOpen && event.key === "Escape") { event.stopPropagation(); document.removeEventListener("click", this.documentClick); this.aiLabelPopover?.handleChange(false, event); } } /** * Dismisses the popover when a click lands outside the host element. */ handleOutsideClick(event) { if (!this.elementRef.nativeElement.contains(event.target)) { this.aiLabelPopover?.handleChange(false, event); document.removeEventListener("click", this.documentClick); } } } AILabelComponent.labelCounter = 0; AILabelComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: AILabelComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component }); AILabelComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.3.0", type: AILabelComponent, selector: "cds-ai-label, ibm-ai-label", inputs: { align: "align", caret: "caret", dropShadow: "dropShadow", highContrast: "highContrast", autoAlign: "autoAlign", isOpen: "isOpen", id: "id", aiText: "aiText", textLabel: "textLabel", kind: "kind", size: "size", revertActive: "revertActive", revertLabel: "revertLabel", ariaLabel: "ariaLabel" }, outputs: { onClose: "onClose", onOpen: "onOpen", isOpenChange: "isOpenChange", revertClick: "revertClick" }, host: { listeners: { "keyup": "hostkeys($event)" }, properties: { "class.cds--ai-label": "this.aiLabelClass", "class.cds--ai-label--revert": "this.revertClass" } }, viewQueries: [{ propertyName: "aiLabelPopover", first: true, predicate: ["aiLabelPopoverHost"], descendants: true, read: AILabelPopoverDirective }], usesOnChanges: true, ngImport: i0, template: ` <ng-container *ngIf="!revertActive"> <span #aiLabelPopoverHost cdsAILabelPopover class="cds--toggletip" [isOpen]="isOpen" (isOpenChange)="onPopoverIsOpenChange($event)" (onOpen)="onOpen.emit($event)" (onClose)="onClose.emit($event)" [align]="align" [caret]="caret" [dropShadow]="dropShadow" [highContrast]="highContrast" [autoAlign]="autoAlign" [alignmentAxisOffset]="alignmentAxisOffset"> <button type="button" [attr.aria-label]="computedAriaLabel" [attr.aria-expanded]="isOpen" [attr.aria-controls]="id" [ngClass]="triggerClasses" (click)="onTriggerClick($event)"> <span class="cds--ai-label__text">{{aiText}}</span> <span *ngIf="kind === 'inline' && textLabel" class="cds--ai-label__additional-text">{{textLabel}}</span> </button> <span [id]="id" class="cds--popover" aria-live="polite"> <span class="cds--popover-content cds--ai-label-content"> <div class="cds--toggletip-content"> <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> </span> </span> </ng-container> <cds-icon-button *ngIf="revertActive" kind="ghost" size="sm" [description]="revertLabel" [autoAlign]="autoAlign" [buttonAttributes]="{ 'aria-label': revertLabel }" (click)="onRevertButtonClick($event)"> <svg cdsIcon="undo" size="16"></svg> </cds-icon-button> `, isInline: true, dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i2.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: i3.IconDirective, selector: "[cdsIcon], [ibmIcon]", inputs: ["ibmIcon", "cdsIcon", "size", "title", "ariaLabel", "ariaLabelledBy", "ariaHidden", "isFocusable"] }, { kind: "directive", type: AILabelPopoverDirective, selector: "[cdsAILabelPopover]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: AILabelComponent, decorators: [{ type: Component, args: [{ selector: "cds-ai-label, ibm-ai-label", changeDetection: ChangeDetectionStrategy.OnPush, template: ` <ng-container *ngIf="!revertActive"> <span #aiLabelPopoverHost cdsAILabelPopover class="cds--toggletip" [isOpen]="isOpen" (isOpenChange)="onPopoverIsOpenChange($event)" (onOpen)="onOpen.emit($event)" (onClose)="onClose.emit($event)" [align]="align" [caret]="caret" [dropShadow]="dropShadow" [highContrast]="highContrast" [autoAlign]="autoAlign" [alignmentAxisOffset]="alignmentAxisOffset"> <button type="button" [attr.aria-label]="computedAriaLabel" [attr.aria-expanded]="isOpen" [attr.aria-controls]="id" [ngClass]="triggerClasses" (click)="onTriggerClick($event)"> <span class="cds--ai-label__text">{{aiText}}</span> <span *ngIf="kind === 'inline' && textLabel" class="cds--ai-label__additional-text">{{textLabel}}</span> </button> <span [id]="id" class="cds--popover" aria-live="polite"> <span class="cds--popover-content cds--ai-label-content"> <div class="cds--toggletip-content"> <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> </span> </span> </ng-container> <cds-icon-button *ngIf="revertActive" kind="ghost" size="sm" [description]="revertLabel" [autoAlign]="autoAlign" [buttonAttributes]="{ 'aria-label': revertLabel }" (click)="onRevertButtonClick($event)"> <svg cdsIcon="undo" size="16"></svg> </cds-icon-button> ` }] }], ctorParameters: function () { return [{ type: i0.ElementRef }]; }, propDecorators: { aiLabelClass: [{ type: HostBinding, args: ["class.cds--ai-label"] }], revertClass: [{ type: HostBinding, args: ["class.cds--ai-label--revert"] }], align: [{ type: Input }], caret: [{ type: Input }], dropShadow: [{ type: Input }], highContrast: [{ type: Input }], autoAlign: [{ type: Input }], isOpen: [{ type: Input }], onClose: [{ type: Output }], onOpen: [{ type: Output }], isOpenChange: [{ type: Output }], id: [{ type: Input }], aiText: [{ type: Input }], textLabel: [{ type: Input }], kind: [{ type: Input }], size: [{ type: Input }], revertActive: [{ type: Input }], revertLabel: [{ type: Input }], ariaLabel: [{ type: Input }], revertClick: [{ type: Output }], aiLabelPopover: [{ type: ViewChild, args: ["aiLabelPopoverHost", { read: AILabelPopoverDirective }] }], hostkeys: [{ type: HostListener, args: ["keyup", ["$event"]] }] } }); /** * Copyright IBM Corp. 2024, 2026 * * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. */ class AILabelModule { } AILabelModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: AILabelModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); AILabelModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "14.3.0", ngImport: i0, type: AILabelModule, declarations: [AILabelComponent, AILabelPopoverDirective, AILabelContent, AILabelActions], imports: [CommonModule, ButtonModule, IconModule, PopoverModule], exports: [AILabelComponent, AILabelContent, AILabelActions] }); AILabelModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: AILabelModule, imports: [CommonModule, ButtonModule, IconModule, PopoverModule] }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: AILabelModule, decorators: [{ type: NgModule, args: [{ declarations: [ AILabelComponent, AILabelPopoverDirective, AILabelContent, AILabelActions ], exports: [ AILabelComponent, AILabelContent, AILabelActions ], imports: [ CommonModule, ButtonModule, IconModule, PopoverModule ] }] }] }); /** * Generated bundle index. Do not edit. */ export { AILabelActions, AILabelComponent, AILabelContent, AILabelModule }; //# sourceMappingURL=carbon-components-angular-ai-label.mjs.map