c3-tooltipTrigger.directive.ts 3.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. import { Directive, forwardRef, Input, AfterViewInit, ElementRef, ViewContainerRef, OnDestroy, NgZone } from '@angular/core';
  2. import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
  3. import { C3Tooltip } from './c3-tooltip.component';
  4. import { Overlay, OverlayRef, OverlayConfig, PositionStrategy, FlexibleConnectedPositionStrategy, ConnectedPosition } from '@angular/cdk/overlay';
  5. import { Platform } from '@angular/cdk/platform';
  6. import { TemplatePortal } from '@angular/cdk/portal';
  7. export const MAT_AUTOCOMPLETE_VALUE_ACCESSOR: any = {
  8. provide: NG_VALUE_ACCESSOR,
  9. useExisting: forwardRef(() => C3TooltipTrigger),
  10. multi: true
  11. };
  12. @Directive({
  13. selector: '[c3Tooltip]',
  14. exportAs: 'c3TooltipTrigger',
  15. providers: [MAT_AUTOCOMPLETE_VALUE_ACCESSOR]
  16. })
  17. export class C3TooltipTrigger implements AfterViewInit, OnDestroy {
  18. private _overlayRef: OverlayRef | null;
  19. private _portal: TemplatePortal;
  20. private _positionStrategy: FlexibleConnectedPositionStrategy;
  21. private _overlayAttached: boolean = false;
  22. @Input('c3Tooltip') tooltip: C3Tooltip;
  23. @Input('c3TooltipDisabled') disabled: Boolean = false;
  24. /** The default delay in ms before showing the tooltip after show is called */
  25. @Input('c3TooltipShowDelay') showDelay = 0;
  26. /** The default delay in ms before hiding the tooltip after hide is called */
  27. @Input('c3TooltipHideDelay') hideDelay = 0;
  28. private _manualListeners = new Map<string, EventListenerOrEventListenerObject>();
  29. constructor(
  30. private _element: ElementRef<HTMLElement>,
  31. private _overlay: Overlay,
  32. platform: Platform,
  33. private _ngZone: NgZone,
  34. private _viewContainerRef: ViewContainerRef) {
  35. if (!platform.IOS && !platform.ANDROID) {
  36. this._manualListeners
  37. .set('mouseenter', () => this.show())
  38. .set('mouseleave', () => this.hide());
  39. }
  40. }
  41. ngAfterViewInit() {
  42. this._manualListeners.forEach((listener, event) => this._element.nativeElement.addEventListener(event, listener));
  43. }
  44. ngOnDestroy() {
  45. this._manualListeners.forEach((listener, event) => {
  46. this._element.nativeElement.removeEventListener(event, listener);
  47. });
  48. this._manualListeners.clear();
  49. }
  50. show(delay: number = this.showDelay) {
  51. console.log('show')
  52. let overlayRef = this._overlayRef;
  53. if (!overlayRef) {
  54. this._portal = new TemplatePortal(this.tooltip.template, this._viewContainerRef);
  55. overlayRef = this._overlay.create(this._getOverlayConfig());
  56. this._overlayRef = overlayRef;
  57. }
  58. if (overlayRef && !overlayRef.hasAttached()) {
  59. overlayRef.attach(this._portal);
  60. this._overlayAttached = true;
  61. }
  62. }
  63. get panelOpen(): boolean {
  64. return this._overlayAttached;
  65. }
  66. hide() {
  67. if (!this._overlayAttached) {
  68. return;
  69. }
  70. if (this._overlayRef && this._overlayRef.hasAttached()) {
  71. this._overlayRef.detach();
  72. }
  73. }
  74. private _getOverlayConfig(): OverlayConfig {
  75. return new OverlayConfig({
  76. positionStrategy: this._getOverlayPosition(),
  77. width: this._element.nativeElement.getBoundingClientRect().width
  78. });
  79. }
  80. private _getOverlayPosition(): PositionStrategy {
  81. const strategy = this._overlay.position()
  82. .flexibleConnectedTo(this.tooltip.template.elementRef)
  83. .withFlexibleDimensions(true)
  84. .withPush(false)
  85. .withViewportMargin(8);
  86. this._setStrategyPositions(strategy);
  87. this._positionStrategy = strategy;
  88. return strategy;
  89. }
  90. private _setStrategyPositions(positionStrategy: FlexibleConnectedPositionStrategy) {
  91. const belowPosition: ConnectedPosition = {
  92. originX: 'start',
  93. originY: 'bottom',
  94. overlayX: 'start',
  95. overlayY: 'top'
  96. };
  97. const abovePosition: ConnectedPosition = {
  98. originX: 'start',
  99. originY: 'top',
  100. overlayX: 'start',
  101. overlayY: 'bottom',
  102. // The overlay edge connected to the trigger should have squared corners, while
  103. // the opposite end has rounded corners. We apply a CSS class to swap the
  104. // border-radius based on the overlay position.
  105. };
  106. let positions: ConnectedPosition[];
  107. positions = [belowPosition, abovePosition];
  108. positionStrategy.withPositions(positions);
  109. }
  110. }