Skip to content

Commit f4441ab

Browse files
viktorkombovChronosSFigdmdimitrov
authored
fix(tooltip): prevent delayed tooltip after target moves - 22.0.x (#17475)
* fix(tooltip): prevent delayed tooltip after target moves * fix(tooltip): improve hover interaction handling * fix(tooltip): remove unused imports * fix(grid-validation): mock element.matches for tooltip hover test --------- Co-authored-by: Stamen Stoychev <chronos.stz@gmail.com> Co-authored-by: igdmdimitrov <49060557+igdmdimitrov@users.noreply.github.com>
1 parent 97f490e commit f4441ab

3 files changed

Lines changed: 41 additions & 4 deletions

File tree

projects/igniteui-angular/directives/src/directives/tooltip/tooltip-target.directive.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ export interface ITooltipHideEventArgs extends IBaseEventArgs {
2727
cancel: boolean;
2828
}
2929

30+
const HOVER_SHOW_TRIGGERS = new Set(['mouseenter', 'mouseover', 'pointerenter', 'pointerover']);
31+
3032
/**
3133
* **Ignite UI for Angular Tooltip Target** -
3234
* [Documentation](https://www.infragistics.com/products/ignite-ui-angular/angular/components/tooltip)
@@ -564,8 +566,14 @@ export class IgxTooltipTargetDirective extends IgxToggleActionDirective implemen
564566
this._pendingShowTrigger = triggerEvent?.type ?? null;
565567

566568
this.target.timeoutId = setTimeout(() => {
567-
// Call open() of IgxTooltipDirective
569+
const isHoverTrigger = this._pendingShowTrigger && HOVER_SHOW_TRIGGERS.has(this._pendingShowTrigger);
568570
this._pendingShowTrigger = null;
571+
this.target.timeoutId = null;
572+
573+
if (isHoverTrigger && !this.nativeElement.matches(':hover')) {
574+
return;
575+
}
576+
569577
this.target.open(this._mergedOverlaySettings);
570578
}, withDelay ? this.showDelay : 0);
571579
}

projects/igniteui-angular/directives/src/directives/tooltip/tooltip.directive.spec.ts

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const SHOW_DELAY = 200;
1515
const HIDE_DELAY = 300;
1616
const AUTO_HIDE_DELAY = 180;
1717
const TOOLTIP_ARROW_SELECTOR = '[data-arrow="true"]';
18+
const hoveredElements = new WeakSet<Element>();
1819

1920
describe('IgxTooltip', () => {
2021
let fix: ComponentFixture<any>;
@@ -23,6 +24,11 @@ describe('IgxTooltip', () => {
2324
let button: DebugElement;
2425

2526
beforeEach(waitForAsync(() => {
27+
const matches = Element.prototype.matches;
28+
spyOn(Element.prototype, 'matches').and.callFake(function(this: Element, selectors: string): boolean {
29+
return selectors === ':hover' ? hoveredElements.has(this) : matches.call(this, selectors);
30+
} as typeof Element.prototype.matches);
31+
2632
TestBed.configureTestingModule({
2733
imports: [
2834
NoopAnimationsModule,
@@ -532,6 +538,22 @@ describe('IgxTooltip', () => {
532538
tick(300);
533539
verifyTooltipVisibility(tooltipNativeElement, tooltipTarget, false);
534540
}));
541+
542+
for (const trigger of ['mouseenter', 'mouseover', 'pointerenter', 'pointerover']) {
543+
it(`should not open after the delay when the target is no longer hovered using ${trigger}`, fakeAsync(() => {
544+
tooltipTarget.showDelay = 500;
545+
tooltipTarget.showTriggers = trigger;
546+
tooltipTarget.hideTriggers = 'click';
547+
fix.detectChanges();
548+
549+
hoverElement(button, trigger);
550+
tick(300);
551+
unhoverElement(button);
552+
tick(200);
553+
554+
verifyTooltipVisibility(tooltipNativeElement, tooltipTarget, false);
555+
}));
556+
}
535557
});
536558
});
537559

@@ -1169,9 +1191,15 @@ interface ElementRefLike {
11691191
nativeElement: HTMLElement
11701192
}
11711193

1172-
const hoverElement = (element: ElementRefLike) => element.nativeElement.dispatchEvent(new MouseEvent('pointerenter'));
1194+
const hoverElement = (element: ElementRefLike, event = 'pointerenter') => {
1195+
hoveredElements.add(element.nativeElement);
1196+
element.nativeElement.dispatchEvent(new MouseEvent(event));
1197+
};
11731198

1174-
const unhoverElement = (element: ElementRefLike) => element.nativeElement.dispatchEvent(new MouseEvent('pointerleave'));
1199+
const unhoverElement = (element: ElementRefLike) => {
1200+
hoveredElements.delete(element.nativeElement);
1201+
element.nativeElement.dispatchEvent(new MouseEvent('pointerleave'));
1202+
};
11751203

11761204
const simulateTriggerEvent = (element: ElementRefLike, event: string) => element.nativeElement.dispatchEvent(new Event(event, { bubbles: true }));
11771205

@@ -1188,7 +1216,7 @@ const alignmentTolerance = 2;
11881216
export const verifyTooltipPosition = (
11891217
tooltipNativeElement: HTMLElement,
11901218
actualTarget: { nativeElement: HTMLElement },
1191-
shouldAlign:boolean = true,
1219+
shouldAlign: boolean = true,
11921220
placement: Placement = Placement.Bottom,
11931221
offset: number = 6
11941222
) => {

projects/igniteui-angular/grids/grid/src/grid-validation.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,7 @@ describe('IgxGrid - Validation #grid', () => {
225225
expect(cell.errorTooltip.first.collapsed).toBeTrue();
226226

227227
const element = fixture.debugElement.query(By.directive(IgxTooltipTargetDirective)).nativeElement;
228+
spyOn(element, 'matches').and.returnValue(true);
228229
element.dispatchEvent(new MouseEvent('pointerenter'));
229230
flush();
230231
fixture.detectChanges();

0 commit comments

Comments
 (0)