All files / tooltip si-tooltip.directive.ts

100% Statements 28/28
90% Branches 9/10
100% Functions 7/7
100% Lines 28/28

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119                                                                1x 1x             77x             77x             77x         77x   77x       77x 77x 77x     77x 77x       86x 5x 5x         6x 6x 1x     5x   5x   5x 5x             5x         5x 5x         1x       4x 4x      
/**
 * Copyright (c) Siemens 2016 - 2026
 * SPDX-License-Identifier: MIT
 */
import { isPlatformBrowser } from '@angular/common';
import {
  booleanAttribute,
  Directive,
  ElementRef,
  inject,
  input,
  OnDestroy,
  PLATFORM_ID,
  TemplateRef
} from '@angular/core';
import { positions } from '@siemens/element-ng/common';
import { TranslatableString } from '@siemens/element-translate-ng/translate';
 
import { SiTooltipService, TooltipRef } from './si-tooltip.service';
 
@Directive({
  selector: '[siTooltip]',
  providers: [SiTooltipService],
  host: {
    '[attr.aria-describedby]': 'describedBy',
    '(focus)': 'focusIn($event)',
    '(mouseenter)': 'show()',
    '(touchstart)': 'hide()',
    '(focusout)': 'hide()',
    '(mouseleave)': 'hide()'
  }
})
export class SiTooltipDirective implements OnDestroy {
  private static idCounter = 0;
 
  /**
   * The tooltip text to be displayed
   *
   * @defaultValue ''
   */
  readonly siTooltip = input<TranslatableString | TemplateRef<any>>('');
 
  /**
   * The placement of the tooltip. One of 'top', 'start', end', 'bottom'
   *
   * @defaultValue 'auto'
   */
  readonly placement = input<keyof typeof positions>('auto');
 
  /**
   * Allows the tooltip to be disabled
   *
   * @defaultValue false
   */
  readonly isDisabled = input(false, { transform: booleanAttribute });
 
  /**
   * The context for the attached template
   */
  readonly tooltipContext = input();
 
  protected describedBy = `__tooltip_${SiTooltipDirective.idCounter++}`;
 
  private tooltipRef?: TooltipRef;
  private showTimeout?: ReturnType<typeof setTimeout>;
  private tooltipService = inject(SiTooltipService);
  private elementRef = inject(ElementRef);
  private isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
 
  ngOnDestroy(): void {
    this.clearShowTimeout();
    this.tooltipRef?.destroy();
  }
 
  private clearShowTimeout(): void {
    if (this.showTimeout) {
      clearTimeout(this.showTimeout);
      this.showTimeout = undefined;
    }
  }
 
  private showTooltip(immediate = false): void {
    const siTooltip = this.siTooltip();
    if (this.isDisabled() || !siTooltip) {
      return;
    }
 
    this.clearShowTimeout();
 
    const delay = immediate ? 0 : 500;
 
    this.showTimeout = setTimeout(() => {
      this.tooltipRef ??= this.tooltipService.createTooltip({
        describedBy: this.describedBy,
        element: this.elementRef,
        placement: this.placement(),
        tooltip: this.siTooltip,
        tooltipContext: this.tooltipContext
      });
      this.tooltipRef.show();
    }, delay);
  }
 
  protected focusIn(event: FocusEvent): void {
    if (this.isBrowser && (event.target as Element).matches(':focus-visible')) {
      this.showTooltip(true);
    }
  }
 
  protected show(): void {
    this.showTooltip(false);
  }
 
  protected hide(): void {
    this.clearShowTimeout();
    this.tooltipRef?.hide();
  }
}