UNPKG

@progress/kendo-angular-tooltip

Version:

Kendo UI Tooltip for Angular - A highly customizable and easily themeable tooltip from the creators developers trust for professional Angular components.

472 lines (463 loc) 19.2 kB
/**----------------------------------------------------------------------------------------- * Copyright © 2025 Progress Software Corporation. All rights reserved. * Licensed under commercial license. See LICENSE.md in the project root for more information *-------------------------------------------------------------------------------------------*/ /* eslint-disable @typescript-eslint/no-explicit-any */ import { Component, ContentChild, ElementRef, EventEmitter, HostBinding, Input, isDevMode, NgZone, Output, Renderer2, ViewChild } from '@angular/core'; import { LocalizationService, L10N_PREFIX } from '@progress/kendo-angular-l10n'; import { Subscription } from 'rxjs'; import { validatePackage } from '@progress/kendo-licensing'; import { packageMetadata } from '../package-metadata'; import { ERRORS } from '../constants'; import { PopoverTitleTemplateDirective } from './template-directives/title-template.directive'; import { PopoverBodyTemplateDirective } from './template-directives/body-template.directive'; import { PopoverActionsTemplateDirective } from './template-directives/actions-template.directive'; import { Keys } from '@progress/kendo-angular-common'; import { take } from 'rxjs/operators'; import { getAllFocusableChildren, getFirstAndLastFocusable, getId } from '../utils'; import { NgIf, NgStyle, NgClass, NgTemplateOutlet } from '@angular/common'; import * as i0 from "@angular/core"; import * as i1 from "@progress/kendo-angular-l10n"; /** * Represents the [Kendo UI Popover component for Angular]({% slug overview_popover %}). * Used to display additional information that is related to a target element. * * @example * ```ts-no-run * <kendo-popover> * <ng-template kendoPopoverTitleTemplate>Foo Title</ng-template> * <ng-template kendoPopoverBodyTemplate>Foo Body</ng-template> * <ng-template kendoPopoverActionsTemplate>Foo Actions</ng-template> * </kendo-popover> * ``` */ export class PopoverComponent { localization; renderer; element; zone; /** * @hidden */ anchor; /** * Specifies the position of the Popover in relation to its anchor element. [See example]({% slug positioning_popover %}) * * The possible options are: * `top` * `bottom` * `right` (Default) * `left` */ position = 'right'; /** * Specifies the distance from the Popover to its anchor element in pixels. * * @default `6` */ set offset(value) { this._offset = value; } get offset() { const calloutBuffer = 14; return this.callout ? calloutBuffer + this._offset : this._offset; } /** * Determines the width of the popover. Numeric values are treated as pixels. * @default 'auto' */ set width(value) { this._width = typeof value === 'number' ? `${value}px` : value; } get width() { return this._width; } /** * Determines the height of the popover. Numeric values are treated as pixels. * @default 'auto' */ set height(value) { this._height = typeof value === 'number' ? `${value}px` : value; } get height() { return this._height; } /** * @hidden */ direction; /** * Specifies the main header text of the Popover. * * If a `titleTemplate` is provided it would take precedence over the title. */ title; /** * @hidden * Specifies the secondary header text of the Popover. * * If a `titleTemplate` is provided it would take precedence over the subtitle. */ subtitle; /** * Represents the text that will be rendered in the Popover body section. * * If a `bodyTemplate` is provided it would take precedence over this text. */ body; /** * Determines whether a callout will be rendered along the Popover. [See example]({% slug callout_popover %}) * * @default true */ callout = true; /** * Enables and configures the Popover animation. [See example]({% slug animations_popover %}) * * The possible options are: * * * `boolean`&mdash;Enables the default animation * * `PopoverAnimation`&mdash;A configuration object which allows setting the `direction`, `duration` and `type` of the animation. * * @default false */ animation = false; /** * Defines a callback function which returns custom data passed to the Popover templates. * It exposes the current anchor element as an argument. [See example](slug:templates_popover#toc-passing-data-to-templates) */ set templateData(fn) { if (isDevMode && typeof fn !== 'function') { throw new Error(`${ERRORS.templateData} ${JSON.stringify(fn)}.`); } this._templateData = fn; } get templateData() { return this._templateData; } /** * @hidden * Determines the visibility of the Popover. */ visible = false; /** * @hidden */ get isHidden() { return !this.visible; } /** * @hidden */ get hasAttributeHidden() { return !this.visible; } /** * Fires before the Popover is about to be shown ([see example]({% slug events_popover %})). * The event is preventable. If canceled, the Popover will not be displayed. [See example]({% slug events_popover %}) */ show = new EventEmitter(); /** * Fires after the Popover has been shown and the animation has ended. [See example]({% slug events_popover %}) */ shown = new EventEmitter(); /** * Fires when the Popover is about to be hidden ([see example]({% slug events_popover %})). * The event is preventable. If canceled, the Popover will remain visible. */ hide = new EventEmitter(); /** * Fires after the Popover has been hidden and the animation has ended. [See example]({% slug events_popover %}) */ hidden = new EventEmitter(); /** * @hidden */ closeOnKeyDown = new EventEmitter(); /** * @hidden */ popoverWrapper; /** * @hidden */ titleTemplateWrapper; /** * @hidden */ bodyTemplateWrapper; /** * @hidden */ titleTemplate; /** * @hidden */ bodyTemplate; /** * @hidden */ actionsTemplate; /** * @hidden */ contextData; /** * @hidden */ _width = 'auto'; /** * @hidden */ _height = 'auto'; /** * @hidden */ popoverId = ''; _offset = 6; subs = new Subscription(); constructor(localization, renderer, element, zone) { this.localization = localization; this.renderer = renderer; this.element = element; this.zone = zone; validatePackage(packageMetadata); } ngOnInit() { this.popoverId = getId('k-popover'); this.subs.add(this.localization.changes.subscribe(({ rtl }) => { this.direction = rtl ? 'rtl' : 'ltr'; })); this.subs.add(this.renderer.listen(this.element.nativeElement, 'keydown', event => this.onKeyDown(event))); } ngAfterViewInit() { this.zone.onStable.pipe(take(1)).subscribe(() => { if (this.visible) { const wrapper = this.popoverWrapper.nativeElement; const focusablePopoverChildren = getAllFocusableChildren(wrapper); if (focusablePopoverChildren.length > 0) { focusablePopoverChildren[0].focus(); } this.setAriaAttributes(wrapper, focusablePopoverChildren); } }); } ngOnDestroy() { this.subs.unsubscribe(); } /** * @hidden */ getCalloutPosition() { switch (this.position) { case 'top': return { 'k-callout-s': true }; case 'bottom': return { 'k-callout-n': true }; case 'left': return { 'k-callout-e': true }; case 'right': return { 'k-callout-w': true }; default: return { 'k-callout-s': true }; } } /** * @hidden */ onKeyDown(event) { const keyCode = event.keyCode; const target = event.target; if (keyCode === Keys.Tab) { this.keepFocusWithinComponent(target, event); } if (keyCode === Keys.Escape) { this.closeOnKeyDown.emit(); } } _templateData = () => null; keepFocusWithinComponent(target, event) { const wrapper = this.popoverWrapper.nativeElement; const [firstFocusable, lastFocusable] = getFirstAndLastFocusable(wrapper); const tabAfterLastFocusable = !event.shiftKey && target === lastFocusable; const shiftTabAfterFirstFocusable = event.shiftKey && target === firstFocusable; if (tabAfterLastFocusable) { event.preventDefault(); firstFocusable.focus(); } if (shiftTabAfterFirstFocusable) { event.preventDefault(); lastFocusable.focus(); } } setAriaAttributes(wrapper, focusablePopoverChildren) { if (this.titleTemplate) { const titleRef = this.titleTemplateWrapper.nativeElement; const focusableHeaderChildren = getAllFocusableChildren(titleRef).length > 0; if (focusableHeaderChildren) { const headerId = getId('k-popover-header', 'popoverTitle'); this.renderer.setAttribute(titleRef, 'id', headerId); this.renderer.setAttribute(wrapper, 'aria-labelledby', headerId); } } if (this.bodyTemplate) { const bodyRef = this.bodyTemplateWrapper.nativeElement; const focusableBodyChildren = getAllFocusableChildren(bodyRef).length > 0; if (focusableBodyChildren) { const bodyId = getId('k-popover-body', 'popoverBody'); this.renderer.setAttribute(bodyRef, 'id', bodyId); this.renderer.setAttribute(wrapper, 'aria-describedby', bodyId); } } this.renderer.setAttribute(wrapper, 'id', this.popoverId); this.renderer.setAttribute(wrapper, 'role', focusablePopoverChildren.length > 0 ? 'dialog' : 'tooltip'); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: PopoverComponent, deps: [{ token: i1.LocalizationService }, { token: i0.Renderer2 }, { token: i0.ElementRef }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: PopoverComponent, isStandalone: true, selector: "kendo-popover", inputs: { position: "position", offset: "offset", width: "width", height: "height", title: "title", subtitle: "subtitle", body: "body", callout: "callout", animation: "animation", templateData: "templateData" }, outputs: { show: "show", shown: "shown", hide: "hide", hidden: "hidden", closeOnKeyDown: "closeOnKeyDown" }, host: { properties: { "attr.dir": "this.direction", "class.k-hidden": "this.isHidden", "attr.aria-hidden": "this.hasAttributeHidden", "style.width": "this._width", "style.height": "this._height" } }, providers: [ LocalizationService, { provide: L10N_PREFIX, useValue: 'kendo.popover' } ], queries: [{ propertyName: "titleTemplate", first: true, predicate: PopoverTitleTemplateDirective, descendants: true }, { propertyName: "bodyTemplate", first: true, predicate: PopoverBodyTemplateDirective, descendants: true }, { propertyName: "actionsTemplate", first: true, predicate: PopoverActionsTemplateDirective, descendants: true }], viewQueries: [{ propertyName: "popoverWrapper", first: true, predicate: ["popoverWrapper"], descendants: true }, { propertyName: "titleTemplateWrapper", first: true, predicate: ["titleTemplateWrapper"], descendants: true }, { propertyName: "bodyTemplateWrapper", first: true, predicate: ["bodyTemplateWrapper"], descendants: true }], ngImport: i0, template: ` <div #popoverWrapper *ngIf="visible" class="k-popover k-popup" [ngStyle]="{'width': width, 'height': height}"> <div class="k-popover-callout" [ngClass]="getCalloutPosition()" *ngIf="callout"></div> <div class="k-popover-inner" *ngIf="callout; else noCallout"> <ng-container *ngTemplateOutlet="noCallout"></ng-container> </div> <ng-template #noCallout> <div #titleTemplateWrapper *ngIf="titleTemplate || title" class="k-popover-header"> <ng-template *ngIf="titleTemplate" [ngTemplateOutlet]="titleTemplate?.templateRef" [ngTemplateOutletContext]="{ $implicit: anchor, data: contextData }"> </ng-template> <ng-container *ngIf="title && !titleTemplate"> {{ title }} </ng-container> </div> <div #bodyTemplateWrapper *ngIf="bodyTemplate || body" class="k-popover-body"> <ng-template *ngIf="bodyTemplate" [ngTemplateOutlet]="bodyTemplate?.templateRef" [ngTemplateOutletContext]="{ $implicit: anchor, data: contextData }"> </ng-template> <ng-container *ngIf="body && !bodyTemplate"> {{ body }} </ng-container> </div> <div *ngIf="actionsTemplate" class="k-popover-actions k-actions k-actions-stretched k-actions-horizontal"> <ng-template *ngIf="actionsTemplate" [ngTemplateOutlet]="actionsTemplate?.templateRef" [ngTemplateOutletContext]="{ $implicit: anchor, data: contextData }"> </ng-template> </div> </ng-template> </div> `, isInline: true, dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: PopoverComponent, decorators: [{ type: Component, args: [{ selector: 'kendo-popover', providers: [ LocalizationService, { provide: L10N_PREFIX, useValue: 'kendo.popover' } ], template: ` <div #popoverWrapper *ngIf="visible" class="k-popover k-popup" [ngStyle]="{'width': width, 'height': height}"> <div class="k-popover-callout" [ngClass]="getCalloutPosition()" *ngIf="callout"></div> <div class="k-popover-inner" *ngIf="callout; else noCallout"> <ng-container *ngTemplateOutlet="noCallout"></ng-container> </div> <ng-template #noCallout> <div #titleTemplateWrapper *ngIf="titleTemplate || title" class="k-popover-header"> <ng-template *ngIf="titleTemplate" [ngTemplateOutlet]="titleTemplate?.templateRef" [ngTemplateOutletContext]="{ $implicit: anchor, data: contextData }"> </ng-template> <ng-container *ngIf="title && !titleTemplate"> {{ title }} </ng-container> </div> <div #bodyTemplateWrapper *ngIf="bodyTemplate || body" class="k-popover-body"> <ng-template *ngIf="bodyTemplate" [ngTemplateOutlet]="bodyTemplate?.templateRef" [ngTemplateOutletContext]="{ $implicit: anchor, data: contextData }"> </ng-template> <ng-container *ngIf="body && !bodyTemplate"> {{ body }} </ng-container> </div> <div *ngIf="actionsTemplate" class="k-popover-actions k-actions k-actions-stretched k-actions-horizontal"> <ng-template *ngIf="actionsTemplate" [ngTemplateOutlet]="actionsTemplate?.templateRef" [ngTemplateOutletContext]="{ $implicit: anchor, data: contextData }"> </ng-template> </div> </ng-template> </div> `, standalone: true, imports: [NgIf, NgStyle, NgClass, NgTemplateOutlet] }] }], ctorParameters: function () { return [{ type: i1.LocalizationService }, { type: i0.Renderer2 }, { type: i0.ElementRef }, { type: i0.NgZone }]; }, propDecorators: { position: [{ type: Input }], offset: [{ type: Input }], width: [{ type: Input }], height: [{ type: Input }], direction: [{ type: HostBinding, args: ['attr.dir'] }], title: [{ type: Input }], subtitle: [{ type: Input }], body: [{ type: Input }], callout: [{ type: Input }], animation: [{ type: Input }], templateData: [{ type: Input }], isHidden: [{ type: HostBinding, args: ['class.k-hidden'] }], hasAttributeHidden: [{ type: HostBinding, args: ['attr.aria-hidden'] }], show: [{ type: Output }], shown: [{ type: Output }], hide: [{ type: Output }], hidden: [{ type: Output }], closeOnKeyDown: [{ type: Output }], popoverWrapper: [{ type: ViewChild, args: ['popoverWrapper'] }], titleTemplateWrapper: [{ type: ViewChild, args: ['titleTemplateWrapper'] }], bodyTemplateWrapper: [{ type: ViewChild, args: ['bodyTemplateWrapper'] }], titleTemplate: [{ type: ContentChild, args: [PopoverTitleTemplateDirective, { static: false }] }], bodyTemplate: [{ type: ContentChild, args: [PopoverBodyTemplateDirective, { static: false }] }], actionsTemplate: [{ type: ContentChild, args: [PopoverActionsTemplateDirective, { static: false }] }], _width: [{ type: HostBinding, args: ['style.width'] }], _height: [{ type: HostBinding, args: ['style.height'] }] } });