55 lines
1.8 KiB
TypeScript
55 lines
1.8 KiB
TypeScript
// SPDX-FileCopyrightText: 2025-2026 Lucio Lelii <lucio.lelii@isti.cnr.it> - 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<HTMLElement>);
|
|
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;
|
|
}
|
|
}
|