diff --git a/src/components/Tooltip/Tooltip.tsx b/src/components/Tooltip/Tooltip.tsx
index 32297bbf..eda9e2d9 100644
--- a/src/components/Tooltip/Tooltip.tsx
+++ b/src/components/Tooltip/Tooltip.tsx
@@ -1,4 +1,4 @@
-import React, { useEffect, useState, useRef, useCallback } from 'react'
+import React, { useEffect, useState, useRef, useCallback, useImperativeHandle } from 'react'
import { autoUpdate } from '@floating-ui/dom'
import classNames from 'classnames'
import debounce from 'utils/debounce'
@@ -15,10 +15,12 @@ import type {
IPosition,
ITooltip,
PlacesType,
+ TooltipImperativeOpenOptions,
} from './TooltipTypes'
const Tooltip = ({
// props
+ forwardRef,
id,
className,
classNameArrow,
@@ -44,6 +46,7 @@ const Tooltip = ({
openEvents,
closeEvents,
globalCloseEvents,
+ imperativeModeOnly,
style: externalStyles,
position,
afterShow,
@@ -68,6 +71,9 @@ const Tooltip = ({
const [inlineArrowStyles, setInlineArrowStyles] = useState({})
const [show, setShow] = useState(false)
const [rendered, setRendered] = useState(false)
+ const [imperativeOptions, setImperativeOptions] = useState
(
+ null,
+ )
const wasShowing = useRef(false)
const lastFloatPosition = useRef(null)
/**
@@ -106,6 +112,8 @@ const Tooltip = ({
mouseleave: true,
blur: true,
click: false,
+ dblclick: false,
+ mouseup: false,
}
if (!closeEvents && shouldOpenOnClick) {
Object.assign(actualCloseEvents, {
@@ -122,6 +130,29 @@ const Tooltip = ({
clickOutsideAnchor: hasClickEvent || false,
}
+ if (imperativeModeOnly) {
+ Object.assign(actualOpenEvents, {
+ mouseenter: false,
+ focus: false,
+ click: false,
+ dblclick: false,
+ mousedown: false,
+ })
+ Object.assign(actualCloseEvents, {
+ mouseleave: false,
+ blur: false,
+ click: false,
+ dblclick: false,
+ mouseup: false,
+ })
+ Object.assign(actualGlobalCloseEvents, {
+ escape: false,
+ scroll: false,
+ resize: false,
+ clickOutsideAnchor: false,
+ })
+ }
+
/**
* useLayoutEffect runs before useEffect,
* but should be used carefully because of caveats
@@ -183,18 +214,20 @@ const Tooltip = ({
if (show) {
afterShow?.()
} else {
- afterHide?.()
+ /**
+ * see `onTransitionEnd` on tooltip wrapper
+ */
}
}, [show])
- const handleShowTooltipDelayed = () => {
+ const handleShowTooltipDelayed = (delay = delayShow) => {
if (tooltipShowDelayTimerRef.current) {
clearTimeout(tooltipShowDelayTimerRef.current)
}
tooltipShowDelayTimerRef.current = setTimeout(() => {
handleShow(true)
- }, delayShow)
+ }, delay)
}
const handleHideTooltipDelayed = (delay = delayHide) => {
@@ -268,7 +301,7 @@ const Tooltip = ({
},
} as Element
computeTooltipPosition({
- place,
+ place: imperativeOptions?.place ?? place,
offset,
elementReference: virtualElement,
tooltipReference: tooltipRef.current,
@@ -301,12 +334,16 @@ const Tooltip = ({
}
const handleClickOutsideAnchors = (event: MouseEvent) => {
- const anchorById = document.querySelector(`[id='${anchorId}']`)
- const anchors = [anchorById, ...anchorsBySelect]
- if (anchors.some((anchor) => anchor?.contains(event.target as HTMLElement))) {
+ if (!show) {
return
}
- if (tooltipRef.current?.contains(event.target as HTMLElement)) {
+ const target = event.target as HTMLElement
+ if (tooltipRef.current?.contains(target)) {
+ return
+ }
+ const anchorById = document.querySelector(`[id='${anchorId}']`)
+ const anchors = [anchorById, ...anchorsBySelect]
+ if (anchors.some((anchor) => anchor?.contains(target))) {
return
}
handleShow(false)
@@ -320,9 +357,10 @@ const Tooltip = ({
const debouncedHandleShowTooltip = debounce(handleShowTooltip, 50, true)
const debouncedHandleHideTooltip = debounce(handleHideTooltip, 50, true)
const updateTooltipPosition = useCallback(() => {
- if (position) {
+ const actualPosition = imperativeOptions?.position ?? position
+ if (actualPosition) {
// if `position` is set, override regular and `float` positioning
- handleTooltipPosition(position)
+ handleTooltipPosition(actualPosition)
return
}
@@ -346,7 +384,7 @@ const Tooltip = ({
}
computeTooltipPosition({
- place,
+ place: imperativeOptions?.place ?? place,
offset,
elementReference: activeAnchor,
tooltipReference: tooltipRef.current,
@@ -373,9 +411,11 @@ const Tooltip = ({
content,
externalStyles,
place,
+ imperativeOptions?.place,
offset,
positionStrategy,
position,
+ imperativeOptions?.position,
float,
])
@@ -550,7 +590,7 @@ const Tooltip = ({
])
useEffect(() => {
- let selector = anchorSelect ?? ''
+ let selector = imperativeOptions?.anchorSelect ?? anchorSelect ?? ''
if (!selector && id) {
selector = `[data-tooltip-id='${id}']`
}
@@ -650,7 +690,7 @@ const Tooltip = ({
return () => {
documentObserver.disconnect()
}
- }, [id, anchorSelect, activeAnchor])
+ }, [id, anchorSelect, imperativeOptions?.anchorSelect, activeAnchor])
useEffect(() => {
updateTooltipPosition()
@@ -694,7 +734,7 @@ const Tooltip = ({
}, [])
useEffect(() => {
- let selector = anchorSelect
+ let selector = imperativeOptions?.anchorSelect ?? anchorSelect
if (!selector && id) {
selector = `[data-tooltip-id='${id}']`
}
@@ -708,11 +748,44 @@ const Tooltip = ({
// warning was already issued in the controller
setAnchorsBySelect([])
}
- }, [id, anchorSelect])
+ }, [id, anchorSelect, imperativeOptions?.anchorSelect])
+ const actualContent = imperativeOptions?.content ?? content
const canShow = show && Object.keys(inlineStyles).length > 0
- return rendered && !hidden && content ? (
+ useImperativeHandle(forwardRef, () => ({
+ open: (options) => {
+ if (options?.anchorSelect) {
+ try {
+ document.querySelector(options.anchorSelect)
+ } catch {
+ if (!process.env.NODE_ENV || process.env.NODE_ENV !== 'production') {
+ // eslint-disable-next-line no-console
+ console.warn(`[react-tooltip] "${options.anchorSelect}" is not a valid CSS selector`)
+ }
+ return
+ }
+ }
+ setImperativeOptions(options ?? null)
+ if (options?.delay) {
+ handleShowTooltipDelayed(options.delay)
+ } else {
+ handleShow(true)
+ }
+ },
+ close: (options) => {
+ if (options?.delay) {
+ handleHideTooltipDelayed(options.delay)
+ } else {
+ handleShow(false)
+ }
+ },
+ activeAnchor,
+ place: actualPlacement,
+ isOpen: Boolean(rendered && !hidden && actualContent && canShow),
+ }))
+
+ return rendered && !hidden && actualContent ? (
- {content}
+ {actualContent}
void
+ close: (options?: TooltipImperativeCloseOptions) => void
+ /**
+ * @readonly
+ */
+ activeAnchor: HTMLElement | null
+ /**
+ * @readonly
+ */
+ place: PlacesType
+ /**
+ * @readonly
+ */
+ isOpen: boolean
+}
+
export type AnchorOpenEvents = {
mouseenter?: boolean
focus?: boolean
@@ -71,6 +106,7 @@ export type GlobalCloseEvents = {
}
export interface ITooltip {
+ forwardRef?: React.ForwardedRef
className?: string
classNameArrow?: string
content?: ChildrenType
@@ -105,6 +141,7 @@ export interface ITooltip {
openEvents?: AnchorOpenEvents
closeEvents?: AnchorCloseEvents
globalCloseEvents?: GlobalCloseEvents
+ imperativeModeOnly?: boolean
style?: CSSProperties
position?: IPosition
isOpen?: boolean
diff --git a/src/components/TooltipController/TooltipController.tsx b/src/components/TooltipController/TooltipController.tsx
index b5613170..cdc65cff 100644
--- a/src/components/TooltipController/TooltipController.tsx
+++ b/src/components/TooltipController/TooltipController.tsx
@@ -9,348 +9,357 @@ import type {
DataAttribute,
ITooltip,
ChildrenType,
+ TooltipRefProps,
} from 'components/Tooltip/TooltipTypes'
import { useTooltip } from 'components/TooltipProvider'
import { TooltipContent } from 'components/TooltipContent'
import cssSupports from 'utils/css-supports'
import type { ITooltipController } from './TooltipControllerTypes'
-const TooltipController = ({
- id,
- anchorId,
- anchorSelect,
- content,
- html,
- render,
- className,
- classNameArrow,
- variant = 'dark',
- place = 'top',
- offset = 10,
- wrapper = 'div',
- children = null,
- events = ['hover'],
- openOnClick = false,
- positionStrategy = 'absolute',
- middlewares,
- delayShow = 0,
- delayHide = 0,
- float = false,
- hidden = false,
- noArrow = false,
- clickable = false,
- closeOnEsc = false,
- closeOnScroll = false,
- closeOnResize = false,
- openEvents,
- closeEvents,
- globalCloseEvents,
- style,
- position,
- isOpen,
- disableStyleInjection = false,
- border,
- opacity,
- arrowColor,
- setIsOpen,
- afterShow,
- afterHide,
-}: ITooltipController) => {
- const [tooltipContent, setTooltipContent] = useState(content)
- const [tooltipHtml, setTooltipHtml] = useState(html)
- const [tooltipPlace, setTooltipPlace] = useState(place)
- const [tooltipVariant, setTooltipVariant] = useState(variant)
- const [tooltipOffset, setTooltipOffset] = useState(offset)
- const [tooltipDelayShow, setTooltipDelayShow] = useState(delayShow)
- const [tooltipDelayHide, setTooltipDelayHide] = useState(delayHide)
- const [tooltipFloat, setTooltipFloat] = useState(float)
- const [tooltipHidden, setTooltipHidden] = useState(hidden)
- const [tooltipWrapper, setTooltipWrapper] = useState(wrapper)
- const [tooltipEvents, setTooltipEvents] = useState(events)
- const [tooltipPositionStrategy, setTooltipPositionStrategy] = useState(positionStrategy)
- const [activeAnchor, setActiveAnchor] = useState(null)
- const styleInjectionRef = useRef(disableStyleInjection)
- /**
- * @todo Remove this in a future version (provider/wrapper method is deprecated)
- */
- const { anchorRefs, activeAnchor: providerActiveAnchor } = useTooltip(id)
+const TooltipController = React.forwardRef(
+ (
+ {
+ id,
+ anchorId,
+ anchorSelect,
+ content,
+ html,
+ render,
+ className,
+ classNameArrow,
+ variant = 'dark',
+ place = 'top',
+ offset = 10,
+ wrapper = 'div',
+ children = null,
+ events = ['hover'],
+ openOnClick = false,
+ positionStrategy = 'absolute',
+ middlewares,
+ delayShow = 0,
+ delayHide = 0,
+ float = false,
+ hidden = false,
+ noArrow = false,
+ clickable = false,
+ closeOnEsc = false,
+ closeOnScroll = false,
+ closeOnResize = false,
+ openEvents,
+ closeEvents,
+ globalCloseEvents,
+ imperativeModeOnly = false,
+ style,
+ position,
+ isOpen,
+ disableStyleInjection = false,
+ border,
+ opacity,
+ arrowColor,
+ setIsOpen,
+ afterShow,
+ afterHide,
+ }: ITooltipController,
+ ref,
+ ) => {
+ const [tooltipContent, setTooltipContent] = useState(content)
+ const [tooltipHtml, setTooltipHtml] = useState(html)
+ const [tooltipPlace, setTooltipPlace] = useState(place)
+ const [tooltipVariant, setTooltipVariant] = useState(variant)
+ const [tooltipOffset, setTooltipOffset] = useState(offset)
+ const [tooltipDelayShow, setTooltipDelayShow] = useState(delayShow)
+ const [tooltipDelayHide, setTooltipDelayHide] = useState(delayHide)
+ const [tooltipFloat, setTooltipFloat] = useState(float)
+ const [tooltipHidden, setTooltipHidden] = useState(hidden)
+ const [tooltipWrapper, setTooltipWrapper] = useState(wrapper)
+ const [tooltipEvents, setTooltipEvents] = useState(events)
+ const [tooltipPositionStrategy, setTooltipPositionStrategy] = useState(positionStrategy)
+ const [activeAnchor, setActiveAnchor] = useState(null)
+ const styleInjectionRef = useRef(disableStyleInjection)
+ /**
+ * @todo Remove this in a future version (provider/wrapper method is deprecated)
+ */
+ const { anchorRefs, activeAnchor: providerActiveAnchor } = useTooltip(id)
- const getDataAttributesFromAnchorElement = (elementReference: HTMLElement) => {
- const dataAttributes = elementReference?.getAttributeNames().reduce((acc, name) => {
- if (name.startsWith('data-tooltip-')) {
- const parsedAttribute = name.replace(/^data-tooltip-/, '') as DataAttribute
- acc[parsedAttribute] = elementReference?.getAttribute(name) ?? null
- }
- return acc
- }, {} as Record)
+ const getDataAttributesFromAnchorElement = (elementReference: HTMLElement) => {
+ const dataAttributes = elementReference?.getAttributeNames().reduce((acc, name) => {
+ if (name.startsWith('data-tooltip-')) {
+ const parsedAttribute = name.replace(/^data-tooltip-/, '') as DataAttribute
+ acc[parsedAttribute] = elementReference?.getAttribute(name) ?? null
+ }
+ return acc
+ }, {} as Record)
- return dataAttributes
- }
+ return dataAttributes
+ }
- const applyAllDataAttributesFromAnchorElement = (
- dataAttributes: Record,
- ) => {
- const handleDataAttributes: Record void> = {
- place: (value) => {
- setTooltipPlace((value as PlacesType) ?? place)
- },
- content: (value) => {
- setTooltipContent(value ?? content)
- },
- html: (value) => {
- setTooltipHtml(value ?? html)
- },
- variant: (value) => {
- setTooltipVariant((value as VariantType) ?? variant)
- },
- offset: (value) => {
- setTooltipOffset(value === null ? offset : Number(value))
- },
- wrapper: (value) => {
- setTooltipWrapper((value as WrapperType) ?? wrapper)
- },
- events: (value) => {
- const parsed = value?.split(' ') as EventsType[]
- setTooltipEvents(parsed ?? events)
- },
- 'position-strategy': (value) => {
- setTooltipPositionStrategy((value as PositionStrategy) ?? positionStrategy)
- },
- 'delay-show': (value) => {
- setTooltipDelayShow(value === null ? delayShow : Number(value))
- },
- 'delay-hide': (value) => {
- setTooltipDelayHide(value === null ? delayHide : Number(value))
- },
- float: (value) => {
- setTooltipFloat(value === null ? float : value === 'true')
- },
- hidden: (value) => {
- setTooltipHidden(value === null ? hidden : value === 'true')
- },
+ const applyAllDataAttributesFromAnchorElement = (
+ dataAttributes: Record,
+ ) => {
+ const handleDataAttributes: Record void> = {
+ place: (value) => {
+ setTooltipPlace((value as PlacesType) ?? place)
+ },
+ content: (value) => {
+ setTooltipContent(value ?? content)
+ },
+ html: (value) => {
+ setTooltipHtml(value ?? html)
+ },
+ variant: (value) => {
+ setTooltipVariant((value as VariantType) ?? variant)
+ },
+ offset: (value) => {
+ setTooltipOffset(value === null ? offset : Number(value))
+ },
+ wrapper: (value) => {
+ setTooltipWrapper((value as WrapperType) ?? wrapper)
+ },
+ events: (value) => {
+ const parsed = value?.split(' ') as EventsType[]
+ setTooltipEvents(parsed ?? events)
+ },
+ 'position-strategy': (value) => {
+ setTooltipPositionStrategy((value as PositionStrategy) ?? positionStrategy)
+ },
+ 'delay-show': (value) => {
+ setTooltipDelayShow(value === null ? delayShow : Number(value))
+ },
+ 'delay-hide': (value) => {
+ setTooltipDelayHide(value === null ? delayHide : Number(value))
+ },
+ float: (value) => {
+ setTooltipFloat(value === null ? float : value === 'true')
+ },
+ hidden: (value) => {
+ setTooltipHidden(value === null ? hidden : value === 'true')
+ },
+ }
+ // reset unset data attributes to default values
+ // without this, data attributes from the last active anchor will still be used
+ Object.values(handleDataAttributes).forEach((handler) => handler(null))
+ Object.entries(dataAttributes).forEach(([key, value]) => {
+ handleDataAttributes[key as DataAttribute]?.(value)
+ })
}
- // reset unset data attributes to default values
- // without this, data attributes from the last active anchor will still be used
- Object.values(handleDataAttributes).forEach((handler) => handler(null))
- Object.entries(dataAttributes).forEach(([key, value]) => {
- handleDataAttributes[key as DataAttribute]?.(value)
- })
- }
- useEffect(() => {
- setTooltipContent(content)
- }, [content])
+ useEffect(() => {
+ setTooltipContent(content)
+ }, [content])
- useEffect(() => {
- setTooltipHtml(html)
- }, [html])
+ useEffect(() => {
+ setTooltipHtml(html)
+ }, [html])
- useEffect(() => {
- setTooltipPlace(place)
- }, [place])
+ useEffect(() => {
+ setTooltipPlace(place)
+ }, [place])
- useEffect(() => {
- setTooltipVariant(variant)
- }, [variant])
+ useEffect(() => {
+ setTooltipVariant(variant)
+ }, [variant])
- useEffect(() => {
- setTooltipOffset(offset)
- }, [offset])
+ useEffect(() => {
+ setTooltipOffset(offset)
+ }, [offset])
- useEffect(() => {
- setTooltipDelayShow(delayShow)
- }, [delayShow])
+ useEffect(() => {
+ setTooltipDelayShow(delayShow)
+ }, [delayShow])
- useEffect(() => {
- setTooltipDelayHide(delayHide)
- }, [delayHide])
+ useEffect(() => {
+ setTooltipDelayHide(delayHide)
+ }, [delayHide])
- useEffect(() => {
- setTooltipFloat(float)
- }, [float])
+ useEffect(() => {
+ setTooltipFloat(float)
+ }, [float])
- useEffect(() => {
- setTooltipHidden(hidden)
- }, [hidden])
+ useEffect(() => {
+ setTooltipHidden(hidden)
+ }, [hidden])
- useEffect(() => {
- setTooltipPositionStrategy(positionStrategy)
- }, [positionStrategy])
+ useEffect(() => {
+ setTooltipPositionStrategy(positionStrategy)
+ }, [positionStrategy])
- useEffect(() => {
- if (styleInjectionRef.current === disableStyleInjection) {
- return
- }
- if (process.env.NODE_ENV !== 'production') {
- // eslint-disable-next-line no-console
- console.warn('[react-tooltip] Do not change `disableStyleInjection` dynamically.')
- }
- }, [disableStyleInjection])
+ useEffect(() => {
+ if (styleInjectionRef.current === disableStyleInjection) {
+ return
+ }
+ if (process.env.NODE_ENV !== 'production') {
+ // eslint-disable-next-line no-console
+ console.warn('[react-tooltip] Do not change `disableStyleInjection` dynamically.')
+ }
+ }, [disableStyleInjection])
- useEffect(() => {
- if (typeof window !== 'undefined') {
- window.dispatchEvent(
- new CustomEvent('react-tooltip-inject-styles', {
- detail: {
- disableCore: disableStyleInjection === 'core',
- disableBase: disableStyleInjection,
- },
- }),
- )
- }
- }, [])
+ useEffect(() => {
+ if (typeof window !== 'undefined') {
+ window.dispatchEvent(
+ new CustomEvent('react-tooltip-inject-styles', {
+ detail: {
+ disableCore: disableStyleInjection === 'core',
+ disableBase: disableStyleInjection,
+ },
+ }),
+ )
+ }
+ }, [])
- useEffect(() => {
- const elementRefs = new Set(anchorRefs)
+ useEffect(() => {
+ const elementRefs = new Set(anchorRefs)
- let selector = anchorSelect
- if (!selector && id) {
- selector = `[data-tooltip-id='${id}']`
- }
- if (selector) {
- try {
- const anchorsBySelect = document.querySelectorAll(selector)
- anchorsBySelect.forEach((anchor) => {
- elementRefs.add({ current: anchor })
- })
- } catch {
- if (!process.env.NODE_ENV || process.env.NODE_ENV !== 'production') {
- // eslint-disable-next-line no-console
- console.warn(`[react-tooltip] "${selector}" is not a valid CSS selector`)
+ let selector = anchorSelect
+ if (!selector && id) {
+ selector = `[data-tooltip-id='${id}']`
+ }
+ if (selector) {
+ try {
+ const anchorsBySelect = document.querySelectorAll(selector)
+ anchorsBySelect.forEach((anchor) => {
+ elementRefs.add({ current: anchor })
+ })
+ } catch {
+ if (!process.env.NODE_ENV || process.env.NODE_ENV !== 'production') {
+ // eslint-disable-next-line no-console
+ console.warn(`[react-tooltip] "${selector}" is not a valid CSS selector`)
+ }
}
}
- }
- const anchorById = document.querySelector(`[id='${anchorId}']`)
- if (anchorById) {
- elementRefs.add({ current: anchorById })
- }
+ const anchorById = document.querySelector(`[id='${anchorId}']`)
+ if (anchorById) {
+ elementRefs.add({ current: anchorById })
+ }
- if (!elementRefs.size) {
- return () => null
- }
+ if (!elementRefs.size) {
+ return () => null
+ }
- const anchorElement = activeAnchor ?? anchorById ?? providerActiveAnchor.current
+ const anchorElement = activeAnchor ?? anchorById ?? providerActiveAnchor.current
- const observerCallback: MutationCallback = (mutationList) => {
- mutationList.forEach((mutation) => {
- if (
- !anchorElement ||
- mutation.type !== 'attributes' ||
- !mutation.attributeName?.startsWith('data-tooltip-')
- ) {
- return
- }
- // make sure to get all set attributes, since all unset attributes are reset
+ const observerCallback: MutationCallback = (mutationList) => {
+ mutationList.forEach((mutation) => {
+ if (
+ !anchorElement ||
+ mutation.type !== 'attributes' ||
+ !mutation.attributeName?.startsWith('data-tooltip-')
+ ) {
+ return
+ }
+ // make sure to get all set attributes, since all unset attributes are reset
+ const dataAttributes = getDataAttributesFromAnchorElement(anchorElement)
+ applyAllDataAttributesFromAnchorElement(dataAttributes)
+ })
+ }
+
+ // Create an observer instance linked to the callback function
+ const observer = new MutationObserver(observerCallback)
+
+ // do not check for subtree and childrens, we only want to know attribute changes
+ // to stay watching `data-attributes-*` from anchor element
+ const observerConfig = { attributes: true, childList: false, subtree: false }
+
+ if (anchorElement) {
const dataAttributes = getDataAttributesFromAnchorElement(anchorElement)
applyAllDataAttributesFromAnchorElement(dataAttributes)
- })
- }
+ // Start observing the target node for configured mutations
+ observer.observe(anchorElement, observerConfig)
+ }
- // Create an observer instance linked to the callback function
- const observer = new MutationObserver(observerCallback)
+ return () => {
+ // Remove the observer when the tooltip is destroyed
+ observer.disconnect()
+ }
+ }, [anchorRefs, providerActiveAnchor, activeAnchor, anchorId, anchorSelect])
- // do not check for subtree and childrens, we only want to know attribute changes
- // to stay watching `data-attributes-*` from anchor element
- const observerConfig = { attributes: true, childList: false, subtree: false }
+ useEffect(() => {
+ if (process.env.NODE_ENV === 'production') {
+ return
+ }
+ if (style?.border) {
+ // eslint-disable-next-line no-console
+ console.warn('[react-tooltip] Do not set `style.border`. Use `border` prop instead.')
+ }
+ if (border && !cssSupports('border', `${border}`)) {
+ // eslint-disable-next-line no-console
+ console.warn(`[react-tooltip] "${border}" is not a valid \`border\`.`)
+ }
+ if (style?.opacity) {
+ // eslint-disable-next-line no-console
+ console.warn('[react-tooltip] Do not set `style.opacity`. Use `opacity` prop instead.')
+ }
+ if (opacity && !cssSupports('opacity', `${opacity}`)) {
+ // eslint-disable-next-line no-console
+ console.warn(`[react-tooltip] "${opacity}" is not a valid \`opacity\`.`)
+ }
+ }, [])
- if (anchorElement) {
- const dataAttributes = getDataAttributesFromAnchorElement(anchorElement)
- applyAllDataAttributesFromAnchorElement(dataAttributes)
- // Start observing the target node for configured mutations
- observer.observe(anchorElement, observerConfig)
+ /**
+ * content priority: children < render or content < html
+ * children should be lower priority so that it can be used as the "default" content
+ */
+ let renderedContent: ChildrenType = children
+ const contentWrapperRef = useRef(null)
+ if (render) {
+ const rendered = render({ content: tooltipContent ?? null, activeAnchor }) as React.ReactNode
+ renderedContent = rendered ? (
+
+ {rendered}
+
+ ) : null
+ } else if (tooltipContent) {
+ renderedContent = tooltipContent
}
-
- return () => {
- // Remove the observer when the tooltip is destroyed
- observer.disconnect()
+ if (tooltipHtml) {
+ renderedContent =
}
- }, [anchorRefs, providerActiveAnchor, activeAnchor, anchorId, anchorSelect])
- useEffect(() => {
- if (process.env.NODE_ENV === 'production') {
- return
- }
- if (style?.border) {
- // eslint-disable-next-line no-console
- console.warn('[react-tooltip] Do not set `style.border`. Use `border` prop instead.')
+ const props: ITooltip = {
+ forwardRef: ref,
+ id,
+ anchorId,
+ anchorSelect,
+ className,
+ classNameArrow,
+ content: renderedContent,
+ contentWrapperRef,
+ place: tooltipPlace,
+ variant: tooltipVariant,
+ offset: tooltipOffset,
+ wrapper: tooltipWrapper,
+ events: tooltipEvents,
+ openOnClick,
+ positionStrategy: tooltipPositionStrategy,
+ middlewares,
+ delayShow: tooltipDelayShow,
+ delayHide: tooltipDelayHide,
+ float: tooltipFloat,
+ hidden: tooltipHidden,
+ noArrow,
+ clickable,
+ closeOnEsc,
+ closeOnScroll,
+ closeOnResize,
+ openEvents,
+ closeEvents,
+ globalCloseEvents,
+ imperativeModeOnly,
+ style,
+ position,
+ isOpen,
+ border,
+ opacity,
+ arrowColor,
+ setIsOpen,
+ afterShow,
+ afterHide,
+ activeAnchor,
+ setActiveAnchor: (anchor: HTMLElement | null) => setActiveAnchor(anchor),
}
- if (border && !cssSupports('border', `${border}`)) {
- // eslint-disable-next-line no-console
- console.warn(`[react-tooltip] "${border}" is not a valid \`border\`.`)
- }
- if (style?.opacity) {
- // eslint-disable-next-line no-console
- console.warn('[react-tooltip] Do not set `style.opacity`. Use `opacity` prop instead.')
- }
- if (opacity && !cssSupports('opacity', `${opacity}`)) {
- // eslint-disable-next-line no-console
- console.warn(`[react-tooltip] "${opacity}" is not a valid \`opacity\`.`)
- }
- }, [])
-
- /**
- * content priority: children < render or content < html
- * children should be lower priority so that it can be used as the "default" content
- */
- let renderedContent: ChildrenType = children
- const contentWrapperRef = useRef(null)
- if (render) {
- const rendered = render({ content: tooltipContent ?? null, activeAnchor }) as React.ReactNode
- renderedContent = rendered ? (
-
- {rendered}
-
- ) : null
- } else if (tooltipContent) {
- renderedContent = tooltipContent
- }
- if (tooltipHtml) {
- renderedContent =
- }
-
- const props: ITooltip = {
- id,
- anchorId,
- anchorSelect,
- className,
- classNameArrow,
- content: renderedContent,
- contentWrapperRef,
- place: tooltipPlace,
- variant: tooltipVariant,
- offset: tooltipOffset,
- wrapper: tooltipWrapper,
- events: tooltipEvents,
- openOnClick,
- positionStrategy: tooltipPositionStrategy,
- middlewares,
- delayShow: tooltipDelayShow,
- delayHide: tooltipDelayHide,
- float: tooltipFloat,
- hidden: tooltipHidden,
- noArrow,
- clickable,
- closeOnEsc,
- closeOnScroll,
- closeOnResize,
- openEvents,
- closeEvents,
- globalCloseEvents,
- style,
- position,
- isOpen,
- border,
- opacity,
- arrowColor,
- setIsOpen,
- afterShow,
- afterHide,
- activeAnchor,
- setActiveAnchor: (anchor: HTMLElement | null) => setActiveAnchor(anchor),
- }
- return
-}
+ return
+ },
+)
export default TooltipController
diff --git a/src/components/TooltipController/TooltipControllerTypes.d.ts b/src/components/TooltipController/TooltipControllerTypes.d.ts
index deeb2afc..101463eb 100644
--- a/src/components/TooltipController/TooltipControllerTypes.d.ts
+++ b/src/components/TooltipController/TooltipControllerTypes.d.ts
@@ -72,6 +72,11 @@ export interface ITooltipController {
* @description The global events listened to close the tooltip.
*/
globalCloseEvents?: GlobalCloseEvents
+ /**
+ * @description Used to disable default tooltip behavior.
+ * Overrides `openEvents`, `closeEvents`, and `globalCloseEvents`.
+ */
+ imperativeModeOnly?: boolean
style?: CSSProperties
position?: IPosition
isOpen?: boolean
diff --git a/src/index.tsx b/src/index.tsx
index 6dc35544..c11ef9cf 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -12,6 +12,7 @@ import type {
WrapperType,
IPosition,
Middleware,
+ TooltipRefProps,
} from './components/Tooltip/TooltipTypes'
import type { ITooltipController } from './components/TooltipController/TooltipControllerTypes'
import type { ITooltipWrapper } from './components/TooltipProvider/TooltipProviderTypes'
@@ -47,6 +48,7 @@ export type {
ITooltipWrapper,
IPosition,
Middleware,
+ TooltipRefProps,
}
export { removeStyle } from './utils/handle-style'