// SPDX-FileCopyrightText: 2025-2026 Lucio Lelii - ISTI-CNR // SPDX-License-Identifier: AGPL-3.0-or-later // Attribution term under AGPL-3.0 section 7(b): see LICENSE-ADDENDUM. import { AfterViewInit, Directive, ElementRef, Input, OnChanges, OnDestroy, SimpleChanges, inject } from '@angular/core'; import { MatTooltip } from '@angular/material/tooltip'; /** * Shows a title preview only when its host has actually been ellipsized. Measuring after render * and observing size changes avoids the stale first-row-only result a template expression gives * while a list is still laying itself out. */ @Directive({ selector: '[appTruncatedTooltip]', standalone: true, hostDirectives: [MatTooltip] }) export class TruncatedTooltipDirective implements AfterViewInit, OnChanges, OnDestroy { @Input({ required: true }) appTruncatedTooltip = ''; private readonly element = inject(ElementRef); private readonly tooltip = inject(MatTooltip); private resizeObserver: ResizeObserver | null = null; ngAfterViewInit() { this.tooltip.position = 'above'; this.tooltip.tooltipClass = 'title-preview-tooltip'; this.scheduleRefresh(); if (typeof ResizeObserver !== 'undefined') { this.resizeObserver = new ResizeObserver(() => this.refresh()); this.resizeObserver.observe(this.element.nativeElement); } } ngOnChanges(_changes: SimpleChanges) { this.tooltip.message = this.appTruncatedTooltip; this.scheduleRefresh(); } ngOnDestroy() { this.resizeObserver?.disconnect(); } private scheduleRefresh() { queueMicrotask(() => this.refresh()); } private refresh() { const host = this.element.nativeElement; this.tooltip.message = this.appTruncatedTooltip; this.tooltip.disabled = host.scrollWidth <= host.clientWidth; } }