Source

lib/directives/tooltip.directive.ts

/**
 * @module DecafTooltipDirective
 * @description Provides a tooltip directive for the decaf-ts for-angular library.
 * @summary This module defines the {@link DecafTooltipDirective}, a standalone Angular directive
 * that dynamically appends a tooltip element to any host element it decorates. It also supports
 * optional text truncation using the {@link DecafTruncatePipe}.
 */

import { Directive, ElementRef, inject, Input, OnChanges, Renderer2 } from '@angular/core';
import { ITooltipConfig } from '../engine/interfaces';
import { DecafTruncatePipe } from '../pipes/truncate.pipe';

/**
 * @description Angular directive that appends a tooltip `<span>` to the host element and
 * optionally truncates its visible text content.
 * @summary The `DecafTooltipDirective` is a standalone Angular directive bound to the
 * `[ngx-decaf-tooltip]` attribute selector. It processes the {@link TooltipConfig} provided
 * via the `options` input, sanitizes the text, and appends a `.dcf-tooltip` `<span>` containing
 * the sanitized full text to the host element. The directive also applies the `dcf-tooltip-parent`
 * CSS class to the host for tooltip positioning. When truncation is enabled, the host element's
 * inner content is replaced with the truncated text before the tooltip span is added.
 * @example
 * ```html
 * <!-- Basic tooltip -->
 * <span [ngx-decaf-tooltip]="{ text: 'Full description here' }">Hover me</span>
 *
 * <!-- Truncated visible text with tooltip showing the full content -->
 * <span [ngx-decaf-tooltip]="{ text: veryLongLabel, truncate: true, limit: 20, trail: '…' }">
 *   {{ veryLongLabel }}
 * </span>
 * ```
 * @class DecafTooltipDirective
 */

/**
 * @description Angular lifecycle hook invoked whenever one or more input properties change.
 * @summary Processes the {@link ITooltipConfig} options, sanitizes the text, and updates the
 * host element's content and tooltip span accordingly. Applies the `dcf-tooltip-parent` CSS class
 * to the host for styling.
 * @return {void}
 */
@Directive({
  selector: '[ngx-decaf-tooltip]',
  providers: [DecafTruncatePipe],
  standalone: true,
})
export class DecafTooltipDirective implements OnChanges {
  @Input('ngx-decaf-tooltip')
  options!: ITooltipConfig;

  /**
   * @description Reference to the host DOM element into which the SVG will be injected.
   * @summary Obtained via Angular's `inject(ElementRef)`. Provides access to the native
   * element forwarded to {@link NgxMediaService.loadSvgObserver} as the injection target,
   * and used as a fallback source for the `src` attribute when `path` is not set.
   * @type {ElementRef}
   * @memberOf module:lib/directives/NgxSvgDirective
   */
  element: ElementRef = inject(ElementRef);
  renderer: Renderer2 = inject(Renderer2);
  truncatePipe: DecafTruncatePipe = inject(DecafTruncatePipe);

  /**
   * @description Angular lifecycle hook invoked whenever one or more input properties change.
   * @summary Processes the {@link TooltipConfig} options, sanitizes the text, and updates the
   * host element's content and tooltip span accordingly. Applies the `dcf-tooltip-parent` CSS class
   * to the host for styling.
   * @return {void}
   */
  ngOnChanges(): void {
    const options = {
      truncate: false,
      limit: 30,
      ...this.options,
    };
    if (options?.text && options?.text.trim().length) {
      const value = options.text.replace(/<[^>]+>/g, '').trim();
      if (value.length > options.limit) {
        const text = !options.truncate
          ? value
          : this.truncatePipe.transform(value, options.limit, options.trail || '...');

        const element = this.element?.nativeElement ? this.element?.nativeElement : this.element;
        if (options.truncate) {
          this.renderer.setProperty(element, 'innerHTML', '');
          const textNode = this.renderer.createText(text);
          this.renderer.appendChild(element, textNode);
        }

        // creating tooltip element
        const tooltip = this.renderer.createElement('span');
        this.renderer.addClass(tooltip, 'dcf-tooltip');
        this.renderer.appendChild(tooltip, this.renderer.createText(this.truncatePipe.sanitize(value)));
        this.renderer.appendChild(element, tooltip);
        this.renderer.addClass(element, 'dcf-tooltip-parent');
      }
    }
  }
}