ngx-ellipsis-tool
Version:
Angular directive that automatically detects when text is truncated with ellipsis (...) and shows a tooltip with the full text — only when needed!
111 lines • 12.8 kB
JavaScript
import { Directive, Input, HostListener, ApplicationRef, EnvironmentInjector, inject, DestroyRef, createComponent } from '@angular/core';
import { TooltipComponent } from './custom-tooltip/custom-tooltip.component';
import * as i0 from "@angular/core";
export class NgxEllipsisTooltipDirective {
constructor(el) {
this.el = el;
this.placement = 'top';
this.delay = 300;
this.tooltipClass = '';
this.appRef = inject(ApplicationRef);
this.envInjector = inject(EnvironmentInjector);
this.destroyRef = inject(DestroyRef);
this.destroyRef.onDestroy(() => this.hideTooltip());
}
onMouseEnter() {
this.scheduleShowTooltip();
}
onMouseLeave() {
this.cancelShow();
this.hideTooltip();
}
onClick() {
// For mobile → show immediately on click
this.cancelShow();
this.tryShowTooltip();
}
onDocumentClick(event) {
if (this.tooltipRef && !this.el.nativeElement.contains(event.target)) {
this.hideTooltip();
}
}
scheduleShowTooltip() {
this.cancelShow();
this.showTimeoutId = setTimeout(() => {
this.tryShowTooltip();
}, this.delay);
}
cancelShow() {
if (this.showTimeoutId) {
clearTimeout(this.showTimeoutId);
this.showTimeoutId = null;
}
}
tryShowTooltip() {
if (this.isTruncated()) {
const text = this.tooltipText || this.el.nativeElement.textContent?.trim() || '';
const rect = this.el.nativeElement.getBoundingClientRect();
let top = rect.top + window.scrollY;
let left = rect.left + window.scrollX;
const elHeight = rect.height;
if (this.placement === 'top') {
top -= 8;
}
else {
top += elHeight + 8;
}
this.showTooltip(text, left, top);
}
}
isTruncated() {
const el = this.el.nativeElement;
return el.scrollWidth > el.clientWidth || el.scrollHeight > el.clientHeight;
}
showTooltip(text, left, top) {
this.hideTooltip();
this.tooltipRef = createComponent(TooltipComponent, {
environmentInjector: this.envInjector,
hostElement: document.body
});
this.tooltipRef.instance.text = text;
this.tooltipRef.instance.left = left;
this.tooltipRef.instance.top = top;
this.tooltipRef.instance.customClass = this.tooltipClass;
}
hideTooltip() {
if (this.tooltipRef) {
this.tooltipRef.destroy();
this.tooltipRef = undefined;
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: NgxEllipsisTooltipDirective, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.3.12", type: NgxEllipsisTooltipDirective, isStandalone: true, selector: "[ngxEllipsisTooltip]", inputs: { tooltipText: "tooltipText", placement: "placement", delay: "delay", tooltipClass: "tooltipClass" }, host: { listeners: { "mouseenter": "onMouseEnter()", "mouseleave": "onMouseLeave()", "click": "onClick()", "document:click": "onDocumentClick($event)" } }, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: NgxEllipsisTooltipDirective, decorators: [{
type: Directive,
args: [{
selector: '[ngxEllipsisTooltip]',
standalone: true
}]
}], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { tooltipText: [{
type: Input
}], placement: [{
type: Input
}], delay: [{
type: Input
}], tooltipClass: [{
type: Input
}], onMouseEnter: [{
type: HostListener,
args: ['mouseenter']
}], onMouseLeave: [{
type: HostListener,
args: ['mouseleave']
}], onClick: [{
type: HostListener,
args: ['click']
}], onDocumentClick: [{
type: HostListener,
args: ['document:click', ['$event']]
}] } });
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmd4LWVsbGlwc2lzLXRvb2wuZGlyZWN0aXZlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vcHJvamVjdHMvbmd4LWVsbGlwc2lzLXRvb2wvc3JjL2xpYi9uZ3gtZWxsaXBzaXMtdG9vbC5kaXJlY3RpdmUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUNMLFNBQVMsRUFFVCxLQUFLLEVBQ0wsWUFBWSxFQUVaLGNBQWMsRUFDZCxtQkFBbUIsRUFDbkIsTUFBTSxFQUNOLFVBQVUsRUFDVixlQUFlLEVBQ2hCLE1BQU0sZUFBZSxDQUFDO0FBQ3ZCLE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLDJDQUEyQyxDQUFDOztBQU03RSxNQUFNLE9BQU8sMkJBQTJCO0lBYXRDLFlBQW9CLEVBQTJCO1FBQTNCLE9BQUUsR0FBRixFQUFFLENBQXlCO1FBWHRDLGNBQVMsR0FBcUIsS0FBSyxDQUFDO1FBQ3BDLFVBQUssR0FBRyxHQUFHLENBQUM7UUFDWixpQkFBWSxHQUFHLEVBQUUsQ0FBQztRQUduQixXQUFNLEdBQUcsTUFBTSxDQUFDLGNBQWMsQ0FBQyxDQUFDO1FBQ2hDLGdCQUFXLEdBQUcsTUFBTSxDQUFDLG1CQUFtQixDQUFDLENBQUM7UUFDMUMsZUFBVSxHQUFHLE1BQU0sQ0FBQyxVQUFVLENBQUMsQ0FBQztRQUt0QyxJQUFJLENBQUMsVUFBVSxDQUFDLFNBQVMsQ0FBQyxHQUFHLEVBQUUsQ0FBQyxJQUFJLENBQUMsV0FBVyxFQUFFLENBQUMsQ0FBQztJQUN0RCxDQUFDO0lBR0QsWUFBWTtRQUNWLElBQUksQ0FBQyxtQkFBbUIsRUFBRSxDQUFDO0lBQzdCLENBQUM7SUFHRCxZQUFZO1FBQ1YsSUFBSSxDQUFDLFVBQVUsRUFBRSxDQUFDO1FBQ2xCLElBQUksQ0FBQyxXQUFXLEVBQUUsQ0FBQztJQUNyQixDQUFDO0lBR0QsT0FBTztRQUNMLHlDQUF5QztRQUN6QyxJQUFJLENBQUMsVUFBVSxFQUFFLENBQUM7UUFD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QixJQUFJLENBQUMsVUFBVSxDQUFDLE9BQU8sRUFBRSxDQUFDO1lBQzFCLElBQUksQ0FBQyxVQUFVLEdBQUcsU0FBUyxDQUFDO1FBQzlCLENBQUM7SUFDSCxDQUFDOytHQW5HVSwyQkFBMkI7bUdBQTNCLDJCQUEyQjs7NEZBQTNCLDJCQUEyQjtrQkFKdkMsU0FBUzttQkFBQztvQkFDVCxRQUFRLEVBQUUsc0JBQXNCO29CQUNoQyxVQUFVLEVBQUUsSUFBSTtpQkFDakI7K0VBRVUsV0FBVztzQkFBbkIsS0FBSztnQkFDRyxTQUFTO3NCQUFqQixLQUFLO2dCQUNHLEtBQUs7c0JBQWIsS0FBSztnQkFDRyxZQUFZO3NCQUFwQixLQUFLO2dCQWNOLFlBQVk7c0JBRFgsWUFBWTt1QkFBQyxZQUFZO2dCQU0xQixZQUFZO3NCQURYLFlBQVk7dUJBQUMsWUFBWTtnQkFPMUIsT0FBTztzQkFETixZQUFZO3VCQUFDLE9BQU87Z0JBUXJCLGVBQWU7c0JBRGQsWUFBWTt1QkFBQyxnQkFBZ0IsRUFBRSxDQUFDLFFBQVEsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7XG4gIERpcmVjdGl2ZSxcbiAgRWxlbWVudFJlZixcbiAgSW5wdXQsXG4gIEhvc3RMaXN0ZW5lcixcbiAgQ29tcG9uZW50UmVmLFxuICBBcHBsaWNhdGlvblJlZixcbiAgRW52aXJvbm1lbnRJbmplY3RvcixcbiAgaW5qZWN0LFxuICBEZXN0cm95UmVmLFxuICBjcmVhdGVDb21wb25lbnRcbn0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7IFxuaW1wb3J0IHsgVG9vbHRpcENvbXBvbmVudCB9IGZyb20gJy4vY3VzdG9tLXRvb2x0aXAvY3VzdG9tLXRvb2x0aXAuY29tcG9uZW50JztcblxuQERpcmVjdGl2ZSh7XG4gIHNlbGVjdG9yOiAnW25neEVsbGlwc2lzVG9vbHRpcF0nLFxuICBzdGFuZGFsb25lOiB0cnVlXG59KVxuZXhwb3J0IGNsYXNzIE5neEVsbGlwc2lzVG9vbHRpcERpcmVjdGl2ZSB7XG4gIEBJbnB1dCgpIHRvb2x0aXBUZXh0Pzogc3RyaW5nO1xuICBASW5wdXQoKSBwbGFjZW1lbnQ6ICd0b3AnIHwgJ2JvdHRvbScgPSAndG9wJztcbiAgQElucHV0KCkgZGVsYXkgPSAzMDA7XG4gIEBJbnB1dCgpIHRvb2x0aXBDbGFzcyA9ICcnO1xuXG4gIHByaXZhdGUgdG9vbHRpcFJlZj86IENvbXBvbmVudFJlZjxUb29sdGlwQ29tcG9uZW50PjtcbiAgcHJpdmF0ZSBhcHBSZWYgPSBpbmplY3QoQXBwbGljYXRpb25SZWYpO1xuICBwcml2YXRlIGVudkluamVjdG9yID0gaW5qZWN0KEVudmlyb25tZW50SW5qZWN0b3IpO1xuICBwcml2YXRlIGRlc3Ryb3lSZWYgPSBpbmplY3QoRGVzdHJveVJlZik7XG5cbiAgcHJpdmF0ZSBzaG93VGltZW91dElkOiBhbnk7XG5cbiAgY29uc3RydWN0b3IocHJpdmF0ZSBlbDogRWxlbWVudFJlZjxIVE1MRWxlbWVudD4pIHtcbiAgICB0aGlzLmRlc3Ryb3lSZWYub25EZXN0cm95KCgpID0+IHRoaXMuaGlkZVRvb2x0aXAoKSk7XG4gIH1cblxuICBASG9zdExpc3RlbmVyKCdtb3VzZWVudGVyJylcbiAgb25Nb3VzZUVudGVyKCkge1xuICAgIHRoaXMuc2NoZWR1bGVTaG93VG9vbHRpcCgpO1xuICB9XG5cbiAgQEhvc3RMaXN0ZW5lcignbW91c2VsZWF2ZScpXG4gIG9uTW91c2VMZWF2ZSgpIHtcbiAgICB0aGlzLmNhbmNlbFNob3coKTtcbiAgICB0aGlzLmhpZGVUb29sdGlwKCk7XG4gIH1cblxuICBASG9zdExpc3RlbmVyKCdjbGljaycpXG4gIG9uQ2xpY2soKSB7XG4gICAgLy8gRm9yIG1vYmlsZSDihpIgc2hvdyBpbW1lZGlhdGVseSBvbiBjbGlja1xuICAgIHRoaXMuY2FuY2VsU2hvdygpO1xuICAgIHRoaXMudHJ5U2hvd1Rvb2x0aXAoKTtcbiAgfVxuXG4gIEBIb3N0TGlzdGVuZXIoJ2RvY3VtZW50OmNsaWNrJywgWyckZXZlbnQnXSlcbiAgb25Eb2N1bWVudENsaWNrKGV2ZW50OiBNb3VzZUV2ZW50KSB7XG4gICAgaWYgKHRoaXMudG9vbHRpcFJlZiAmJiAhdGhpcy5lbC5uYXRpdmVFbGVtZW50LmNvbnRhaW5zKGV2ZW50LnRhcmdldCBhcyBOb2RlKSkge1xuICAgICAgdGhpcy5oaWRlVG9vbHRpcCgpO1xuICAgIH1cbiAgfVxuXG4gIHByaXZhdGUgc2NoZWR1bGVTaG93VG9vbHRpcCgpIHtcbiAgICB0aGlzLmNhbmNlbFNob3coKTtcbiAgICB0aGlzLnNob3dUaW1lb3V0SWQgPSBzZXRUaW1lb3V0KCgpID0+IHtcbiAgICAgIHRoaXMudHJ5U2hvd1Rvb2x0aXAoKTtcbiAgICB9LCB0aGlzLmRlbGF5KTtcbiAgfVxuXG4gIHByaXZhdGUgY2FuY2VsU2hvdygpIHtcbiAgICBpZiAodGhpcy5zaG93VGltZW91dElkKSB7XG4gICAgICBjbGVhclRpbWVvdXQodGhpcy5zaG93VGltZW91dElkKTtcbiAgICAgIHRoaXMuc2hvd1RpbWVvdXRJZCA9IG51bGw7XG4gICAgfVxuICB9XG5cbiAgcHJpdmF0ZSB0cnlTaG93VG9vbHRpcCgpIHtcbiAgICBpZiAodGhpcy5pc1RydW5jYXRlZCgpKSB7XG4gICAgICBjb25zdCB0ZXh0ID0gdGhpcy50b29sdGlwVGV4dCB8fCB0aGlzLmVsLm5hdGl2ZUVsZW1lbnQudGV4dENvbnRlbnQ/LnRyaW0oKSB8fCAnJztcbiAgICAgIGNvbnN0IHJlY3QgPSB0aGlzLmVsLm5hdGl2ZUVsZW1lbnQuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCk7XG5cbiAgICAgIGxldCB0b3AgPSByZWN0LnRvcCArIHdpbmRvdy5zY3JvbGxZO1xuICAgICAgbGV0IGxlZnQgPSByZWN0LmxlZnQgKyB3aW5kb3cuc2Nyb2xsWDtcbiAgICAgIGNvbnN0IGVsSGVpZ2h0ID0gcmVjdC5oZWlnaHQ7XG5cbiAgICAgIGlmICh0aGlzLnBsYWNlbWVudCA9PT0gJ3RvcCcpIHtcbiAgICAgICAgdG9wIC09IDg7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICB0b3AgKz0gZWxIZWlnaHQgKyA4O1xuICAgICAgfVxuXG4gICAgICB0aGlzLnNob3dUb29sdGlwKHRleHQsIGxlZnQsIHRvcCk7XG4gICAgfVxuICB9XG5cbiAgcHJpdmF0ZSBpc1RydW5jYXRlZCgpOiBib29sZWFuIHtcbiAgICBjb25zdCBlbCA9IHRoaXMuZWwubmF0aXZlRWxlbWVudDtcbiAgICByZXR1cm4gZWwuc2Nyb2xsV2lkdGggPiBlbC5jbGllbnRXaWR0aCB8fCBlbC5zY3JvbGxIZWlnaHQgPiBlbC5jbGllbnRIZWlnaHQ7XG4gIH1cblxuICBwcml2YXRlIHNob3dUb29sdGlwKHRleHQ6IHN0cmluZywgbGVmdDogbnVtYmVyLCB0b3A6IG51bWJlcikge1xuICAgIHRoaXMuaGlkZVRvb2x0aXAoKTtcbiAgXG4gICAgdGhpcy50b29sdGlwUmVmID0gY3JlYXRlQ29tcG9uZW50KFRvb2x0aXBDb21wb25lbnQsIHtcbiAgICAgIGVudmlyb25tZW50SW5qZWN0b3I6IHRoaXMuZW52SW5qZWN0b3IsXG4gICAgICBob3N0RWxlbWVudDogZG9jdW1lbnQuYm9keVxuICAgIH0pO1xuICAgIHRoaXMudG9vbHRpcFJlZi5pbnN0YW5jZS50ZXh0ID0gdGV4dDtcbiAgICB0aGlzLnRvb2x0aXBSZWYuaW5zdGFuY2UubGVmdCA9IGxlZnQ7XG4gICAgdGhpcy50b29sdGlwUmVmLmluc3RhbmNlLnRvcCA9IHRvcDtcbiAgICB0aGlzLnRvb2x0aXBSZWYuaW5zdGFuY2UuY3VzdG9tQ2xhc3MgPSB0aGlzLnRvb2x0aXBDbGFzcztcbiAgfVxuICBcblxuICBwcml2YXRlIGhpZGVUb29sdGlwKCkge1xuICAgIGlmICh0aGlzLnRvb2x0aXBSZWYpIHtcbiAgICAgIHRoaXMudG9vbHRpcFJlZi5kZXN0cm95KCk7XG4gICAgICB0aGlzLnRvb2x0aXBSZWYgPSB1bmRlZmluZWQ7XG4gICAgfVxuICB9XG59XG4iXX0=