ngx-bootstrap icon indicating copy to clipboard operation
ngx-bootstrap copied to clipboard

Popover/tooltip mouseleave trigger - extend mouseleave area to tooltip/popover content

Open ejasarevic opened this issue 6 years ago • 2 comments

Bug description or feature request:

When using Tooltip/Popover component, we can specify custom triggers when they will appear/hide. What I am trying to achieve is to display tooltip/popover on hover and hide when either popover element or popover content are not hovering. Currently, it only works for the element on which popover is attached, but if we have some links or something in the popover content, we will not be able to click them.

Is there an option to include also popover content for "mouseleave" trigger when closing/hiding element?

Plunker/StackBlitz that reproduces the issue:

https://stackblitz.com/edit/angular-jcy2ks?file=src%2Fapp%2Fapp.component.html

Versions of ngx-bootstrap, Angular, and Bootstrap:

ngx-bootstrap: 5.1.1

Angular: 8.0.0

Bootstrap: 4.3.1

Build system: Angular CLI, System.js, webpack, starter seed:

ejasarevic avatar Jul 31 '19 13:07 ejasarevic

5 years later, still no answer... What a shame...

on3dd avatar Oct 11 '24 05:10 on3dd

Posting this in case someone else needs a quick workaround. Unfortunately, I had to override PopoverDirective instead of extending it since one of the dependencies is not exported ComponentLoaderFactory:

import { ElementRef } from '@angular/core';
import { PopoverDirective } from 'ngx-bootstrap/popover';

export const overridePopoverWithHover = () => {
  const proShow = PopoverDirective.prototype.show
  const proHide = PopoverDirective.prototype.hide
  const proOnDestroy = PopoverDirective.prototype.ngOnDestroy
  const hoverMap = new Map<string, boolean>()
  const getHandlers = (ref: ElementRef, hide: () => void, delay: number) => {
    if (!hoverMap.has(ref.nativeElement.id)) hoverMap.set(ref.nativeElement.id, false)

    return {
      isHovering: () => !!hoverMap.get(ref.nativeElement.id),
      enter: {
        event: 'mouseenter',
        handler: () => hoverMap.set(ref.nativeElement.id, true),
      },
      leave: {
        event: 'mouseleave',
        handler: () => {
          hoverMap.set(ref.nativeElement.id, false)
          setTimeout(() => hide(), delay)
        },
      },
    }
  }

  PopoverDirective.prototype.show = function () {
    proShow.call(this)

    const handlers = getHandlers(this['_elementRef'], this.hide.bind(this), this.delay)
    const el = this['_popover']?._componentRef?.location?.nativeElement

    el?.addEventListener(handlers.enter.event, handlers.enter.handler)
    el?.addEventListener(handlers.leave.event, handlers.leave.handler)
  }

  PopoverDirective.prototype.hide = function () {
    setTimeout(() => {
      const handlers = getHandlers(this['_elementRef'], this.hide.bind(this), this.delay)
      const el = this['_popover']?._componentRef?.location?.nativeElement

      if (handlers.isHovering()) return

      el?.removeEventListener(handlers.enter.event, handlers.enter.handler)
      el?.removeEventListener(handlers.leave.event, handlers.leave.handler)
      proHide.call(this)
    }, this.delay)
  }

  PopoverDirective.prototype.ngOnDestroy = function () {
    const handlers = getHandlers(this['_elementRef'], this.hide.bind(this), this.delay)
    const el = this['_popover']?._componentRef?.location?.nativeElement

    el?.removeEventListener(handlers.enter.event, handlers.enter.handler)
    el?.removeEventListener(handlers.leave.event, handlers.leave.handler)
    proOnDestroy.call(this)
  }
}

mrf345 avatar Jul 08 '25 13:07 mrf345