carbon-components-angular
Version:
Next generation components
519 lines (507 loc) • 21 kB
JavaScript
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