From 99923bf435d4e56cfe7872995446b79f9ba66a65 Mon Sep 17 00:00:00 2001 From: "a.kiselev" Date: Mon, 21 Jul 2025 10:38:52 +0300 Subject: [PATCH 01/12] fix(evo-tooltip): fixed behavior --- .../directives/evo-tooltip.directive.ts | 6 +- .../evo-tooltip/evo-tooltip.component.scss | 102 ++---------------- .../evo-tooltip/evo-tooltip.component.ts | 81 +++++++------- .../lib/components/evo-tooltip/public-api.ts | 3 + .../services/evo-tooltip.service.ts | 4 +- 5 files changed, 64 insertions(+), 132 deletions(-) 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..795a8f8c2 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 @@ -9,7 +9,7 @@ import { Output, TemplateRef, } from '@angular/core'; -import {fromEvent, Observable, Subject} from 'rxjs'; +import {fromEvent, merge, 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'; @@ -80,7 +80,9 @@ export class EvoTooltipDirective implements OnInit, OnDestroy { } 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(() => { 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..d4fb1cd23 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,8 +15,6 @@ } .evo-tooltip { - $arrow-size: 8px; - display: inline-block; position: relative; background-color: var(--evo-tooltip-background-color); @@ -39,34 +37,19 @@ content: ""; position: absolute; border-style: solid; + left: var(--evo-tooltip-horizontal-position-arrow); + top: var(--evo-tooltip-vertical-position-arrow); + width: 0; + height: 0; + border-width: 0 8px 8px 8px; + border-color: transparent transparent var(--evo-tooltip-background-color) transparent; } &_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); + transform: rotate(180deg); } } @@ -74,28 +57,7 @@ &_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) translateX(-4px) translateY(-12px); } } @@ -103,28 +65,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: translateY(-8px); } } @@ -132,28 +73,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) translateX(4px) translateY(4px); } } } 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..7d781f96c 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 @@ -16,7 +16,6 @@ import {EvoTooltipService} from './services/evo-tooltip.service'; import {EvoTooltipStyles} from './interfaces/evo-tooltip-styles'; import {EvoTooltipPosition} from './enums/evo-tooltip-position'; import {EVO_TOOLTIP_ARROW_SIZE} from './constants/evo-tooltip-arrow-size'; -import {EVO_TOOLTIP_RADIUS} from './constants/evo-tooltip-radius'; import {EvoTooltipVariableArrowPosition} from './enums/evo-tooltip-variable-arrow-position'; @Component({ @@ -52,18 +51,6 @@ export class EvoTooltipComponent implements OnInit, AfterViewInit, OnDestroy { 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); }), @@ -92,34 +79,52 @@ export class EvoTooltipComponent implements OnInit, AfterViewInit, OnDestroy { this._destroy$.complete(); } + private getArrowOffset(params: { + parentStart: number; + parentEnd: number; + tooltipStart: number; + tooltipEnd: number; + }): number { + const tooltipSize = params.tooltipEnd - params.tooltipStart; + + if (params.parentEnd <= params.tooltipStart) { + return 0; + } + + if (params.parentStart >= params.tooltipEnd) { + return tooltipSize; + } + + const parentSize = params.parentEnd - params.parentStart; + + const parentMiddleOffset = params.parentStart + parentSize / 2; + const elementMiddleOffset = params.tooltipStart + tooltipSize / 2; + + const diff = elementMiddleOffset - parentMiddleOffset; + return elementMiddleOffset - diff - params.tooltipStart - EVO_TOOLTIP_ARROW_SIZE / 2; + } + 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; + 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, + }); + + const horizontal = this.getArrowOffset({ + parentStart: parentRect.left, + parentEnd: parentRect.right, + tooltipStart: tooltipRect.left, + tooltipEnd: tooltipRect.right, + }); this._positionArrowStyles$.next({ - [EvoTooltipVariableArrowPosition.VERTICAL_POSITION_ARROW]: `${verticalPositionArrow}px`, - [EvoTooltipVariableArrowPosition.HORIZONTAL_POSITION_ARROW]: `${horizontalPositionArrow}px`, + [EvoTooltipVariableArrowPosition.VERTICAL_POSITION_ARROW]: `${vertical}px`, + [EvoTooltipVariableArrowPosition.HORIZONTAL_POSITION_ARROW]: `${horizontal}px`, }); } } 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..16718cd89 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,5 @@ export * from './evo-tooltip.module'; export * from './directives/evo-tooltip.directive'; +export * from './types/evo-tooltip-position-type'; +export * from './interfaces/evo-tooltip-config'; +export * from './interfaces/evo-tooltip-styles'; 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..f5da213e3 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 @@ -135,7 +135,9 @@ export class EvoTooltipService { .flexibleConnectedTo(elementRef) .withPositions(this.getPositions(position)); - const scrollStrategy = this.overlay.scrollStrategies.reposition(); + const scrollStrategy = this.overlay.scrollStrategies.close({ + threshold: 10, + }); this.overlayRef = this.overlay.create({positionStrategy: this.positionStrategy, scrollStrategy}); } From 2e3ed97913e0130b7e32e9e8ad83bc2310725e2b Mon Sep 17 00:00:00 2001 From: "a.kiselev" Date: Sat, 26 Jul 2025 13:01:07 +0300 Subject: [PATCH 02/12] fix(evo-tooltip): fixed behavior and tests --- .../directives/evo-tooltip.directive.spec.ts | 18 +++++++-- .../enums/evo-tooltip-style-variable.ts | 12 ++++++ .../evo-tooltip-variable-arrow-position.ts | 4 -- .../evo-tooltip/evo-tooltip.component.scss | 21 ++++------ .../evo-tooltip/evo-tooltip.component.spec.ts | 13 ++++--- .../evo-tooltip/evo-tooltip.component.ts | 38 +++++++++++++------ .../interfaces/evo-tooltip-styles.ts | 15 ++++++-- .../services/evo-tooltip.service.spec.ts | 6 +-- .../services/evo-tooltip.service.ts | 2 +- 9 files changed, 84 insertions(+), 45 deletions(-) create mode 100644 projects/evo-ui-kit/src/lib/components/evo-tooltip/enums/evo-tooltip-style-variable.ts delete mode 100644 projects/evo-ui-kit/src/lib/components/evo-tooltip/enums/evo-tooltip-variable-arrow-position.ts 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 b59bd54de..8633bece5 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 @@ -3,7 +3,7 @@ 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 { 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'; @@ -32,8 +32,8 @@ class TestHostComponent { config = { showDelay: 0, hideDelay: 0 }; visibleArrow = true; styles: EvoTooltipStyles = { - [EvoTooltipVariableArrowPosition.VERTICAL_POSITION_ARROW]: '10px', - [EvoTooltipVariableArrowPosition.HORIZONTAL_POSITION_ARROW]: '20px', + [EvoTooltipStyleVariable.VERTICAL_POSITION_ARROW]: '10px', + [EvoTooltipStyleVariable.HORIZONTAL_POSITION_ARROW]: '20px', }; classes = ['class-1', 'class-2']; onOpen = jasmine.createSpy('onOpen'); @@ -120,4 +120,14 @@ describe('EvoTooltipDirective', () => { expect(isOpen).toBeTrue(); }); })); -}); \ No newline at end of file + + it('should handle touchstart event', fakeAsync(() => { + const element = fixture.debugElement.children[0].nativeElement; + element.dispatchEvent(new MouseEvent('touchstart')); + tick(0); + fixture.detectChanges(); + tooltipService.isOpen$.pipe(first()).subscribe((isOpen) => { + expect(isOpen).toBeTrue(); + }); + })); +}); 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.scss b/projects/evo-ui-kit/src/lib/components/evo-tooltip/evo-tooltip.component.scss index d4fb1cd23..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 @@ -15,6 +15,8 @@ } .evo-tooltip { + $arrow-border-width: 8px; + display: inline-block; position: relative; background-color: var(--evo-tooltip-background-color); @@ -36,28 +38,19 @@ &:not(&_not-arrow):before { content: ""; position: absolute; - border-style: solid; left: var(--evo-tooltip-horizontal-position-arrow); top: var(--evo-tooltip-vertical-position-arrow); width: 0; height: 0; - border-width: 0 8px 8px 8px; - border-color: transparent transparent var(--evo-tooltip-background-color) transparent; - } - - &_top-start, - &_top, - &_top-end { - &:before { - transform: rotate(180deg); - } + border: $arrow-border-width solid transparent; + border-top: $arrow-border-width solid var(--evo-tooltip-background-color); } &_right-start, &_right, &_right-end { &:before { - transform: rotate(-90deg) translateX(-4px) translateY(-12px); + transform: rotate(90deg); } } @@ -65,7 +58,7 @@ &_bottom, &_bottom-end { &:before { - transform: translateY(-8px); + transform: rotate(180deg); } } @@ -73,7 +66,7 @@ &_left, &_left-end { &:before { - transform: rotate(90deg) translateX(4px) translateY(4px); + 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 a18ec84eb..c3d44d4de 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,16 +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 {EvoTooltipStyleVariable} from './enums/evo-tooltip-style-variable'; import {CommonModule} from '@angular/common'; @Component({ selector: 'evo-host-component', template: ` +
Parent
Test template content
@@ -20,6 +21,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', () => { @@ -44,6 +46,7 @@ describe('EvoTooltipComponent', () => { testHostComponent = testHostFixture.componentInstance; tooltipComponent = testHostComponent.tooltipComponent; tooltipService = TestBed.inject(EvoTooltipService); + tooltipService['_parentRef$'].next(testHostComponent.parentRef); testHostFixture.detectChanges(); }); @@ -83,14 +86,14 @@ describe('EvoTooltipComponent', () => { 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); + Object.keys(styles).forEach((key: string) => expect(value[key]).toEqual(styles[key])); }); }); 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 7d781f96c..e0ee5174d 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 @@ -15,8 +15,9 @@ import {EVO_TOOLTIP_FADEIN_ANIMATION} from './constants/evo-tooltip-fadein.anima import {EvoTooltipService} from './services/evo-tooltip.service'; import {EvoTooltipStyles} from './interfaces/evo-tooltip-styles'; import {EvoTooltipPosition} from './enums/evo-tooltip-position'; +import {EvoTooltipStyleVariable} from './enums/evo-tooltip-style-variable'; +import {EVO_TOOLTIP_RADIUS} from './constants/evo-tooltip-radius'; import {EVO_TOOLTIP_ARROW_SIZE} from './constants/evo-tooltip-arrow-size'; -import {EvoTooltipVariableArrowPosition} from './enums/evo-tooltip-variable-arrow-position'; @Component({ selector: 'evo-tooltip', @@ -86,22 +87,37 @@ export class EvoTooltipComponent implements OnInit, AfterViewInit, OnDestroy { tooltipEnd: number; }): number { const tooltipSize = params.tooltipEnd - params.tooltipStart; + const parentSize = params.parentEnd - params.parentStart; - if (params.parentEnd <= params.tooltipStart) { - return 0; + // tooltip after the parent + if (params.parentEnd < params.tooltipStart) { + return -EVO_TOOLTIP_ARROW_SIZE; } - if (params.parentStart >= params.tooltipEnd) { + // tooltip before the parent + if (params.parentStart > params.tooltipEnd) { return tooltipSize; } - const parentSize = params.parentEnd - params.parentStart; + const tooltipCenter = Math.round(tooltipSize / 2); + const parentCenterOnTooltip = Math.round(parentSize / 2 + params.parentStart - params.tooltipStart); - const parentMiddleOffset = params.parentStart + parentSize / 2; - const elementMiddleOffset = params.tooltipStart + tooltipSize / 2; + const defaultArrowOffset = parentCenterOnTooltip - EVO_TOOLTIP_ARROW_SIZE / 2; + + // centers are is one positions + if (tooltipCenter === parentCenterOnTooltip) { + return defaultArrowOffset; + } + + const minPosition = EVO_TOOLTIP_RADIUS; + const maxPosition = tooltipSize - EVO_TOOLTIP_ARROW_SIZE - EVO_TOOLTIP_RADIUS; + + // parent center inside tooltip + if (defaultArrowOffset >= minPosition && parentCenterOnTooltip <= maxPosition) { + return defaultArrowOffset; + } - const diff = elementMiddleOffset - parentMiddleOffset; - return elementMiddleOffset - diff - params.tooltipStart - EVO_TOOLTIP_ARROW_SIZE / 2; + return tooltipCenter > parentCenterOnTooltip ? minPosition : maxPosition; } private setArrowPosition(parentRef: ElementRef): void { @@ -123,8 +139,8 @@ export class EvoTooltipComponent implements OnInit, AfterViewInit, OnDestroy { }); this._positionArrowStyles$.next({ - [EvoTooltipVariableArrowPosition.VERTICAL_POSITION_ARROW]: `${vertical}px`, - [EvoTooltipVariableArrowPosition.HORIZONTAL_POSITION_ARROW]: `${horizontal}px`, + [EvoTooltipStyleVariable.VERTICAL_POSITION_ARROW]: `${vertical}px`, + [EvoTooltipStyleVariable.HORIZONTAL_POSITION_ARROW]: `${horizontal}px`, }); } } 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/services/evo-tooltip.service.spec.ts b/projects/evo-ui-kit/src/lib/components/evo-tooltip/services/evo-tooltip.service.spec.ts index 521286e89..9d2460c00 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'; @@ -44,8 +44,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 f5da213e3..8979160f9 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 @@ -17,9 +17,9 @@ import {EVO_CONNECTED_POSITION} from '../constants/evo-tooltip-connected-positio 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_RADIUS} from '../constants/evo-tooltip-radius'; import {EvoTooltipStyles} from '../interfaces/evo-tooltip-styles'; import {EVO_TOOLTIP_OFFSET} from '../constants/evo-tooltip-offset'; +import {EVO_TOOLTIP_RADIUS} from '../constants/evo-tooltip-radius'; @Injectable() export class EvoTooltipService { From c250992e9402155726a175d91ca191f8c7080104 Mon Sep 17 00:00:00 2001 From: "a.kiselev" Date: Tue, 19 Aug 2025 16:51:43 +0300 Subject: [PATCH 03/12] feat(evo-tooltip): added service to public api --- projects/evo-ui-kit/src/lib/components/evo-tooltip/public-api.ts | 1 + 1 file changed, 1 insertion(+) 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 16718cd89..732c50d68 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 @@ -3,3 +3,4 @@ export * from './directives/evo-tooltip.directive'; export * from './types/evo-tooltip-position-type'; export * from './interfaces/evo-tooltip-config'; export * from './interfaces/evo-tooltip-styles'; +export * from './services/evo-tooltip.service'; From 6026d7f5a75793972ad0b95788c087b10362aa63 Mon Sep 17 00:00:00 2001 From: "a.kiselev" Date: Wed, 20 Aug 2025 13:27:24 +0300 Subject: [PATCH 04/12] feat(evo-tooltip): fixed subscription and scroll strategies added to config --- .../directives/evo-tooltip.directive.ts | 51 +++++++--- .../evo-tooltip/evo-tooltip.component.ts | 12 +-- .../interfaces/evo-tooltip-config.ts | 3 + .../services/evo-tooltip.service.ts | 93 +++++++++---------- .../types/evo-tooltip-scroll-strategy.ts | 1 + 5 files changed, 89 insertions(+), 71 deletions(-) create mode 100644 projects/evo-ui-kit/src/lib/components/evo-tooltip/types/evo-tooltip-scroll-strategy.ts 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 795a8f8c2..2c81fc23d 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 @@ -9,14 +9,14 @@ import { Output, TemplateRef, } from '@angular/core'; -import {fromEvent, merge, 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, Subject} 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'; @Directive({ selector: '[evoTooltip]', @@ -45,8 +45,6 @@ export class EvoTooltipDirective implements OnInit, OnDestroy { return ['evo-tooltip-trigger', ...(this.disabled ? ['evo-tooltip-trigger_disabled'] : [])]; } - readonly isOpen$: Observable = this.tooltipService.isOpen$; - private readonly destroy$ = new Subject(); constructor(private readonly elementRef: ElementRef, private readonly tooltipService: EvoTooltipService) {} @@ -65,18 +63,19 @@ export class EvoTooltipDirective implements OnInit, OnDestroy { this.tooltipService.hideTooltip(); } - show(event?: MouseEvent): void { + show(): void { if (!this.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, ); + + this.initHideSubscription(tooltip); } private initSubscriptions(): void { @@ -85,20 +84,44 @@ export class EvoTooltipDirective implements OnInit, OnDestroy { merge(fromEvent(element, 'mouseenter'), fromEvent(element, 'touchstart')) .pipe( throttleTime(this.config?.showDelay ?? EVO_TOOLTIP_CONFIG.showDelay), - tap(() => { - this.show(); - }), + tap(() => this.show()), takeUntil(this.destroy$), ) .subscribe(); this.tooltipService.isOpen$ .pipe( + distinctUntilChanged(), tap((isOpen) => { - isOpen ? this.evoTooltipOpen.emit() : this.evoTooltipClose.emit(); + if (isOpen) { + this.evoTooltipOpen.emit(); + } else { + this.evoTooltipClose.emit(); + } }), takeUntil(this.destroy$), ) .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.destroy$), + ) + .subscribe(() => { + this.tooltipService.hideTooltip(); + }); + } } 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 e0ee5174d..cb64f135d 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 @@ -11,13 +11,13 @@ import { } from '@angular/core'; import {BehaviorSubject, combineLatest, EMPTY, Observable, Subject} from 'rxjs'; import {filter, map, pairwise, startWith, takeUntil, 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 {EvoTooltipService} from './services/evo-tooltip.service'; -import {EvoTooltipStyles} from './interfaces/evo-tooltip-styles'; +import {EVO_TOOLTIP_RADIUS} from './constants/evo-tooltip-radius'; import {EvoTooltipPosition} from './enums/evo-tooltip-position'; import {EvoTooltipStyleVariable} from './enums/evo-tooltip-style-variable'; -import {EVO_TOOLTIP_RADIUS} from './constants/evo-tooltip-radius'; -import {EVO_TOOLTIP_ARROW_SIZE} from './constants/evo-tooltip-arrow-size'; +import {EvoTooltipStyles} from './interfaces/evo-tooltip-styles'; +import {EvoTooltipService} from './services/evo-tooltip.service'; @Component({ selector: 'evo-tooltip', @@ -52,9 +52,7 @@ export class EvoTooltipComponent implements OnInit, AfterViewInit, OnDestroy { combineLatest([this.position$, this.tooltipService.parentRef$, this.visibleArrow$]) .pipe( filter(([_position, _parentRef, visibleArrow]) => visibleArrow), - tap(([_, parentRef]) => { - this.setArrowPosition(parentRef); - }), + tap(([_, parentRef]) => this.setArrowPosition(parentRef)), takeUntil(this._destroy$), ) .subscribe(); 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/services/evo-tooltip.service.ts b/projects/evo-ui-kit/src/lib/components/evo-tooltip/services/evo-tooltip.service.ts index 8979160f9..f3c121680 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,25 +1,23 @@ -import {ComponentRef, ElementRef, Injectable, Injector, TemplateRef} from '@angular/core'; -import {ComponentPortal} from '@angular/cdk/portal'; import { ConnectedPosition, FlexibleConnectedPositionStrategy, Overlay, OverlayPositionBuilder, - OverlayRef, + 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, TemplateRef} from '@angular/core'; +import {BehaviorSubject, EMPTY, Observable, Subject} from 'rxjs'; +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 {EvoTooltipStyles} from '../interfaces/evo-tooltip-styles'; 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 {EvoTooltipConfig} from '../public-api'; @Injectable() export class EvoTooltipService { @@ -37,7 +35,6 @@ 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(); @@ -45,7 +42,6 @@ export class EvoTooltipService { private overlayRef: OverlayRef; private positionStrategy: FlexibleConnectedPositionStrategy; private tooltipComponentRef: ComponentRef | null; - private targetElement: EventTarget | null; constructor( private readonly overlay: Overlay, @@ -67,19 +63,18 @@ export class EvoTooltipService { 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.initSubscriptions(); + + return this.overlayRef.overlayElement; } hideTooltip(): void { @@ -105,8 +100,8 @@ export class EvoTooltipService { !tooltipClassOrClasses ? [] : Array.isArray(tooltipClassOrClasses) - ? tooltipClassOrClasses - : [tooltipClassOrClasses], + ? tooltipClassOrClasses + : [tooltipClassOrClasses], ); } @@ -114,10 +109,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,15 +121,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.close({ - threshold: 10, + this.overlayRef = this.overlay.create({ + positionStrategy: this.positionStrategy, + scrollStrategy: this.getScrollStrategy(config) }); - this.overlayRef = this.overlay.create({positionStrategy: this.positionStrategy, scrollStrategy}); + } + + 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 { @@ -147,27 +161,6 @@ 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(); - }); - this.positionStrategy.positionChanges.subscribe((value) => { this._position$.next(value.connectionPair.panelClass as EvoTooltipPosition); }); 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'; From 473c59124e587dc79d893be875b305df52964682 Mon Sep 17 00:00:00 2001 From: "a.kiselev" Date: Wed, 20 Aug 2025 13:31:26 +0300 Subject: [PATCH 05/12] feat(evo-tooltip-config): added scroll strategy to config --- .../components/evo-tooltip/constants/evo-tooltip-config.ts | 1 + .../evo-tooltip/directives/evo-tooltip.directive.ts | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) 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.ts b/projects/evo-ui-kit/src/lib/components/evo-tooltip/directives/evo-tooltip.directive.ts index 2c81fc23d..a365cef9c 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 @@ -9,8 +9,8 @@ import { Output, TemplateRef, } from '@angular/core'; -import {EMPTY, filter, fromEvent, merge, Subject} from 'rxjs'; -import {catchError, debounceTime, distinctUntilChanged, first, map, takeUntil, tap, throttleTime} from 'rxjs/operators'; +import {EMPTY, fromEvent, merge, Subject} from 'rxjs'; +import {catchError, debounceTime, distinctUntilChanged, filter, 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'; From 2cd5e72c2f5f25b9a1fc2d889db946035754b5f2 Mon Sep 17 00:00:00 2001 From: "a.kiselev" Date: Thu, 21 Aug 2025 15:42:39 +0300 Subject: [PATCH 06/12] feat(evo-tooltip): stories added --- .../lib/components/evo-tooltip/public-api.ts | 1 + src/stories/components/evo-tooltip.stories.ts | 96 +++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 src/stories/components/evo-tooltip.stories.ts 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 732c50d68..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,6 +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/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: ` + +
+ + +
    +
  • +

    {{item | titlecase }}

    +

    Tooltip parent with some content. Lorem ipsum dolor sit

    +
  • +
+ + + Some tooltip content + +
+ `, + props: { + POSITIONS_LIST: POSITIONS_LIST, + isDisabled: false + } +}); + +Default.storyName = 'default'; From 9ab7a926ac8234d0af395e065b2a1e5dd11ad69c Mon Sep 17 00:00:00 2001 From: "a.kiselev" Date: Mon, 1 Sep 2025 15:28:30 +0300 Subject: [PATCH 07/12] feat(evo-tooltip): stories added --- .../directives/evo-tooltip.directive.ts | 11 +++-- .../services/evo-tooltip.service.ts | 49 ++++++++++++++----- 2 files changed, 45 insertions(+), 15 deletions(-) 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 a365cef9c..cd654ea8c 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 @@ -118,10 +118,13 @@ export class EvoTooltipDirective implements OnInit, OnDestroy { this.tooltipService.hideTooltip(); return EMPTY; }), - takeUntil(this.destroy$), + takeUntil(merge( + this.destroy$, + this.tooltipService.isOpen$.pipe( + filter((isOpened: boolean) => !isOpened) + ) + )), ) - .subscribe(() => { - this.tooltipService.hideTooltip(); - }); + .subscribe(() => this.tooltipService.hideTooltip()); } } 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 f3c121680..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 @@ -3,11 +3,13 @@ import { FlexibleConnectedPositionStrategy, Overlay, OverlayPositionBuilder, - OverlayRef, ScrollStrategy, + OverlayRef, + ScrollStrategy, } from '@angular/cdk/overlay'; import {ComponentPortal} from '@angular/cdk/portal'; -import {ComponentRef, ElementRef, Injectable, Injector, TemplateRef} from '@angular/core'; -import {BehaviorSubject, EMPTY, Observable, Subject} from 'rxjs'; +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_DEFAULT_POSITIONS_ORDER} from '../constants/evo-tooltip-default-positions-order'; @@ -20,7 +22,7 @@ import {EvoTooltipStyles} from '../interfaces/evo-tooltip-styles'; 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; @@ -38,9 +40,10 @@ export class EvoTooltipService { 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; constructor( @@ -58,6 +61,10 @@ export class EvoTooltipService { this.isOpen$ = this._isOpen$.asObservable(); } + ngOnDestroy(): void { + this.destroy$.next(); + } + showTooltip( parentRef: ElementRef, content: string | TemplateRef, @@ -71,7 +78,7 @@ export class EvoTooltipService { this.createOverlay(parentRef, position, config); this.createPortal(); - this._isOpen$.next(this.overlayRef.hasAttached()); + this._isOpen$.next(this.hasAttached); this.initSubscriptions(); return this.overlayRef.overlayElement; @@ -84,7 +91,7 @@ export class EvoTooltipService { } this.overlayRef?.detach(); - this._isOpen$.next(!!this.overlayRef?.hasAttached()); + this._isOpen$.next(this.hasAttached); } setArrowVisibility(hasArrow: boolean): void { @@ -161,9 +168,29 @@ export class EvoTooltipService { } private initSubscriptions(): void { - this.positionStrategy.positionChanges.subscribe((value) => { - this._position$.next(value.connectionPair.panelClass as EvoTooltipPosition); - }); + 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); + }); + } + + 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[] { From 4450de0221c41ca96521011afa00503bfcbf2f8b Mon Sep 17 00:00:00 2001 From: "a.kiselev" Date: Fri, 1 May 2026 12:57:56 +0300 Subject: [PATCH 08/12] fix(evo-tooltip): fixed arrow logic --- .../evo-tooltip/evo-tooltip.component.ts | 122 +++++++++++------- 1 file changed, 74 insertions(+), 48 deletions(-) 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 cb64f135d..cf3940ae2 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 @@ -5,12 +5,11 @@ import { ElementRef, HostBinding, OnDestroy, - OnInit, Renderer2, TemplateRef, } from '@angular/core'; -import {BehaviorSubject, combineLatest, EMPTY, Observable, Subject} from 'rxjs'; -import {filter, map, pairwise, startWith, takeUntil, tap} from 'rxjs/operators'; +import {combineLatest, Observable, Subject} from 'rxjs'; +import {map, pairwise, startWith, takeUntil, 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'; @@ -19,6 +18,28 @@ import {EvoTooltipStyleVariable} from './enums/evo-tooltip-style-variable'; import {EvoTooltipStyles} from './interfaces/evo-tooltip-styles'; import {EvoTooltipService} from './services/evo-tooltip.service'; +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', templateUrl: './evo-tooltip.component.html', @@ -26,37 +47,40 @@ import {EvoTooltipService} from './services/evo-tooltip.service'; changeDetection: ChangeDetectionStrategy.OnPush, animations: [EVO_TOOLTIP_FADEIN_ANIMATION], }) -export class EvoTooltipComponent implements OnInit, AfterViewInit, OnDestroy { +export class EvoTooltipComponent implements 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; + + readonly styles$: Observable = combineLatest([ + this.position$, + this.tooltipService.styles$, + this.tooltipService.parentRef$, + this.visibleArrow$, + ]).pipe( + map( + ([position, baseStyles, parentRef, visibleArrow]: [ + EvoTooltipPosition, + EvoTooltipStyles, + ElementRef, + boolean, + ]) => + visibleArrow && parentRef + ? {...baseStyles, ...this.calculateArrowStyles(parentRef, position)} + : baseStyles, + ), + ); @HostBinding('@fadeIn') fadeIn = true; - private readonly _positionArrowStyles$ = new BehaviorSubject(null); private readonly _destroy$ = new Subject(); 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), - tap(([_, parentRef]) => this.setArrowPosition(parentRef)), - takeUntil(this._destroy$), - ) - .subscribe(); - } + ) {} ngAfterViewInit(): void { this.tooltipService.tooltipClasses$ @@ -78,14 +102,28 @@ export class EvoTooltipComponent implements OnInit, AfterViewInit, OnDestroy { this._destroy$.complete(); } - private getArrowOffset(params: { - parentStart: number; - parentEnd: number; - tooltipStart: number; - tooltipEnd: number; - }): number { + 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; - const parentSize = params.parentEnd - params.parentStart; // tooltip after the parent if (params.parentEnd < params.tooltipStart) { @@ -97,28 +135,14 @@ export class EvoTooltipComponent implements OnInit, AfterViewInit, OnDestroy { return tooltipSize; } - const tooltipCenter = Math.round(tooltipSize / 2); - const parentCenterOnTooltip = Math.round(parentSize / 2 + params.parentStart - params.tooltipStart); - - const defaultArrowOffset = parentCenterOnTooltip - EVO_TOOLTIP_ARROW_SIZE / 2; - - // centers are is one positions - if (tooltipCenter === parentCenterOnTooltip) { - return defaultArrowOffset; - } - const minPosition = EVO_TOOLTIP_RADIUS; - const maxPosition = tooltipSize - EVO_TOOLTIP_ARROW_SIZE - EVO_TOOLTIP_RADIUS; + const arrowStartEdge = this.getArrowStartEdge(params); + const maxPosition = tooltipSize - EVO_TOOLTIP_RADIUS - EVO_TOOLTIP_ARROW_SIZE; - // parent center inside tooltip - if (defaultArrowOffset >= minPosition && parentCenterOnTooltip <= maxPosition) { - return defaultArrowOffset; - } - - return tooltipCenter > parentCenterOnTooltip ? minPosition : maxPosition; + return Math.max(minPosition, Math.min(arrowStartEdge, maxPosition)); } - private setArrowPosition(parentRef: ElementRef): void { + private calculateArrowStyles(parentRef: ElementRef, position: EvoTooltipPosition): EvoTooltipStyles { const parentRect = (parentRef.nativeElement as HTMLElement).getBoundingClientRect(); const tooltipRect = (this.elementRef.nativeElement as HTMLElement).getBoundingClientRect(); @@ -127,6 +151,7 @@ export class EvoTooltipComponent implements OnInit, AfterViewInit, OnDestroy { parentEnd: parentRect.bottom, tooltipStart: tooltipRect.top, tooltipEnd: tooltipRect.bottom, + position, }); const horizontal = this.getArrowOffset({ @@ -134,11 +159,12 @@ export class EvoTooltipComponent implements OnInit, AfterViewInit, OnDestroy { parentEnd: parentRect.right, tooltipStart: tooltipRect.left, tooltipEnd: tooltipRect.right, + position, }); - this._positionArrowStyles$.next({ + return { [EvoTooltipStyleVariable.VERTICAL_POSITION_ARROW]: `${vertical}px`, [EvoTooltipStyleVariable.HORIZONTAL_POSITION_ARROW]: `${horizontal}px`, - }); + }; } } From 7a09e7e5be2d8a1864156327f75c1e414190362f Mon Sep 17 00:00:00 2001 From: "a.kiselev" Date: Tue, 5 May 2026 19:43:50 +0300 Subject: [PATCH 09/12] feat(evo-tooltip-trigger): use styles --- .../components/evo-tooltip-trigger.scss} | 0 projects/evo-ui-kit/src/lib/styles/main.scss | 1 + 2 files changed, 1 insertion(+) rename projects/evo-ui-kit/src/lib/{components/evo-tooltip/assets/evo-tooltip-global.scss => styles/components/evo-tooltip-trigger.scss} (100%) 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 8531ec4a0..12f6d6a7c 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"; From a2fa65804d7e10cbbc48c20787133987fb0db088 Mon Sep 17 00:00:00 2001 From: "a.kiselev" Date: Tue, 12 May 2026 13:24:47 +0300 Subject: [PATCH 10/12] feat(evo-scroll-strategies): created strategies --- .../classes/evo-close-scroll-strategy.ts | 103 ++++++++++++++++++ .../classes/evo-reposition-scroll-strategy.ts | 94 ++++++++++++++++ .../classes/evo-scroll-strategy-options.ts | 22 ++++ .../evo-ui-kit/src/lib/common/scroll/index.ts | 5 + .../evo-close-scroll-strategy-params.ts | 7 ++ .../scroll/interfaces/scroll-position.ts | 4 + .../scroll/types/evo-scroll-strategy.ts | 1 + .../scroll/utils/create-scroll-stream.ts | 14 +++ .../types/evo-tooltip-scroll-strategy.ts | 1 - projects/evo-ui-kit/src/public_api.ts | 3 + 10 files changed, 253 insertions(+), 1 deletion(-) create mode 100644 projects/evo-ui-kit/src/lib/common/scroll/classes/evo-close-scroll-strategy.ts create mode 100644 projects/evo-ui-kit/src/lib/common/scroll/classes/evo-reposition-scroll-strategy.ts create mode 100644 projects/evo-ui-kit/src/lib/common/scroll/classes/evo-scroll-strategy-options.ts create mode 100644 projects/evo-ui-kit/src/lib/common/scroll/index.ts create mode 100644 projects/evo-ui-kit/src/lib/common/scroll/interfaces/evo-close-scroll-strategy-params.ts create mode 100644 projects/evo-ui-kit/src/lib/common/scroll/interfaces/scroll-position.ts create mode 100644 projects/evo-ui-kit/src/lib/common/scroll/types/evo-scroll-strategy.ts create mode 100644 projects/evo-ui-kit/src/lib/common/scroll/utils/create-scroll-stream.ts delete mode 100644 projects/evo-ui-kit/src/lib/components/evo-tooltip/types/evo-tooltip-scroll-strategy.ts diff --git a/projects/evo-ui-kit/src/lib/common/scroll/classes/evo-close-scroll-strategy.ts b/projects/evo-ui-kit/src/lib/common/scroll/classes/evo-close-scroll-strategy.ts new file mode 100644 index 000000000..3ba3635fb --- /dev/null +++ b/projects/evo-ui-kit/src/lib/common/scroll/classes/evo-close-scroll-strategy.ts @@ -0,0 +1,103 @@ +import {Injector, NgZone} from '@angular/core'; +import {DOCUMENT} from '@angular/common'; +import {ScrollStrategy} from '@angular/cdk/overlay'; +import {OverlayReference} from '@angular/cdk/overlay/overlay-reference'; +import {EvoCloseScrollStrategyParams} from '../interfaces/evo-close-scroll-strategy-params'; +import {filter, first, tap} from 'rxjs/operators'; +import {createScrollStream} from '../utils/create-scroll-stream'; +import {Subscription} from 'rxjs'; +import {ScrollPosition} from '../interfaces/scroll-position'; + +export class EvoCloseScrollStrategy implements ScrollStrategy { + private readonly document: Document; + private readonly ngZone: NgZone; + + private overlayRef: OverlayReference | null = null; + private scrollSubscription: Subscription | null = null; + + private readonly initialScrollPositionsOld = new Map(); + + private initialScrollPosition: ScrollPosition | null = null; + + constructor(private readonly injector: Injector, private readonly params?: EvoCloseScrollStrategyParams) { + this.document = this.injector.get(DOCUMENT); + this.ngZone = this.injector.get(NgZone); + } + + attach(overlayRef: OverlayReference): void { + this.overlayRef = overlayRef; + } + + detach(): void { + this.disable(); + this.overlayRef = null; + } + + disable(): void { + this.initialScrollPositionsOld.clear(); + if (!this.scrollSubscription) { + return; + } + + this.scrollSubscription.unsubscribe(); + this.scrollSubscription = null; + } + + enable(): void { + this.ngZone.runOutsideAngular(() => { + this.initialScrollPosition = this.getCurrentScrollPosition(); + + this.scrollSubscription = createScrollStream(this.document, this.overlayRef) + .pipe( + filter(() => this.checkThreshold()), + first(), + tap(() => this.detachOverlay()), + ) + .subscribe(); + }); + } + + private detachOverlay(): void { + if (!this.overlayRef?.hasAttached()) { + return; + } + + this.disable(); + this.ngZone.run(() => this.overlayRef.detach()); + } + + private checkThreshold(): boolean { + const threshold = this.params?.threshold ?? 0; + const triggerEl = this.params?.triggerRef?.nativeElement; + + if (!triggerEl || !this.initialScrollPosition) { + return true; + } + + const scrollPosition = this.getCurrentScrollPosition(); + + if (!scrollPosition) { + return null; + } + + const distanceY = Math.abs(scrollPosition.vertical - this.initialScrollPosition.vertical); + const distanceX = Math.abs(scrollPosition.horizontal - this.initialScrollPosition.horizontal); + + return distanceY > threshold || distanceX > threshold; + } + + private getCurrentScrollPosition(): ScrollPosition | null { + const element = this.params?.triggerRef?.nativeElement as Element; + + if (!element) { + return null; + } + + const rect = element.getBoundingClientRect(); + + return { + vertical: rect.top, + horizontal: rect.left, + }; + } +} diff --git a/projects/evo-ui-kit/src/lib/common/scroll/classes/evo-reposition-scroll-strategy.ts b/projects/evo-ui-kit/src/lib/common/scroll/classes/evo-reposition-scroll-strategy.ts new file mode 100644 index 000000000..bad6027d8 --- /dev/null +++ b/projects/evo-ui-kit/src/lib/common/scroll/classes/evo-reposition-scroll-strategy.ts @@ -0,0 +1,94 @@ +import {Injector, NgZone} from '@angular/core'; +import {DOCUMENT} from '@angular/common'; +import {ScrollStrategy} from '@angular/cdk/overlay'; +import {OverlayReference} from '@angular/cdk/overlay/overlay-reference'; +import {Subscription} from 'rxjs'; +import {tap} from 'rxjs/operators'; +import {createScrollStream} from '../utils/create-scroll-stream'; + +export class EvoRepositionScrollStrategy implements ScrollStrategy { + private readonly document: Document; + private readonly ngZone: NgZone; + + private overlayRef: OverlayReference | null = null; + + private scrollSubscription: Subscription | null = null; + + constructor(private readonly injector: Injector) { + this.document = this.injector.get(DOCUMENT); + this.ngZone = this.injector.get(NgZone); + } + + attach(overlayRef: OverlayReference): void { + this.overlayRef = overlayRef; + } + + detach(): void { + this.disable(); + this.overlayRef = null; + } + + disable(): void { + if (!this.scrollSubscription) { + return; + } + + this.scrollSubscription.unsubscribe(); + this.scrollSubscription = null; + } + + enable(): void { + if (this.scrollSubscription || !this.overlayRef) { + return; + } + + this.ngZone.runOutsideAngular(() => { + this.scrollSubscription = createScrollStream(this.document, this.overlayRef) + .pipe( + tap(() => { + if (this.isOverlayScrolledOutsideView()) { + this.detachOverlay(); + return; + } + + this.updatePosition(); + }), + ) + .subscribe(); + }); + } + + private updatePosition(): void { + if (!this.overlayRef?.hasAttached()) { + return; + } + + this.ngZone.run(() => this.overlayRef.updatePosition()); + } + + private detachOverlay(): void { + if (!this.overlayRef?.hasAttached()) { + return; + } + + this.disable(); + this.ngZone.run(() => this.overlayRef.detach()); + } + + private isOverlayScrolledOutsideView(): boolean { + if (!this.overlayRef?.hasAttached()) { + return true; + } + + const overlayRect = this.overlayRef.overlayElement.getBoundingClientRect(); + const pageHeight = this.document.documentElement.clientHeight; + const pageWidth = this.document.documentElement.clientWidth; + + return ( + overlayRect.bottom < 0 || + overlayRect.top > pageHeight || + overlayRect.right < 0 || + overlayRect.left > pageWidth + ); + } +} diff --git a/projects/evo-ui-kit/src/lib/common/scroll/classes/evo-scroll-strategy-options.ts b/projects/evo-ui-kit/src/lib/common/scroll/classes/evo-scroll-strategy-options.ts new file mode 100644 index 000000000..efc97e1a0 --- /dev/null +++ b/projects/evo-ui-kit/src/lib/common/scroll/classes/evo-scroll-strategy-options.ts @@ -0,0 +1,22 @@ +import {NoopScrollStrategy, ScrollStrategy} from '@angular/cdk/overlay'; +import {Injectable, Injector} from '@angular/core'; +import {EvoCloseScrollStrategy} from './evo-close-scroll-strategy'; +import {EvoRepositionScrollStrategy} from './evo-reposition-scroll-strategy'; +import {EvoCloseScrollStrategyParams} from '../interfaces/evo-close-scroll-strategy-params'; + +@Injectable() +export class EvoScrollStrategyOptions { + constructor(private readonly injector: Injector) {} + + noop(): ScrollStrategy { + return new NoopScrollStrategy(); + }; + + reposition(): ScrollStrategy { + return new EvoRepositionScrollStrategy(this.injector); + }; + + close(params?: EvoCloseScrollStrategyParams): ScrollStrategy { + return new EvoCloseScrollStrategy(this.injector, params); + }; +} diff --git a/projects/evo-ui-kit/src/lib/common/scroll/index.ts b/projects/evo-ui-kit/src/lib/common/scroll/index.ts new file mode 100644 index 000000000..02a3aab51 --- /dev/null +++ b/projects/evo-ui-kit/src/lib/common/scroll/index.ts @@ -0,0 +1,5 @@ +export * from './types/evo-scroll-strategy'; + +export * from './classes/evo-scroll-strategy-options'; +export * from './classes/evo-close-scroll-strategy'; +export * from './classes/evo-reposition-scroll-strategy'; diff --git a/projects/evo-ui-kit/src/lib/common/scroll/interfaces/evo-close-scroll-strategy-params.ts b/projects/evo-ui-kit/src/lib/common/scroll/interfaces/evo-close-scroll-strategy-params.ts new file mode 100644 index 000000000..d49c21d8b --- /dev/null +++ b/projects/evo-ui-kit/src/lib/common/scroll/interfaces/evo-close-scroll-strategy-params.ts @@ -0,0 +1,7 @@ +import {ElementRef} from '@angular/core'; + +export interface EvoCloseScrollStrategyParams { + /** Amount of pixels the user has to scroll before the overlay is closed. */ + threshold: number; + triggerRef: ElementRef; +} diff --git a/projects/evo-ui-kit/src/lib/common/scroll/interfaces/scroll-position.ts b/projects/evo-ui-kit/src/lib/common/scroll/interfaces/scroll-position.ts new file mode 100644 index 000000000..b3c81a4de --- /dev/null +++ b/projects/evo-ui-kit/src/lib/common/scroll/interfaces/scroll-position.ts @@ -0,0 +1,4 @@ +export interface ScrollPosition { + vertical: number; + horizontal: number; +} diff --git a/projects/evo-ui-kit/src/lib/common/scroll/types/evo-scroll-strategy.ts b/projects/evo-ui-kit/src/lib/common/scroll/types/evo-scroll-strategy.ts new file mode 100644 index 000000000..f64c188ad --- /dev/null +++ b/projects/evo-ui-kit/src/lib/common/scroll/types/evo-scroll-strategy.ts @@ -0,0 +1 @@ +export type EvoScrollStrategy = 'noop' | 'close' | 'reposition'; diff --git a/projects/evo-ui-kit/src/lib/common/scroll/utils/create-scroll-stream.ts b/projects/evo-ui-kit/src/lib/common/scroll/utils/create-scroll-stream.ts new file mode 100644 index 000000000..1b8dc398c --- /dev/null +++ b/projects/evo-ui-kit/src/lib/common/scroll/utils/create-scroll-stream.ts @@ -0,0 +1,14 @@ +import {animationFrameScheduler, fromEvent, Observable} from 'rxjs'; +import {filter, throttleTime} from 'rxjs/operators'; +import {OverlayReference} from '@angular/cdk/overlay/overlay-reference'; + +export function createScrollStream(document: Document, overlayRef: OverlayReference): Observable { + return fromEvent(document, 'scroll', {capture: true, passive: true}).pipe( + throttleTime(10, animationFrameScheduler, {leading: true, trailing: true}), + filter((event) => { + const target = event.target as Node; + + return !overlayRef.overlayElement.contains(target); + }), + ); +} 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 deleted file mode 100644 index e899cd4ce..000000000 --- a/projects/evo-ui-kit/src/lib/components/evo-tooltip/types/evo-tooltip-scroll-strategy.ts +++ /dev/null @@ -1 +0,0 @@ -export type EvoTooltipScrollStrategy = 'noop' | 'close' | 'reposition' | 'block'; diff --git a/projects/evo-ui-kit/src/public_api.ts b/projects/evo-ui-kit/src/public_api.ts index 29a4f91b6..448352869 100644 --- a/projects/evo-ui-kit/src/public_api.ts +++ b/projects/evo-ui-kit/src/public_api.ts @@ -10,6 +10,9 @@ export * from './lib/common/evo-control-state-manager/evo-control-states.enum'; export * from './lib/common/evo-control-state-manager/evo-control-state.interface'; export * from './lib/common/evo-base-control'; +// Scroll +export * from './lib/common/scroll/index'; + // Animations export * from './lib/common/animations/index'; From b1c211bcc484033c35e6117d9550f62f493d18fe Mon Sep 17 00:00:00 2001 From: "a.kiselev" Date: Tue, 12 May 2026 13:25:10 +0300 Subject: [PATCH 11/12] feat(evo-tooltip): use scroll strategies in tooltip --- .../directives/evo-tooltip.directive.ts | 35 +++++--- .../interfaces/evo-tooltip-config.ts | 4 +- .../services/evo-tooltip.service.ts | 86 +++++++++---------- 3 files changed, 69 insertions(+), 56 deletions(-) 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 cd654ea8c..5810c9523 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 @@ -10,30 +10,44 @@ import { TemplateRef, } from '@angular/core'; import {EMPTY, fromEvent, merge, Subject} from 'rxjs'; -import {catchError, debounceTime, distinctUntilChanged, filter, first, map, takeUntil, tap, throttleTime} from 'rxjs/operators'; +import { + catchError, + debounceTime, + distinctUntilChanged, + filter, + 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 {EvoScrollStrategyOptions} from '../../../common/scroll'; @Directive({ selector: '[evoTooltip]', exportAs: 'evoTooltip', - providers: [EvoTooltipService], + providers: [EvoTooltipService, EvoScrollStrategyOptions], }) 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); } @@ -105,8 +119,12 @@ export class EvoTooltipDirective implements OnInit, OnDestroy { } 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)); + const mouseLeaveParentElement$ = fromEvent(tooltip, 'mouseleave').pipe( + map(() => this.elementRef.nativeElement), + ); + const mouseLeaveOverlayElement$ = fromEvent(this.elementRef.nativeElement, 'mouseleave').pipe( + map(() => tooltip), + ); merge(mouseLeaveParentElement$, mouseLeaveOverlayElement$) .pipe( @@ -118,12 +136,9 @@ export class EvoTooltipDirective implements OnInit, OnDestroy { this.tooltipService.hideTooltip(); return EMPTY; }), - takeUntil(merge( - this.destroy$, - this.tooltipService.isOpen$.pipe( - filter((isOpened: boolean) => !isOpened) - ) - )), + takeUntil( + merge(this.destroy$, this.tooltipService.isOpen$.pipe(filter((isOpened: boolean) => !isOpened))), + ), ) .subscribe(() => this.tooltipService.hideTooltip()); } 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 0b31df2c8..35f4bb683 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,8 +1,8 @@ -import {EvoTooltipScrollStrategy} from '../types/evo-tooltip-scroll-strategy'; +import {EvoScrollStrategy} from '../../../common/scroll/types/evo-scroll-strategy'; export interface EvoTooltipConfig { // The default delay in ms before hiding the tooltip hideDelay?: number; showDelay?: number; - scrollStrategy?: EvoTooltipScrollStrategy; + scrollStrategy?: EvoScrollStrategy; } 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 978ca940c..efcb88cfa 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 @@ -20,6 +20,7 @@ import {EvoTooltipPosition} from '../enums/evo-tooltip-position'; import {EvoTooltipComponent} from '../evo-tooltip.component'; import {EvoTooltipStyles} from '../interfaces/evo-tooltip-styles'; import {EvoTooltipConfig} from '../public-api'; +import {EvoScrollStrategyOptions} from '../../../common/scroll'; @Injectable() export class EvoTooltipService implements OnDestroy { @@ -46,9 +47,18 @@ export class EvoTooltipService implements OnDestroy { private positionStrategy: FlexibleConnectedPositionStrategy | null = null; private tooltipComponentRef: ComponentRef | null; + private get parentRef(): ElementRef { + return this._parentRef$.value; + } + + get hasAttached(): boolean { + return this.overlayRef?.hasAttached() ?? false; + } + constructor( private readonly overlay: Overlay, private readonly overlayPositionBuilder: OverlayPositionBuilder, + private readonly evoScrollStrategyOptions: EvoScrollStrategyOptions, private readonly injector: Injector, ) { this.stringContent$ = this._stringContent$.asObservable(); @@ -107,19 +117,11 @@ export class EvoTooltipService implements OnDestroy { !tooltipClassOrClasses ? [] : Array.isArray(tooltipClassOrClasses) - ? tooltipClassOrClasses - : [tooltipClassOrClasses], + ? tooltipClassOrClasses + : [tooltipClassOrClasses], ); } - get hasAttached(): boolean { - return this.overlayRef?.hasAttached() ?? false; - } - - private get parentRef(): ElementRef { - return this._parentRef$.value; - } - private setContent(content: string | TemplateRef | undefined): void { if (typeof content === 'string') { this._stringContent$.next(content); @@ -128,35 +130,32 @@ export class EvoTooltipService implements OnDestroy { } } - private createOverlay(elementRef: ElementRef, position: EvoTooltipPosition, config: EvoTooltipConfig): void { + private createOverlay(parentRef: ElementRef, position: EvoTooltipPosition, config: EvoTooltipConfig): void { this.positionStrategy = this.overlayPositionBuilder - .flexibleConnectedTo(elementRef) - .withPositions(this.getPositions(position)); + .flexibleConnectedTo(parentRef) + .withPositions(this.getPositions(position)) + .withPush(false); this.overlayRef = this.overlay.create({ positionStrategy: this.positionStrategy, - scrollStrategy: this.getScrollStrategy(config) + scrollStrategy: this.getScrollStrategy(config, parentRef), }); } - private getScrollStrategy(config: EvoTooltipConfig): ScrollStrategy { - switch (config?.scrollStrategy) { - case 'reposition': { - return this.overlay.scrollStrategies.reposition(); - } - - case 'block': { - return this.overlay.scrollStrategies.block(); - } - + private getScrollStrategy(config: EvoTooltipConfig, parentRef: ElementRef): ScrollStrategy { + switch (config.scrollStrategy) { case 'noop': { - return this.overlay.scrollStrategies.noop(); + return this.evoScrollStrategyOptions.noop(); + } + case 'reposition': { + return this.evoScrollStrategyOptions.reposition(); } case 'close': default: { - return this.overlay.scrollStrategies.close({ + return this.evoScrollStrategyOptions.close({ threshold: 10, + triggerRef: parentRef, }); } } @@ -169,27 +168,26 @@ export class EvoTooltipService implements OnDestroy { private initSubscriptions(): void { 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 + .pipe(takeUntil(merge(this.destroy$, this._isOpen$.pipe(filter((isOpened: boolean) => !isOpened))))) + .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(); + this.overlayRef + .detachments() + .pipe( + take(1), + tap(() => { + this.positionStrategy = null; + this.overlayRef = null; + this._isOpen$.next(false); + }), + takeUntil(this.destroy$), + ) + .subscribe(); } } From b5e2898dba6f0e0c8771e4372e5c157aa3dbb025 Mon Sep 17 00:00:00 2001 From: "a.kiselev" Date: Tue, 12 May 2026 13:25:23 +0300 Subject: [PATCH 12/12] feat(evo-dropdown): use scroll strategies in dropdown --- .../evo-dropdown/evo-dropdown.component.html | 1 + .../evo-dropdown/evo-dropdown.component.ts | 80 +++++-------------- 2 files changed, 22 insertions(+), 59 deletions(-) diff --git a/projects/evo-ui-kit/src/lib/components/evo-dropdown/evo-dropdown.component.html b/projects/evo-ui-kit/src/lib/components/evo-dropdown/evo-dropdown.component.html index 024f481d2..3b97ae0e4 100644 --- a/projects/evo-ui-kit/src/lib/components/evo-dropdown/evo-dropdown.component.html +++ b/projects/evo-ui-kit/src/lib/components/evo-dropdown/evo-dropdown.component.html @@ -3,6 +3,7 @@ [cdkConnectedOverlayOpen]="isOpen" [cdkConnectedOverlayOrigin]="dropdownOrigin" [cdkConnectedOverlayPositions]="connectedPositions" + [cdkConnectedOverlayScrollStrategy]="scrollStrategy$ | async" (detach)="close()" (overlayOutsideClick)="onOverlayOutsideClick($event)" > diff --git a/projects/evo-ui-kit/src/lib/components/evo-dropdown/evo-dropdown.component.ts b/projects/evo-ui-kit/src/lib/components/evo-dropdown/evo-dropdown.component.ts index 8ae4c7b48..0766cfa07 100644 --- a/projects/evo-ui-kit/src/lib/components/evo-dropdown/evo-dropdown.component.ts +++ b/projects/evo-ui-kit/src/lib/components/evo-dropdown/evo-dropdown.component.ts @@ -2,11 +2,8 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, - ElementRef, EventEmitter, Input, - NgZone, - OnDestroy, Output, ViewContainerRef, } from '@angular/core'; @@ -14,8 +11,9 @@ import {EvoDropdownOriginDirective} from './evo-dropdown-origin.directive'; import {ConnectedPosition} from '@angular/cdk/overlay'; import {EVO_DROPDOWN_POSITION_DESCRIPTION} from './evo-dropdown-position-description'; import {EvoDropdownPositions} from './types/evo-dropdown-positions'; -import {fromEvent, Subject, Subscription} from 'rxjs'; -import {filter, take, takeUntil, throttleTime} from 'rxjs/operators'; +import {BehaviorSubject} from 'rxjs'; +import {EvoScrollStrategy} from '../../common/scroll/types/evo-scroll-strategy'; +import {EvoScrollStrategyOptions} from '../../common/scroll'; type Position = EvoDropdownPositions | ConnectedPosition; @@ -25,10 +23,10 @@ const DEFAULT_POSITION = [EVO_DROPDOWN_POSITION_DESCRIPTION['bottom-right']]; selector: 'evo-dropdown', templateUrl: './evo-dropdown.component.html', changeDetection: ChangeDetectionStrategy.OnPush, + providers: [EvoScrollStrategyOptions], }) -export class EvoDropdownComponent implements OnDestroy { +export class EvoDropdownComponent { @Input() closeOnOutsideClick = true; - @Input() scrollStrategy: 'noop' | 'close' = 'close'; @Input() dropdownOrigin!: EvoDropdownOriginDirective; @Output() isOpenChange = new EventEmitter(); @@ -36,8 +34,8 @@ export class EvoDropdownComponent implements OnDestroy { connectedPositions: ConnectedPosition[] = DEFAULT_POSITION; - private scrollEventSubscription: Subscription; - private destroy$ = new Subject(); + readonly scrollStrategy$ = new BehaviorSubject(this.evoScrollStrategyOptions.close()); + private _isOpen = false; get isOpen(): boolean { @@ -46,10 +44,6 @@ export class EvoDropdownComponent implements OnDestroy { @Input() set isOpen(value: boolean) { this._isOpen = value; - - if (value) { - this.listenScroll(); - } } @Input() set positions(value: Position[] | Position) { @@ -58,19 +52,25 @@ export class EvoDropdownComponent implements OnDestroy { : DEFAULT_POSITION; } - private get element(): HTMLElement | null { - if (!this.viewContainerRef) { - return; + @Input() set scrollStrategy(strategy: EvoScrollStrategy) { + switch (strategy) { + case 'noop': { + this.scrollStrategy$.next(this.evoScrollStrategyOptions.noop()); + break; + } + case 'reposition': { + this.scrollStrategy$.next(this.evoScrollStrategyOptions.reposition()); + break; + } + case 'close': { + this.scrollStrategy$.next(this.evoScrollStrategyOptions.close()); + } } - - return this.viewContainerRef?.element instanceof ElementRef - ? (this.viewContainerRef.element?.nativeElement as HTMLElement) - : (this.viewContainerRef.element as HTMLElement); } constructor( + private readonly evoScrollStrategyOptions: EvoScrollStrategyOptions, protected readonly viewContainerRef: ViewContainerRef, - private readonly ngZone: NgZone, private readonly cdr: ChangeDetectorRef, ) {} @@ -100,18 +100,9 @@ export class EvoDropdownComponent implements OnDestroy { this.isOpen = false; this.isOpenChange.emit(this.isOpen); - if (this.scrollEventSubscription && !this.scrollEventSubscription.closed) { - this.scrollEventSubscription.unsubscribe(); - } - this.cdr.detectChanges(); } - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.unsubscribe(); - } - onOverlayOutsideClick(event: MouseEvent): void { this.outsideClick.emit(event); @@ -122,33 +113,4 @@ export class EvoDropdownComponent implements OnDestroy { this.close(); } } - - /** - * Listens to the scroll in the dropdown container and closes it - */ - private listenScroll() { - if (this.scrollStrategy === 'noop') { - return; - } - - this.scrollEventSubscription = this.ngZone.runOutsideAngular(() => { - return fromEvent(document, 'scroll', {capture: true}) - .pipe( - throttleTime(10), - filter((scrollEvent: Event) => { - return ( - (scrollEvent.target instanceof HTMLElement || scrollEvent.target instanceof HTMLDocument) && - (scrollEvent.target.contains(this.element) || !this.element) - ); - }), - take(1), - takeUntil(this.destroy$), - ) - .subscribe(() => { - this.ngZone.run(() => { - this.close(); - }); - }); - }); - } }