diff --git a/projects/evo-ui-kit/src/lib/components/evo-tooltip/constants/evo-tooltip-config.ts b/projects/evo-ui-kit/src/lib/components/evo-tooltip/constants/evo-tooltip-config.ts
index a77faf984..2defc0e33 100644
--- a/projects/evo-ui-kit/src/lib/components/evo-tooltip/constants/evo-tooltip-config.ts
+++ b/projects/evo-ui-kit/src/lib/components/evo-tooltip/constants/evo-tooltip-config.ts
@@ -3,4 +3,5 @@ import {EvoTooltipConfig} from '../interfaces/evo-tooltip-config';
export const EVO_TOOLTIP_CONFIG: EvoTooltipConfig = {
hideDelay: 300,
showDelay: 100,
+ scrollStrategy: 'close',
};
diff --git a/projects/evo-ui-kit/src/lib/components/evo-tooltip/directives/evo-tooltip.directive.spec.ts b/projects/evo-ui-kit/src/lib/components/evo-tooltip/directives/evo-tooltip.directive.spec.ts
index 8e3a94f72..ed9a7e990 100644
--- a/projects/evo-ui-kit/src/lib/components/evo-tooltip/directives/evo-tooltip.directive.spec.ts
+++ b/projects/evo-ui-kit/src/lib/components/evo-tooltip/directives/evo-tooltip.directive.spec.ts
@@ -1,19 +1,18 @@
-import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
-import { EvoTooltipDirective } from './evo-tooltip.directive';
-import { Component } from '@angular/core';
-import { EvoTooltipPosition } from '../enums/evo-tooltip-position';
-import { EvoTooltipStyles } from '../interfaces/evo-tooltip-styles';
-import { EvoTooltipVariableArrowPosition } from '../enums/evo-tooltip-variable-arrow-position';
-import { CommonModule } from '@angular/common';
-import { EvoTooltipService } from '../services/evo-tooltip.service';
-import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
-import { first } from 'rxjs/operators';
-import { EvoTooltipComponent } from '../evo-tooltip.component';
-import { NO_ERRORS_SCHEMA } from '@angular/core';
+import {ComponentFixture, fakeAsync, TestBed, tick} from '@angular/core/testing';
+import {EvoTooltipDirective} from './evo-tooltip.directive';
+import {Component, NO_ERRORS_SCHEMA} from '@angular/core';
+import {EvoTooltipPosition} from '../enums/evo-tooltip-position';
+import {EvoTooltipStyles} from '../interfaces/evo-tooltip-styles';
+import {EvoTooltipStyleVariable} from '../enums/evo-tooltip-style-variable';
+import {CommonModule} from '@angular/common';
+import {EvoTooltipService} from '../services/evo-tooltip.service';
+import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
+import {first} from 'rxjs/operators';
+import {By} from "@angular/platform-browser";
@Component({
template: `
-
{
- let component: TestHostComponent;
let fixture: ComponentFixture
;
+
+ let component: TestHostComponent;
+ let triggerEl: HTMLElement;
+
let directive: EvoTooltipDirective;
let tooltipService: EvoTooltipService;
beforeEach(async () => {
await TestBed.configureTestingModule({
- declarations: [TestHostComponent, EvoTooltipDirective, EvoTooltipComponent],
- imports: [CommonModule, BrowserAnimationsModule],
+ declarations: [TestHostComponent],
+ imports: [CommonModule, BrowserAnimationsModule, EvoTooltipDirective],
providers: [EvoTooltipService],
schemas: [NO_ERRORS_SCHEMA],
}).compileComponents();
fixture = TestBed.createComponent(TestHostComponent);
component = fixture.componentInstance;
- directive = fixture.debugElement.children[0].injector.get(EvoTooltipDirective);
+
+ const directiveEl = fixture.debugElement.query(By.directive(EvoTooltipDirective));
+ triggerEl = directiveEl.nativeElement;
+
+ directive = directiveEl.injector.get(EvoTooltipDirective);
tooltipService = TestBed.inject(EvoTooltipService);
fixture.detectChanges();
});
@@ -66,15 +73,13 @@ describe('EvoTooltipDirective', () => {
});
it('should have correct host classes', () => {
- const element = fixture.debugElement.children[0].nativeElement;
- expect(element.classList.contains('evo-tooltip-trigger')).toBeTrue();
+ expect(triggerEl.classList.contains('evo-tooltip-trigger')).toBeTrue();
});
it('should add disabled class when disabled', () => {
component.disabled = true;
fixture.detectChanges();
- const element = fixture.debugElement.children[0].nativeElement;
- expect(element.classList.contains('evo-tooltip-trigger_disabled')).toBeTrue();
+ expect(triggerEl.classList.contains('evo-tooltip-trigger_disabled')).toBeTrue();
});
it('should emit open event when tooltip is shown', fakeAsync(() => {
@@ -104,7 +109,8 @@ describe('EvoTooltipDirective', () => {
}));
it('should not show tooltip when content is empty', fakeAsync(() => {
- directive.content = null;
+ component.content = null;
+ fixture.detectChanges();
directive.show();
tick(0);
fixture.detectChanges();
@@ -112,8 +118,16 @@ describe('EvoTooltipDirective', () => {
}));
it('should handle mouseenter event', fakeAsync(() => {
- const element = fixture.debugElement.children[0].nativeElement;
- element.dispatchEvent(new MouseEvent('mouseenter'));
+ triggerEl.dispatchEvent(new MouseEvent('mouseenter'));
+ tick(0);
+ fixture.detectChanges();
+ tooltipService.isOpen$.pipe(first()).subscribe((isOpen) => {
+ expect(isOpen).toBeTrue();
+ });
+ }));
+
+ it('should handle touchstart event', fakeAsync(() => {
+ triggerEl.dispatchEvent(new MouseEvent('touchstart'));
tick(0);
fixture.detectChanges();
tooltipService.isOpen$.pipe(first()).subscribe((isOpen) => {
diff --git a/projects/evo-ui-kit/src/lib/components/evo-tooltip/directives/evo-tooltip.directive.ts b/projects/evo-ui-kit/src/lib/components/evo-tooltip/directives/evo-tooltip.directive.ts
index 9a59ffdf2..3e1c2f731 100644
--- a/projects/evo-ui-kit/src/lib/components/evo-tooltip/directives/evo-tooltip.directive.ts
+++ b/projects/evo-ui-kit/src/lib/components/evo-tooltip/directives/evo-tooltip.directive.ts
@@ -1,55 +1,63 @@
import {
+ DestroyRef,
Directive,
+ effect,
ElementRef,
- EventEmitter,
HostBinding,
- Input,
+ inject,
+ input,
OnDestroy,
OnInit,
- Output,
+ output,
TemplateRef,
} from '@angular/core';
-import {fromEvent, Observable, Subject} from 'rxjs';
-import {takeUntil, tap, throttleTime} from 'rxjs/operators';
-import {EvoTooltipService} from '../services/evo-tooltip.service';
-import {EvoTooltipPositionType} from '../types/evo-tooltip-position-type';
-import {EvoTooltipConfig} from '../interfaces/evo-tooltip-config';
+import {EMPTY, filter, fromEvent, merge} from 'rxjs';
+import {catchError, debounceTime, distinctUntilChanged, first, map, takeUntil, tap, throttleTime} from 'rxjs/operators';
import {EVO_TOOLTIP_CONFIG} from '../constants/evo-tooltip-config';
import {EvoTooltipPosition} from '../enums/evo-tooltip-position';
+import {EvoTooltipConfig} from '../interfaces/evo-tooltip-config';
import {EvoTooltipStyles} from '../interfaces/evo-tooltip-styles';
+import {EvoTooltipService} from '../services/evo-tooltip.service';
+import {EvoTooltipPositionType} from '../types/evo-tooltip-position-type';
+import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
@Directive({
selector: '[evoTooltip]',
exportAs: 'evoTooltip',
+ standalone: true,
providers: [EvoTooltipService],
})
export class EvoTooltipDirective implements OnInit, OnDestroy {
- @Input('evoTooltip') content: string | TemplateRef;
- @Input('evoTooltipPosition') position: EvoTooltipPositionType | string = EvoTooltipPosition.BOTTOM;
- @Input('evoTooltipDisabled') disabled = false;
- @Input('evoTooltipConfig') config: Partial;
- @Input() set evoTooltipVisibleArrow(visibleArrow: boolean) {
- this.tooltipService.setArrowVisibility(visibleArrow);
- }
- @Input() set evoTooltipStyles(tooltipStyles: EvoTooltipStyles) {
- this.tooltipService.setTooltipStyles(tooltipStyles);
- }
- @Input() set evoTooltipClass(tooltipClass: string | string[]) {
- this.tooltipService.setTooltipClass(tooltipClass);
- }
+ readonly content = input>('', {alias: 'evoTooltip'});
- @Output() evoTooltipOpen = new EventEmitter();
- @Output() evoTooltipClose = new EventEmitter();
+ readonly position = input(EvoTooltipPosition.BOTTOM, {alias: 'evoTooltipPosition'});
- @HostBinding('class') get hostClasses(): string[] {
- return ['evo-tooltip-trigger', ...(this.disabled ? ['evo-tooltip-trigger_disabled'] : [])];
- }
+ readonly disabled = input(false, {alias: 'evoTooltipDisabled'});
+
+ readonly config = input>(EVO_TOOLTIP_CONFIG, {alias: 'evoTooltipConfig'});
- readonly isOpen$: Observable = this.tooltipService.isOpen$;
+ readonly isVisibleArrow = input(true, {alias: 'evoTooltipVisibleArrow'});
- private readonly destroy$ = new Subject();
+ readonly tooltipStyles = input({}, {alias: 'evoTooltipStyles'});
- constructor(private readonly elementRef: ElementRef, private readonly tooltipService: EvoTooltipService) {}
+ readonly tooltipClass = input([], {alias: 'evoTooltipClass'});
+
+ readonly evoTooltipOpen = output()
+ readonly evoTooltipClose = output()
+
+ private readonly destroyRef = inject(DestroyRef);
+ private readonly elementRef = inject(ElementRef);
+ private readonly tooltipService = inject(EvoTooltipService);
+
+ @HostBinding('class') get hostClasses(): string[] {
+ return ['evo-tooltip-trigger', ...(this.disabled() ? ['evo-tooltip-trigger_disabled'] : [])];
+ }
+
+ constructor() {
+ effect((): void => this.tooltipService.setArrowVisibility(this.isVisibleArrow()));
+ effect((): void => this.tooltipService.setTooltipStyles(this.tooltipStyles()));
+ effect((): void => this.tooltipService.setTooltipClass(this.tooltipClass()));
+ }
ngOnInit(): void {
this.initSubscriptions();
@@ -57,46 +65,72 @@ export class EvoTooltipDirective implements OnInit, OnDestroy {
ngOnDestroy(): void {
this.hide();
- this.destroy$.next();
- this.destroy$.complete();
}
hide(): void {
this.tooltipService.hideTooltip();
}
- show(event?: MouseEvent): void {
- if (!this.content || this.tooltipService.hasAttached || this.disabled) {
+ show(): void {
+ const content = this.content();
+
+ if (!content || this.tooltipService.hasAttached || this.disabled()) {
return;
}
- this.tooltipService.showTooltip(
+ const tooltip = this.tooltipService.showTooltip(
this.elementRef,
- this.content,
- this.position as EvoTooltipPosition,
- {...EVO_TOOLTIP_CONFIG, ...this.config},
- event?.target,
+ content,
+ this.position() as EvoTooltipPosition,
+ {...EVO_TOOLTIP_CONFIG, ...this.config()},
);
+
+ this.initHideSubscription(tooltip);
}
private initSubscriptions(): void {
- fromEvent(this.elementRef.nativeElement, 'mouseenter')
+ const element = this.elementRef.nativeElement;
+
+ merge(fromEvent(element, 'mouseenter'), fromEvent(element, 'touchstart'))
.pipe(
- throttleTime(this.config?.showDelay ?? EVO_TOOLTIP_CONFIG.showDelay),
- tap(() => {
- this.show();
- }),
- takeUntil(this.destroy$),
+ throttleTime(this.config()?.showDelay ?? EVO_TOOLTIP_CONFIG.showDelay),
+ tap((): void => this.show()),
+ takeUntilDestroyed(this.destroyRef),
)
.subscribe();
this.tooltipService.isOpen$
.pipe(
- tap((isOpen) => {
- isOpen ? this.evoTooltipOpen.emit() : this.evoTooltipClose.emit();
+ distinctUntilChanged(),
+ tap((isOpen): void => {
+ if (isOpen) {
+ this.evoTooltipOpen.emit();
+ } else {
+ this.evoTooltipClose.emit();
+ }
}),
- takeUntil(this.destroy$),
+ takeUntilDestroyed(this.destroyRef),
)
.subscribe();
}
+
+ private initHideSubscription(tooltip: HTMLElement): void {
+ const mouseLeaveParentElement$ = fromEvent(tooltip, 'mouseleave').pipe(map(() => this.elementRef.nativeElement));
+ const mouseLeaveOverlayElement$ = fromEvent(this.elementRef.nativeElement, 'mouseleave').pipe(map(() => tooltip));
+
+ merge(mouseLeaveParentElement$, mouseLeaveOverlayElement$)
+ .pipe(
+ filter((element) => !element.matches(':hover')),
+ debounceTime(this.config()?.hideDelay ?? EVO_TOOLTIP_CONFIG.hideDelay),
+ filter(() => !tooltip.matches(':hover') && !this.elementRef.nativeElement.matches(':hover')),
+ first(),
+ catchError(() => {
+ this.tooltipService.hideTooltip();
+ return EMPTY;
+ }),
+ takeUntil(this.tooltipService.isOpen$.pipe(filter((isOpened: boolean) => !isOpened))),
+ takeUntilDestroyed(this.destroyRef),
+ )
+ .subscribe(() => this.tooltipService.hideTooltip());
+ }
}
diff --git a/projects/evo-ui-kit/src/lib/components/evo-tooltip/enums/evo-tooltip-style-variable.ts b/projects/evo-ui-kit/src/lib/components/evo-tooltip/enums/evo-tooltip-style-variable.ts
new file mode 100644
index 000000000..5f6a5806f
--- /dev/null
+++ b/projects/evo-ui-kit/src/lib/components/evo-tooltip/enums/evo-tooltip-style-variable.ts
@@ -0,0 +1,12 @@
+export enum EvoTooltipStyleVariable {
+ HORIZONTAL_POSITION_ARROW = '--evo-tooltip-horizontal-position-arrow',
+ VERTICAL_POSITION_ARROW = '--evo-tooltip-vertical-position-arrow',
+
+ COLOR = '--evo-tooltip-color',
+ BACKGROUND_COLOR = '--evo-tooltip-background-color',
+ MAX_WIDTH = '--evo-tooltip-max-width',
+
+ PADDING = '--evo-tooltip-padding',
+
+ BORDER_RADIUS = '--evo-tooltip-border-radius',
+}
diff --git a/projects/evo-ui-kit/src/lib/components/evo-tooltip/enums/evo-tooltip-variable-arrow-position.ts b/projects/evo-ui-kit/src/lib/components/evo-tooltip/enums/evo-tooltip-variable-arrow-position.ts
deleted file mode 100644
index cd74a502d..000000000
--- a/projects/evo-ui-kit/src/lib/components/evo-tooltip/enums/evo-tooltip-variable-arrow-position.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-export enum EvoTooltipVariableArrowPosition {
- HORIZONTAL_POSITION_ARROW = '--evo-tooltip-horizontal-position-arrow',
- VERTICAL_POSITION_ARROW = '--evo-tooltip-vertical-position-arrow',
-}
diff --git a/projects/evo-ui-kit/src/lib/components/evo-tooltip/evo-tooltip.component.html b/projects/evo-ui-kit/src/lib/components/evo-tooltip/evo-tooltip.component.html
index dc12a1f5e..a1776c097 100644
--- a/projects/evo-ui-kit/src/lib/components/evo-tooltip/evo-tooltip.component.html
+++ b/projects/evo-ui-kit/src/lib/components/evo-tooltip/evo-tooltip.component.html
@@ -1,16 +1,14 @@
-
-
- {{ content }}
-
-
-
-
-
-
+ @if (stringContent(); as content) {
+ {{ content }}
+ } @else {
+ @if (templateContent(); as content) {
+
+ }
+ }
+
diff --git a/projects/evo-ui-kit/src/lib/components/evo-tooltip/evo-tooltip.component.scss b/projects/evo-ui-kit/src/lib/components/evo-tooltip/evo-tooltip.component.scss
index 0ccbb3b20..565e6d82e 100644
--- a/projects/evo-ui-kit/src/lib/components/evo-tooltip/evo-tooltip.component.scss
+++ b/projects/evo-ui-kit/src/lib/components/evo-tooltip/evo-tooltip.component.scss
@@ -1,4 +1,4 @@
-@import '../../styles/mixins';
+@import "../../styles/mixins";
:host {
--evo-tooltip-horizontal-position-arrow: 50%;
@@ -15,7 +15,7 @@
}
.evo-tooltip {
- $arrow-size: 8px;
+ $arrow-border-width: 8px;
display: inline-block;
position: relative;
@@ -38,64 +38,19 @@
&:not(&_not-arrow):before {
content: "";
position: absolute;
- border-style: solid;
- }
-
- &_top-start,
- &_top,
- &_top-end {
- &:before {
- border-width: $arrow-size $arrow-size 0 $arrow-size;
- border-color: var(--evo-tooltip-background-color) transparent transparent transparent;
- bottom: -$arrow-size;
- }
- }
-
- &_top-start {
- &:before {
- left: var(--evo-tooltip-horizontal-position-arrow);
- }
- }
-
- &_top {
- &:before {
- left: 50%;
- transform: translateX(-50%);
- }
- }
-
- &_top-end {
- &:before {
- right: var(--evo-tooltip-horizontal-position-arrow);
- }
+ left: var(--evo-tooltip-horizontal-position-arrow);
+ top: var(--evo-tooltip-vertical-position-arrow);
+ width: 0;
+ height: 0;
+ border: $arrow-border-width solid transparent;
+ border-top: $arrow-border-width solid var(--evo-tooltip-background-color);
}
&_right-start,
&_right,
&_right-end {
&:before {
- border-width: $arrow-size $arrow-size $arrow-size 0;
- border-color: transparent var(--evo-tooltip-background-color) transparent transparent;
- left: -$arrow-size;
- }
- }
-
- &_right-start {
- &:before {
- top: var(--evo-tooltip-vertical-position-arrow);
- }
- }
-
- &_right {
- &:before {
- top: 50%;
- transform: translateY(-50%);
- }
- }
-
- &_right-end {
- &:before {
- bottom: var(--evo-tooltip-vertical-position-arrow);
+ transform: rotate(90deg);
}
}
@@ -103,28 +58,7 @@
&_bottom,
&_bottom-end {
&:before {
- border-width: 0 $arrow-size $arrow-size $arrow-size;
- border-color: transparent transparent var(--evo-tooltip-background-color) transparent;
- top: -$arrow-size;
- }
- }
-
- &_bottom-start {
- &:before {
- left: var(--evo-tooltip-horizontal-position-arrow);
- }
- }
-
- &_bottom {
- &:before {
- left: 50%;
- transform: translateX(-50%);
- }
- }
-
- &_bottom-end {
- &:before {
- right: var(--evo-tooltip-horizontal-position-arrow);
+ transform: rotate(180deg);
}
}
@@ -132,28 +66,7 @@
&_left,
&_left-end {
&:before {
- border-width: $arrow-size 0 $arrow-size $arrow-size;
- border-color: transparent transparent transparent var(--evo-tooltip-background-color);
- right: -$arrow-size;
- }
- }
-
- &_left-start {
- &:before {
- top: var(--evo-tooltip-vertical-position-arrow);
- }
- }
-
- &_left {
- &:before {
- top: 50%;
- transform: translateY(-50%);
- }
- }
-
- &_left-end {
- &:before {
- bottom: var(--evo-tooltip-vertical-position-arrow);
+ transform: rotate(-90deg);
}
}
}
diff --git a/projects/evo-ui-kit/src/lib/components/evo-tooltip/evo-tooltip.component.spec.ts b/projects/evo-ui-kit/src/lib/components/evo-tooltip/evo-tooltip.component.spec.ts
index 3ce4b0b11..d11bc25a9 100644
--- a/projects/evo-ui-kit/src/lib/components/evo-tooltip/evo-tooltip.component.spec.ts
+++ b/projects/evo-ui-kit/src/lib/components/evo-tooltip/evo-tooltip.component.spec.ts
@@ -1,17 +1,17 @@
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {EvoTooltipComponent} from './evo-tooltip.component';
import {EvoTooltipService} from './services/evo-tooltip.service';
-import {NO_ERRORS_SCHEMA, Component, ViewChild, TemplateRef} from '@angular/core';
+import {Component, ElementRef, NO_ERRORS_SCHEMA, TemplateRef, ViewChild} from '@angular/core';
import {EvoTooltipPosition} from './enums/evo-tooltip-position';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {EvoTooltipStyles} from './interfaces/evo-tooltip-styles';
-import {EvoTooltipVariableArrowPosition} from './enums/evo-tooltip-variable-arrow-position';
-import {CommonModule} from '@angular/common';
+import {EvoTooltipStyleVariable} from './enums/evo-tooltip-style-variable';
@Component({
selector: 'evo-host-component',
template: `
-
+ Parent
+
Test template content
@@ -20,6 +20,7 @@ import {CommonModule} from '@angular/common';
class TestHostComponent {
@ViewChild(EvoTooltipComponent, {static: true}) tooltipComponent: EvoTooltipComponent;
@ViewChild('testTemplate', {static: true}) testTemplate: TemplateRef;
+ @ViewChild('tooltipParent', {static: true}) parentRef: ElementRef
}
describe('EvoTooltipComponent', () => {
@@ -31,8 +32,8 @@ describe('EvoTooltipComponent', () => {
beforeEach(
waitForAsync(() => {
TestBed.configureTestingModule({
- declarations: [EvoTooltipComponent, TestHostComponent],
- imports: [BrowserAnimationsModule, CommonModule],
+ declarations: [TestHostComponent],
+ imports: [BrowserAnimationsModule, EvoTooltipComponent],
schemas: [NO_ERRORS_SCHEMA],
providers: [EvoTooltipService],
}).compileComponents();
@@ -44,6 +45,7 @@ describe('EvoTooltipComponent', () => {
testHostComponent = testHostFixture.componentInstance;
tooltipComponent = testHostComponent.tooltipComponent;
tooltipService = TestBed.inject(EvoTooltipService);
+ tooltipService['_parentRef$'].next(testHostComponent.parentRef);
testHostFixture.detectChanges();
});
@@ -56,9 +58,7 @@ describe('EvoTooltipComponent', () => {
tooltipService['_position$'].next(position);
testHostFixture.detectChanges();
- tooltipComponent.position$.subscribe((value) => {
- expect(value).toBe(position);
- });
+ expect(tooltipComponent.position()).toBe(position);
});
it('should update string content when stringContent$ changes', () => {
@@ -66,9 +66,7 @@ describe('EvoTooltipComponent', () => {
tooltipService['_stringContent$'].next(content);
testHostFixture.detectChanges();
- tooltipComponent.stringContent$.subscribe((value) => {
- expect(value).toBe(content);
- });
+ expect(tooltipComponent.stringContent()).toBe(content);
});
it('should update template content when templateContent$ changes', () => {
@@ -76,21 +74,21 @@ describe('EvoTooltipComponent', () => {
tooltipService['_templateContent$'].next(template);
testHostFixture.detectChanges();
- tooltipComponent.templateContent$.subscribe((value) => {
- expect(value).toBe(template);
- });
+ expect(tooltipComponent.templateContent()).toBe(template);
});
it('should update styles when styles$ changes', () => {
const styles: EvoTooltipStyles = {
- [EvoTooltipVariableArrowPosition.VERTICAL_POSITION_ARROW]: '10px',
- [EvoTooltipVariableArrowPosition.HORIZONTAL_POSITION_ARROW]: '20px',
+ [EvoTooltipStyleVariable.MAX_WIDTH]: 'auto',
+ [EvoTooltipStyleVariable.PADDING]: 0,
};
tooltipService['_styles$'].next(styles);
testHostFixture.detectChanges();
- tooltipComponent.styles$.subscribe((value) => {
- expect(value).toEqual(styles);
+ const tooltipStyles: EvoTooltipStyles = tooltipComponent.styles();
+
+ Object.keys(styles).forEach((key): void => {
+ expect(tooltipStyles[key]).toEqual(styles[key]);
});
});
@@ -99,14 +97,6 @@ describe('EvoTooltipComponent', () => {
tooltipService['_visibleArrow$'].next(visibleArrow);
testHostFixture.detectChanges();
- tooltipComponent.visibleArrow$.subscribe((value) => {
- expect(value).toBe(visibleArrow);
- });
- });
-
- it('should unsubscribe from all observables on destroy', () => {
- const destroySpy = spyOn(tooltipComponent['_destroy$'], 'next');
- tooltipComponent.ngOnDestroy();
- expect(destroySpy).toHaveBeenCalled();
+ expect(tooltipComponent.visibleArrow()).toBe(visibleArrow);
});
});
diff --git a/projects/evo-ui-kit/src/lib/components/evo-tooltip/evo-tooltip.component.ts b/projects/evo-ui-kit/src/lib/components/evo-tooltip/evo-tooltip.component.ts
index 9fb9f2eea..2d2a7635d 100644
--- a/projects/evo-ui-kit/src/lib/components/evo-tooltip/evo-tooltip.component.ts
+++ b/projects/evo-ui-kit/src/lib/components/evo-tooltip/evo-tooltip.component.ts
@@ -2,22 +2,48 @@ import {
AfterViewInit,
ChangeDetectionStrategy,
Component,
+ DestroyRef,
ElementRef,
HostBinding,
+ inject,
OnDestroy,
- OnInit,
Renderer2,
+ Signal,
TemplateRef,
} from '@angular/core';
-import {BehaviorSubject, combineLatest, EMPTY, Observable, Subject} from 'rxjs';
-import {filter, map, pairwise, startWith, takeUntil, tap} from 'rxjs/operators';
-import {EVO_TOOLTIP_FADEIN_ANIMATION} from './constants/evo-tooltip-fadein.animation';
-import {EvoTooltipService} from './services/evo-tooltip.service';
-import {EvoTooltipStyles} from './interfaces/evo-tooltip-styles';
-import {EvoTooltipPosition} from './enums/evo-tooltip-position';
+import {combineLatest} from 'rxjs';
+import {map, pairwise, startWith, tap} from 'rxjs/operators';
import {EVO_TOOLTIP_ARROW_SIZE} from './constants/evo-tooltip-arrow-size';
+import {EVO_TOOLTIP_FADEIN_ANIMATION} from './constants/evo-tooltip-fadein.animation';
import {EVO_TOOLTIP_RADIUS} from './constants/evo-tooltip-radius';
-import {EvoTooltipVariableArrowPosition} from './enums/evo-tooltip-variable-arrow-position';
+import {EvoTooltipPosition} from './enums/evo-tooltip-position';
+import {EvoTooltipStyleVariable} from './enums/evo-tooltip-style-variable';
+import {EvoTooltipStyles} from './interfaces/evo-tooltip-styles';
+import {EvoTooltipService} from './services/evo-tooltip.service';
+import {takeUntilDestroyed, toSignal} from "@angular/core/rxjs-interop";
+import {NgTemplateOutlet} from "@angular/common";
+
+const START_POSITIONS_LIST: ReadonlyArray = [
+ EvoTooltipPosition.TOP_START,
+ EvoTooltipPosition.BOTTOM_START,
+ EvoTooltipPosition.LEFT_START,
+ EvoTooltipPosition.RIGHT_START,
+];
+
+const END_POSITIONS_LIST: ReadonlyArray = [
+ EvoTooltipPosition.TOP_END,
+ EvoTooltipPosition.BOTTOM_END,
+ EvoTooltipPosition.LEFT_END,
+ EvoTooltipPosition.RIGHT_END,
+];
+
+interface TooltipArrowCalcParams {
+ parentStart: number;
+ parentEnd: number;
+ tooltipStart: number;
+ tooltipEnd: number;
+ position: EvoTooltipPosition;
+}
@Component({
selector: 'evo-tooltip',
@@ -25,51 +51,45 @@ import {EvoTooltipVariableArrowPosition} from './enums/evo-tooltip-variable-arro
styleUrls: ['./evo-tooltip.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
animations: [EVO_TOOLTIP_FADEIN_ANIMATION],
+ standalone: true,
+ imports: [
+ NgTemplateOutlet
+ ]
})
-export class EvoTooltipComponent implements OnInit, AfterViewInit, OnDestroy {
- readonly position$: Observable = this.tooltipService.position$;
- readonly stringContent$: Observable = this.tooltipService.stringContent$;
- readonly templateContent$: Observable> = this.tooltipService.templateContent$;
- readonly visibleArrow$: Observable = this.tooltipService.visibleArrow$;
- readonly styles$: Observable = EMPTY;
+export class EvoTooltipComponent implements AfterViewInit, OnDestroy {
+ readonly position: Signal = toSignal(this.tooltipService.position$);
+ readonly stringContent: Signal = toSignal(this.tooltipService.stringContent$);
+ readonly templateContent: Signal> = toSignal(this.tooltipService.templateContent$);
+ readonly visibleArrow: Signal = toSignal(this.tooltipService.visibleArrow$);
+
+ readonly styles: Signal = toSignal(combineLatest([
+ this.tooltipService.position$,
+ this.tooltipService.styles$,
+ this.tooltipService.parentRef$,
+ this.tooltipService.visibleArrow$,
+ ]).pipe(
+ map(
+ ([position, baseStyles, parentRef, visibleArrow]: [
+ EvoTooltipPosition,
+ EvoTooltipStyles,
+ ElementRef,
+ boolean,
+ ]): EvoTooltipStyles =>
+ visibleArrow && parentRef
+ ? {...baseStyles, ...this.calculateArrowStyles(parentRef, position)}
+ : baseStyles,
+ ),
+ ));
@HostBinding('@fadeIn') fadeIn = true;
- private readonly _positionArrowStyles$ = new BehaviorSubject(null);
- private readonly _destroy$ = new Subject();
+ private readonly destroyRef = inject(DestroyRef);
constructor(
private readonly elementRef: ElementRef,
private readonly tooltipService: EvoTooltipService,
private readonly renderer: Renderer2,
) {
- this.styles$ = combineLatest([this.tooltipService.styles$, this._positionArrowStyles$]).pipe(
- map(([style1, style2]) => ({...style1, ...style2})),
- );
- }
-
- ngOnInit(): void {
- combineLatest([this.position$, this.tooltipService.parentRef$, this.visibleArrow$])
- .pipe(
- filter(([_position, _parentRef, visibleArrow]) => visibleArrow),
- // Вычисление стрелки нужно только для угловых позиций
- filter(([position]) => {
- switch (position) {
- case EvoTooltipPosition.TOP:
- case EvoTooltipPosition.RIGHT:
- case EvoTooltipPosition.BOTTOM:
- case EvoTooltipPosition.LEFT:
- return false;
- default:
- return true;
- }
- }),
- tap(([_, parentRef]) => {
- this.setArrowPosition(parentRef);
- }),
- takeUntil(this._destroy$),
- )
- .subscribe();
}
ngAfterViewInit(): void {
@@ -81,45 +101,78 @@ export class EvoTooltipComponent implements OnInit, AfterViewInit, OnDestroy {
(a || []).forEach((oldClass) => this.renderer.removeClass(this.elementRef.nativeElement, oldClass));
(b || []).forEach((newClass) => this.renderer.addClass(this.elementRef.nativeElement, newClass));
}),
- takeUntil(this._destroy$),
+ takeUntilDestroyed(this.destroyRef),
)
.subscribe();
}
ngOnDestroy(): void {
this.fadeIn = false;
- this._destroy$.next();
- this._destroy$.complete();
}
- private setArrowPosition(parentRef: ElementRef): void {
- // Для того чтобы стрелка тянулась к центру родителя - берем середину
- const widthParent = parentRef.nativeElement.offsetWidth / 2;
- const heightParent = parentRef.nativeElement.offsetHeight / 2;
- const isParentLonger = widthParent >= this.elementRef.nativeElement.offsetWidth;
- const isParentHigher = heightParent >= this.elementRef.nativeElement.offsetHeight;
- // Если середина родителя оказывается меньше тултипа - берем середину родителя иначе размер тултипа
- // Это проверка на максимальное смещение, смещение стрелки не должно быть больше размера тултипа
- const width = isParentLonger ? this.elementRef.nativeElement.offsetWidth : widthParent;
- const height = isParentHigher ? this.elementRef.nativeElement.offsetHeight : heightParent;
-
- const positionArrow = (size: number, isParentBigger: boolean): number =>
- // Если середина родителя больше тултипа
- // То берем размер тултипа и отнимаем размер стрелки и радиуса
- // Иначе берем середину родителя и отнимаем половину стрелки
- // Это условие нужно чтобы стрелка не смещалась
- isParentBigger ? size - EVO_TOOLTIP_ARROW_SIZE - EVO_TOOLTIP_RADIUS : size - EVO_TOOLTIP_ARROW_SIZE / 2;
- let verticalPositionArrow = positionArrow(height, isParentHigher);
- let horizontalPositionArrow = positionArrow(width, isParentLonger);
-
- // Проверка на минимальное смещение, смещение стрелки не должно быть меньше размера радиуса тултипа 8px
- horizontalPositionArrow =
- horizontalPositionArrow > EVO_TOOLTIP_RADIUS ? horizontalPositionArrow : EVO_TOOLTIP_RADIUS;
- verticalPositionArrow = verticalPositionArrow > EVO_TOOLTIP_RADIUS ? verticalPositionArrow : EVO_TOOLTIP_RADIUS;
-
- this._positionArrowStyles$.next({
- [EvoTooltipVariableArrowPosition.VERTICAL_POSITION_ARROW]: `${verticalPositionArrow}px`,
- [EvoTooltipVariableArrowPosition.HORIZONTAL_POSITION_ARROW]: `${horizontalPositionArrow}px`,
+ private getArrowStartEdge({parentStart, parentEnd, tooltipStart, position}: TooltipArrowCalcParams): number {
+ const parentWidth = parentEnd - parentStart;
+ const parentCenter = Math.round(parentWidth / 2);
+
+ const arrowHalf = EVO_TOOLTIP_ARROW_SIZE / 2;
+ const safeBoundary = EVO_TOOLTIP_RADIUS + arrowHalf;
+
+ let idealCenter: number;
+
+ if (START_POSITIONS_LIST.includes(position)) {
+ idealCenter = Math.min(parentCenter, safeBoundary);
+ } else if (END_POSITIONS_LIST.includes(position)) {
+ idealCenter = Math.max(parentCenter, parentWidth - safeBoundary);
+ } else {
+ idealCenter = parentCenter;
+ }
+
+ return Math.round(idealCenter + parentStart - tooltipStart - arrowHalf);
+ }
+
+ private getArrowOffset(params: TooltipArrowCalcParams): number {
+ const tooltipSize = params.tooltipEnd - params.tooltipStart;
+
+ // tooltip after the parent
+ if (params.parentEnd < params.tooltipStart) {
+ return -EVO_TOOLTIP_ARROW_SIZE;
+ }
+
+ // tooltip before the parent
+ if (params.parentStart > params.tooltipEnd) {
+ return tooltipSize;
+ }
+
+ const minPosition = EVO_TOOLTIP_RADIUS;
+ const arrowStartEdge = this.getArrowStartEdge(params);
+ const maxPosition = tooltipSize - EVO_TOOLTIP_RADIUS - EVO_TOOLTIP_ARROW_SIZE;
+
+ return Math.max(minPosition, Math.min(arrowStartEdge, maxPosition));
+ }
+
+ private calculateArrowStyles(parentRef: ElementRef, position: EvoTooltipPosition): EvoTooltipStyles {
+ const parentRect = (parentRef.nativeElement as HTMLElement).getBoundingClientRect();
+ const tooltipRect = (this.elementRef.nativeElement as HTMLElement).getBoundingClientRect();
+
+ const vertical = this.getArrowOffset({
+ parentStart: parentRect.top,
+ parentEnd: parentRect.bottom,
+ tooltipStart: tooltipRect.top,
+ tooltipEnd: tooltipRect.bottom,
+ position,
+ });
+
+ const horizontal = this.getArrowOffset({
+ parentStart: parentRect.left,
+ parentEnd: parentRect.right,
+ tooltipStart: tooltipRect.left,
+ tooltipEnd: tooltipRect.right,
+ position,
});
+
+ return {
+ [EvoTooltipStyleVariable.VERTICAL_POSITION_ARROW]: `${vertical}px`,
+ [EvoTooltipStyleVariable.HORIZONTAL_POSITION_ARROW]: `${horizontal}px`,
+ };
}
}
diff --git a/projects/evo-ui-kit/src/lib/components/evo-tooltip/evo-tooltip.module.ts b/projects/evo-ui-kit/src/lib/components/evo-tooltip/evo-tooltip.module.ts
index 4c2c36cf7..b15de7170 100644
--- a/projects/evo-ui-kit/src/lib/components/evo-tooltip/evo-tooltip.module.ts
+++ b/projects/evo-ui-kit/src/lib/components/evo-tooltip/evo-tooltip.module.ts
@@ -1,12 +1,9 @@
import {NgModule} from '@angular/core';
-import {CommonModule} from '@angular/common';
-import {EvoTooltipComponent} from './evo-tooltip.component';
import {EvoTooltipDirective} from './directives/evo-tooltip.directive';
-import {OverlayModule} from '@angular/cdk/overlay';
@NgModule({
- imports: [OverlayModule, CommonModule],
- declarations: [EvoTooltipComponent, EvoTooltipDirective],
+ imports: [EvoTooltipDirective],
exports: [EvoTooltipDirective],
})
-export class EvoTooltipModule {}
+export class EvoTooltipModule {
+}
diff --git a/projects/evo-ui-kit/src/lib/components/evo-tooltip/interfaces/evo-tooltip-config.ts b/projects/evo-ui-kit/src/lib/components/evo-tooltip/interfaces/evo-tooltip-config.ts
index 097510ff3..0b31df2c8 100644
--- a/projects/evo-ui-kit/src/lib/components/evo-tooltip/interfaces/evo-tooltip-config.ts
+++ b/projects/evo-ui-kit/src/lib/components/evo-tooltip/interfaces/evo-tooltip-config.ts
@@ -1,5 +1,8 @@
+import {EvoTooltipScrollStrategy} from '../types/evo-tooltip-scroll-strategy';
+
export interface EvoTooltipConfig {
// The default delay in ms before hiding the tooltip
hideDelay?: number;
showDelay?: number;
+ scrollStrategy?: EvoTooltipScrollStrategy;
}
diff --git a/projects/evo-ui-kit/src/lib/components/evo-tooltip/interfaces/evo-tooltip-styles.ts b/projects/evo-ui-kit/src/lib/components/evo-tooltip/interfaces/evo-tooltip-styles.ts
index ff1afa77f..fd118f006 100644
--- a/projects/evo-ui-kit/src/lib/components/evo-tooltip/interfaces/evo-tooltip-styles.ts
+++ b/projects/evo-ui-kit/src/lib/components/evo-tooltip/interfaces/evo-tooltip-styles.ts
@@ -1,5 +1,14 @@
-import {EvoTooltipVariableArrowPosition} from '../enums/evo-tooltip-variable-arrow-position';
+import {EvoTooltipStyleVariable} from '../enums/evo-tooltip-style-variable';
-export type EvoTooltipStyles = {
- [key in EvoTooltipVariableArrowPosition]: string;
+export interface EvoTooltipStyles {
+ [EvoTooltipStyleVariable.HORIZONTAL_POSITION_ARROW]?: string;
+ [EvoTooltipStyleVariable.VERTICAL_POSITION_ARROW]?: string;
+
+ [EvoTooltipStyleVariable.COLOR]?: string;
+ [EvoTooltipStyleVariable.BACKGROUND_COLOR]?: string;
+
+ [EvoTooltipStyleVariable.MAX_WIDTH]?: string | number;
+
+ [EvoTooltipStyleVariable.PADDING]?: string | number;
+ [EvoTooltipStyleVariable.BORDER_RADIUS]?: string | number;
};
diff --git a/projects/evo-ui-kit/src/lib/components/evo-tooltip/public-api.ts b/projects/evo-ui-kit/src/lib/components/evo-tooltip/public-api.ts
index 06bf44990..79a592ce9 100644
--- a/projects/evo-ui-kit/src/lib/components/evo-tooltip/public-api.ts
+++ b/projects/evo-ui-kit/src/lib/components/evo-tooltip/public-api.ts
@@ -1,2 +1,7 @@
export * from './evo-tooltip.module';
export * from './directives/evo-tooltip.directive';
+export * from './types/evo-tooltip-position-type';
+export * from './enums/evo-tooltip-position';
+export * from './interfaces/evo-tooltip-config';
+export * from './interfaces/evo-tooltip-styles';
+export * from './services/evo-tooltip.service';
diff --git a/projects/evo-ui-kit/src/lib/components/evo-tooltip/services/evo-tooltip.service.spec.ts b/projects/evo-ui-kit/src/lib/components/evo-tooltip/services/evo-tooltip.service.spec.ts
index 498201c13..6f9221459 100644
--- a/projects/evo-ui-kit/src/lib/components/evo-tooltip/services/evo-tooltip.service.spec.ts
+++ b/projects/evo-ui-kit/src/lib/components/evo-tooltip/services/evo-tooltip.service.spec.ts
@@ -4,7 +4,7 @@ import {EvoTooltipPosition} from '../enums/evo-tooltip-position';
import {EvoTooltipStyles} from '../interfaces/evo-tooltip-styles';
import {ElementRef} from '@angular/core';
import {first} from 'rxjs/operators';
-import {EvoTooltipVariableArrowPosition} from '../enums/evo-tooltip-variable-arrow-position';
+import {EvoTooltipStyleVariable} from '../enums/evo-tooltip-style-variable';
import {CommonModule} from '@angular/common';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {EvoTooltipComponent} from '../evo-tooltip.component';
@@ -16,8 +16,7 @@ describe('EvoTooltipService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
- imports: [CommonModule, BrowserAnimationsModule],
- declarations: [EvoTooltipComponent],
+ imports: [CommonModule, BrowserAnimationsModule, EvoTooltipComponent],
schemas: [NO_ERRORS_SCHEMA],
providers: [EvoTooltipService],
});
@@ -46,8 +45,8 @@ describe('EvoTooltipService', () => {
it('should set tooltip styles', () => {
const styles: EvoTooltipStyles = {
- [EvoTooltipVariableArrowPosition.VERTICAL_POSITION_ARROW]: '10px',
- [EvoTooltipVariableArrowPosition.HORIZONTAL_POSITION_ARROW]: '20px',
+ [EvoTooltipStyleVariable.VERTICAL_POSITION_ARROW]: '10px',
+ [EvoTooltipStyleVariable.HORIZONTAL_POSITION_ARROW]: '20px',
};
service.setTooltipStyles(styles);
service.styles$.pipe(first()).subscribe((value) => {
diff --git a/projects/evo-ui-kit/src/lib/components/evo-tooltip/services/evo-tooltip.service.ts b/projects/evo-ui-kit/src/lib/components/evo-tooltip/services/evo-tooltip.service.ts
index 26c3e1f0d..978ca940c 100644
--- a/projects/evo-ui-kit/src/lib/components/evo-tooltip/services/evo-tooltip.service.ts
+++ b/projects/evo-ui-kit/src/lib/components/evo-tooltip/services/evo-tooltip.service.ts
@@ -1,28 +1,28 @@
-import {ComponentRef, ElementRef, Injectable, Injector, TemplateRef} from '@angular/core';
-import {ComponentPortal} from '@angular/cdk/portal';
import {
ConnectedPosition,
FlexibleConnectedPositionStrategy,
Overlay,
OverlayPositionBuilder,
OverlayRef,
+ ScrollStrategy,
} from '@angular/cdk/overlay';
-import {BehaviorSubject, EMPTY, fromEvent, merge, Observable, Subject} from 'rxjs';
-import {catchError, debounceTime, filter, first, map} from 'rxjs/operators';
-import {EvoTooltipConfig} from '../interfaces/evo-tooltip-config';
-import {EVO_TOOLTIP_CONFIG} from '../constants/evo-tooltip-config';
-import {EvoTooltipComponent} from '../evo-tooltip.component';
-import {EvoTooltipPosition} from '../enums/evo-tooltip-position';
+import {ComponentPortal} from '@angular/cdk/portal';
+import {ComponentRef, ElementRef, Injectable, Injector, OnDestroy, TemplateRef} from '@angular/core';
+import {BehaviorSubject, EMPTY, merge, Observable, Subject} from 'rxjs';
+import {filter, take, takeUntil, tap} from 'rxjs/operators';
+import {EVO_TOOLTIP_ARROW_SIZE} from '../constants/evo-tooltip-arrow-size';
import {EVO_CONNECTED_POSITION} from '../constants/evo-tooltip-connected-position';
-import {EVO_PRIORITY_POSITIONS_ORDER} from '../constants/evo-tooltip-priority-positions-order';
import {EVO_DEFAULT_POSITIONS_ORDER} from '../constants/evo-tooltip-default-positions-order';
-import {EVO_TOOLTIP_ARROW_SIZE} from '../constants/evo-tooltip-arrow-size';
+import {EVO_TOOLTIP_OFFSET} from '../constants/evo-tooltip-offset';
+import {EVO_PRIORITY_POSITIONS_ORDER} from '../constants/evo-tooltip-priority-positions-order';
import {EVO_TOOLTIP_RADIUS} from '../constants/evo-tooltip-radius';
+import {EvoTooltipPosition} from '../enums/evo-tooltip-position';
+import {EvoTooltipComponent} from '../evo-tooltip.component';
import {EvoTooltipStyles} from '../interfaces/evo-tooltip-styles';
-import {EVO_TOOLTIP_OFFSET} from '../constants/evo-tooltip-offset';
+import {EvoTooltipConfig} from '../public-api';
@Injectable()
-export class EvoTooltipService {
+export class EvoTooltipService implements OnDestroy {
readonly stringContent$: Observable = EMPTY;
readonly templateContent$: Observable | null> = EMPTY;
readonly position$: Observable = EMPTY;
@@ -37,15 +37,14 @@ export class EvoTooltipService {
private readonly _position$ = new BehaviorSubject(EvoTooltipPosition.BOTTOM);
private readonly _parentRef$ = new BehaviorSubject(null);
private readonly _tooltipClasses$ = new BehaviorSubject([]);
- private readonly _config$ = new BehaviorSubject(EVO_TOOLTIP_CONFIG);
private readonly _styles$ = new BehaviorSubject(null);
private readonly _visibleArrow$ = new BehaviorSubject(true);
private readonly _isOpen$ = new Subject();
+ private readonly destroy$ = new Subject();
- private overlayRef: OverlayRef;
- private positionStrategy: FlexibleConnectedPositionStrategy;
+ private overlayRef: OverlayRef | null = null;
+ private positionStrategy: FlexibleConnectedPositionStrategy | null = null;
private tooltipComponentRef: ComponentRef | null;
- private targetElement: EventTarget | null;
constructor(
private readonly overlay: Overlay,
@@ -62,24 +61,27 @@ export class EvoTooltipService {
this.isOpen$ = this._isOpen$.asObservable();
}
+ ngOnDestroy(): void {
+ this.destroy$.next();
+ }
+
showTooltip(
parentRef: ElementRef,
content: string | TemplateRef,
position: EvoTooltipPosition,
config: EvoTooltipConfig,
- targetElement?: EventTarget | null,
- ): void {
+ ): HTMLElement {
this._parentRef$.next(parentRef);
- this.targetElement = targetElement ?? null;
this.setContent(content);
this._position$.next(position);
- this._config$.next(config);
- this.createOverlay(parentRef, position);
+ this.createOverlay(parentRef, position, config);
this.createPortal();
- this._isOpen$.next(this.overlayRef.hasAttached());
+ this._isOpen$.next(this.hasAttached);
this.initSubscriptions();
+
+ return this.overlayRef.overlayElement;
}
hideTooltip(): void {
@@ -89,7 +91,7 @@ export class EvoTooltipService {
}
this.overlayRef?.detach();
- this._isOpen$.next(!!this.overlayRef?.hasAttached());
+ this._isOpen$.next(this.hasAttached);
}
setArrowVisibility(hasArrow: boolean): void {
@@ -105,8 +107,8 @@ export class EvoTooltipService {
!tooltipClassOrClasses
? []
: Array.isArray(tooltipClassOrClasses)
- ? tooltipClassOrClasses
- : [tooltipClassOrClasses],
+ ? tooltipClassOrClasses
+ : [tooltipClassOrClasses],
);
}
@@ -114,10 +116,6 @@ export class EvoTooltipService {
return this.overlayRef?.hasAttached() ?? false;
}
- get config(): EvoTooltipConfig {
- return this._config$.value;
- }
-
private get parentRef(): ElementRef {
return this._parentRef$.value;
}
@@ -130,13 +128,38 @@ export class EvoTooltipService {
}
}
- private createOverlay(elementRef: ElementRef, position: EvoTooltipPosition): void {
+ private createOverlay(elementRef: ElementRef, position: EvoTooltipPosition, config: EvoTooltipConfig): void {
this.positionStrategy = this.overlayPositionBuilder
.flexibleConnectedTo(elementRef)
.withPositions(this.getPositions(position));
- const scrollStrategy = this.overlay.scrollStrategies.reposition();
- this.overlayRef = this.overlay.create({positionStrategy: this.positionStrategy, scrollStrategy});
+ this.overlayRef = this.overlay.create({
+ positionStrategy: this.positionStrategy,
+ scrollStrategy: this.getScrollStrategy(config)
+ });
+ }
+
+ private getScrollStrategy(config: EvoTooltipConfig): ScrollStrategy {
+ switch (config?.scrollStrategy) {
+ case 'reposition': {
+ return this.overlay.scrollStrategies.reposition();
+ }
+
+ case 'block': {
+ return this.overlay.scrollStrategies.block();
+ }
+
+ case 'noop': {
+ return this.overlay.scrollStrategies.noop();
+ }
+
+ case 'close':
+ default: {
+ return this.overlay.scrollStrategies.close({
+ threshold: 10,
+ });
+ }
+ }
}
private createPortal(): void {
@@ -145,30 +168,29 @@ export class EvoTooltipService {
}
private initSubscriptions(): void {
- const parentElement = this.targetElement ? this.targetElement : this.parentRef?.nativeElement;
- const overlayElement = this.overlayRef.overlayElement;
-
- const mouseLeaveParentElement$ = fromEvent(parentElement, 'mouseleave').pipe(map(() => overlayElement));
- const mouseLeaveOverlayElement$ = fromEvent(overlayElement, 'mouseleave').pipe(map(() => parentElement));
-
- merge(mouseLeaveParentElement$, mouseLeaveOverlayElement$)
- .pipe(
- filter((element) => !element.matches(':hover')),
- debounceTime(this.config?.hideDelay ?? EVO_TOOLTIP_CONFIG.hideDelay),
- filter(() => !parentElement.matches(':hover') && !overlayElement.matches(':hover')),
- first(),
- catchError(() => {
- this.hideTooltip();
- return EMPTY;
- }),
- )
- .subscribe(() => {
- this.hideTooltip();
+ if (this.positionStrategy) {
+ this.positionStrategy.positionChanges.pipe(
+ takeUntil(
+ merge(this.destroy$, this._isOpen$.pipe(
+ filter((isOpened: boolean) => !isOpened),
+ ))
+ ),
+ ).subscribe((value) => {
+ this._position$.next(value.connectionPair.panelClass as EvoTooltipPosition);
});
+ }
- this.positionStrategy.positionChanges.subscribe((value) => {
- this._position$.next(value.connectionPair.panelClass as EvoTooltipPosition);
- });
+ if (this.overlayRef) {
+ this.overlayRef.detachments().pipe(
+ take(1),
+ tap(() => {
+ this.positionStrategy = null;
+ this.overlayRef = null;
+ this._isOpen$.next(false);
+ }),
+ takeUntil(this.destroy$),
+ ).subscribe();
+ }
}
private getPositions(position: EvoTooltipPosition): ConnectedPosition[] {
diff --git a/projects/evo-ui-kit/src/lib/components/evo-tooltip/types/evo-tooltip-scroll-strategy.ts b/projects/evo-ui-kit/src/lib/components/evo-tooltip/types/evo-tooltip-scroll-strategy.ts
new file mode 100644
index 000000000..e899cd4ce
--- /dev/null
+++ b/projects/evo-ui-kit/src/lib/components/evo-tooltip/types/evo-tooltip-scroll-strategy.ts
@@ -0,0 +1 @@
+export type EvoTooltipScrollStrategy = 'noop' | 'close' | 'reposition' | 'block';
diff --git a/projects/evo-ui-kit/src/lib/components/evo-tooltip/assets/evo-tooltip-global.scss b/projects/evo-ui-kit/src/lib/styles/components/evo-tooltip-trigger.scss
similarity index 100%
rename from projects/evo-ui-kit/src/lib/components/evo-tooltip/assets/evo-tooltip-global.scss
rename to projects/evo-ui-kit/src/lib/styles/components/evo-tooltip-trigger.scss
diff --git a/projects/evo-ui-kit/src/lib/styles/main.scss b/projects/evo-ui-kit/src/lib/styles/main.scss
index 1f5990798..21c191fb1 100644
--- a/projects/evo-ui-kit/src/lib/styles/main.scss
+++ b/projects/evo-ui-kit/src/lib/styles/main.scss
@@ -22,3 +22,4 @@
@import './components/evo-form.scss';
@import './components/evo-dropdown.scss';
@import './components/skeleton.scss';
+@import "components/evo-tooltip-trigger";
diff --git a/src/stories/components/evo-tooltip.stories.ts b/src/stories/components/evo-tooltip.stories.ts
new file mode 100644
index 000000000..e611e8bc4
--- /dev/null
+++ b/src/stories/components/evo-tooltip.stories.ts
@@ -0,0 +1,96 @@
+import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
+import {EvoButtonModule, EvoTooltipModule, EvoTooltipPosition} from '@evotor-dev/ui-kit';
+import {moduleMetadata} from '@storybook/angular';
+
+export default {
+ title: 'Components/Tooltip',
+
+ decorators: [
+ moduleMetadata({
+ imports: [EvoTooltipModule, EvoButtonModule, BrowserAnimationsModule],
+ }),
+ ],
+};
+
+const POSITIONS_LIST: EvoTooltipPosition[] = [
+ EvoTooltipPosition.TOP_START,
+ EvoTooltipPosition.TOP,
+ EvoTooltipPosition.TOP_END,
+
+ EvoTooltipPosition.BOTTOM_START,
+ EvoTooltipPosition.BOTTOM,
+ EvoTooltipPosition.BOTTOM_END,
+
+ EvoTooltipPosition.RIGHT_START,
+ EvoTooltipPosition.RIGHT,
+ EvoTooltipPosition.RIGHT_END,
+
+ EvoTooltipPosition.LEFT_START,
+ EvoTooltipPosition.LEFT,
+ EvoTooltipPosition.LEFT_END,
+];
+
+export const Default = () => ({
+ template: `
+
+
+
+
+
+
+
+ Some tooltip content
+
+
+ `,
+ props: {
+ POSITIONS_LIST: POSITIONS_LIST,
+ isDisabled: false
+ }
+});
+
+Default.storyName = 'default';