UNPKG

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
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=