1- import React , { useEffect , useState , useRef , useCallback } from 'react'
1+ import React , { useEffect , useState , useRef , useCallback , useImperativeHandle } from 'react'
22import { autoUpdate } from '@floating-ui/dom'
33import classNames from 'classnames'
44import debounce from 'utils/debounce'
@@ -8,10 +8,11 @@ import { getScrollParent } from 'utils/get-scroll-parent'
88import { computeTooltipPosition } from 'utils/compute-positions'
99import coreStyles from './core-styles.module.css'
1010import styles from './styles.module.css'
11- import type { IPosition , ITooltip , PlacesType } from './TooltipTypes'
11+ import type { IPosition , ITooltip , PlacesType , TooltipImperativeOpenOptions } from './TooltipTypes'
1212
1313const Tooltip = ( {
1414 // props
15+ forwardRef,
1516 id,
1617 className,
1718 classNameArrow,
@@ -58,6 +59,9 @@ const Tooltip = ({
5859 const [ inlineArrowStyles , setInlineArrowStyles ] = useState ( { } )
5960 const [ show , setShow ] = useState ( false )
6061 const [ rendered , setRendered ] = useState ( false )
62+ const [ imperativeOptions , setImperativeOptions ] = useState < TooltipImperativeOpenOptions | null > (
63+ null ,
64+ )
6165 const wasShowing = useRef ( false )
6266 const lastFloatPosition = useRef < IPosition | null > ( null )
6367 /**
@@ -149,6 +153,7 @@ const Tooltip = ({
149153 if ( show ) {
150154 afterShow ?.( )
151155 } else {
156+ setImperativeOptions ( null )
152157 afterHide ?.( )
153158 }
154159 } , [ show ] )
@@ -274,6 +279,9 @@ const Tooltip = ({
274279 }
275280
276281 const handleClickOutsideAnchors = ( event : MouseEvent ) => {
282+ if ( ! show ) {
283+ return
284+ }
277285 const anchorById = document . querySelector < HTMLElement > ( `[id='${ anchorId } ']` )
278286 const anchors = [ anchorById , ...anchorsBySelect ]
279287 if ( anchors . some ( ( anchor ) => anchor ?. contains ( event . target as HTMLElement ) ) ) {
@@ -293,9 +301,10 @@ const Tooltip = ({
293301 const debouncedHandleShowTooltip = debounce ( handleShowTooltip , 50 , true )
294302 const debouncedHandleHideTooltip = debounce ( handleHideTooltip , 50 , true )
295303 const updateTooltipPosition = useCallback ( ( ) => {
296- if ( position ) {
304+ const actualPosition = imperativeOptions ?. position ?? position
305+ if ( actualPosition ) {
297306 // if `position` is set, override regular and `float` positioning
298- handleTooltipPosition ( position )
307+ handleTooltipPosition ( actualPosition )
299308 return
300309 }
301310
@@ -349,6 +358,7 @@ const Tooltip = ({
349358 offset ,
350359 positionStrategy ,
351360 position ,
361+ imperativeOptions ?. position ,
352362 float ,
353363 ] )
354364
@@ -484,7 +494,7 @@ const Tooltip = ({
484494 ] )
485495
486496 useEffect ( ( ) => {
487- let selector = anchorSelect ?? ''
497+ let selector = imperativeOptions ?. anchorSelect ?? anchorSelect ?? ''
488498 if ( ! selector && id ) {
489499 selector = `[data-tooltip-id='${ id } ']`
490500 }
@@ -584,7 +594,7 @@ const Tooltip = ({
584594 return ( ) => {
585595 documentObserver . disconnect ( )
586596 }
587- } , [ id , anchorSelect , activeAnchor ] )
597+ } , [ id , anchorSelect , imperativeOptions ?. anchorSelect , activeAnchor ] )
588598
589599 useEffect ( ( ) => {
590600 updateTooltipPosition ( )
@@ -628,7 +638,7 @@ const Tooltip = ({
628638 } , [ ] )
629639
630640 useEffect ( ( ) => {
631- let selector = anchorSelect
641+ let selector = imperativeOptions ?. anchorSelect ?? anchorSelect
632642 if ( ! selector && id ) {
633643 selector = `[data-tooltip-id='${ id } ']`
634644 }
@@ -642,9 +652,34 @@ const Tooltip = ({
642652 // warning was already issued in the controller
643653 setAnchorsBySelect ( [ ] )
644654 }
645- } , [ id , anchorSelect ] )
655+ } , [ id , anchorSelect , imperativeOptions ?. anchorSelect ] )
656+
657+ const actualContent = imperativeOptions ?. content ?? content
658+ const canShow = Boolean ( ! hidden && actualContent && show && Object . keys ( inlineStyles ) . length > 0 )
646659
647- const canShow = ! hidden && content && show && Object . keys ( inlineStyles ) . length > 0
660+ useImperativeHandle ( forwardRef , ( ) => ( {
661+ open : ( options ) => {
662+ if ( options ?. anchorSelect ) {
663+ try {
664+ document . querySelector ( options . anchorSelect )
665+ } catch {
666+ if ( ! process . env . NODE_ENV || process . env . NODE_ENV !== 'production' ) {
667+ // eslint-disable-next-line no-console
668+ console . warn ( `[react-tooltip] "${ options . anchorSelect } " is not a valid CSS selector` )
669+ }
670+ return
671+ }
672+ }
673+ setImperativeOptions ( options ?? null )
674+ handleShow ( true )
675+ } ,
676+ close : ( ) => {
677+ handleShow ( false )
678+ } ,
679+ activeAnchor,
680+ place : actualPlacement ,
681+ isOpen : rendered && canShow ,
682+ } ) )
648683
649684 return rendered ? (
650685 < WrapperElement
@@ -671,7 +706,7 @@ const Tooltip = ({
671706 } }
672707 ref = { tooltipRef }
673708 >
674- { content }
709+ { actualContent }
675710 < WrapperElement
676711 className = { classNames (
677712 'react-tooltip-arrow' ,
0 commit comments