Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 69f0147

Browse files
gabrieljablonskidanielbarion
authored andcommittedApr 16, 2024
chore: add hook rules to eslint and refactor relevant code
1 parent 516bc67 commit 69f0147

File tree

3 files changed

+332
-297
lines changed

3 files changed

+332
-297
lines changed
 

‎.eslintrc.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@
9090
"@typescript-eslint/ban-ts-comment": "off",
9191
"dot-notation": "off",
9292
"no-shadow": "off",
93-
"@typescript-eslint/no-shadow": "error"
93+
"@typescript-eslint/no-shadow": "error",
94+
"react-hooks/rules-of-hooks": "error",
95+
"react-hooks/exhaustive-deps": "warn"
9496
}
9597
}

‎src/components/Tooltip/Tooltip.tsx

+268-248
Original file line numberDiff line numberDiff line change
@@ -83,71 +83,6 @@ const Tooltip = ({
8383
const [anchorElements, setAnchorElements] = useState<HTMLElement[]>([])
8484
const mounted = useRef(false)
8585

86-
const hasClickEvent =
87-
openOnClick || openEvents?.click || openEvents?.dblclick || openEvents?.mousedown
88-
const actualOpenEvents: AnchorOpenEvents = openEvents
89-
? { ...openEvents }
90-
: {
91-
mouseenter: true,
92-
focus: true,
93-
click: false,
94-
dblclick: false,
95-
mousedown: false,
96-
}
97-
if (!openEvents && openOnClick) {
98-
Object.assign(actualOpenEvents, {
99-
mouseenter: false,
100-
focus: false,
101-
click: true,
102-
})
103-
}
104-
const actualCloseEvents: AnchorCloseEvents = closeEvents
105-
? { ...closeEvents }
106-
: {
107-
mouseleave: true,
108-
blur: true,
109-
click: false,
110-
dblclick: false,
111-
mouseup: false,
112-
}
113-
if (!closeEvents && openOnClick) {
114-
Object.assign(actualCloseEvents, {
115-
mouseleave: false,
116-
blur: false,
117-
})
118-
}
119-
const actualGlobalCloseEvents: GlobalCloseEvents = globalCloseEvents
120-
? { ...globalCloseEvents }
121-
: {
122-
escape: false,
123-
scroll: false,
124-
resize: false,
125-
clickOutsideAnchor: hasClickEvent || false,
126-
}
127-
128-
if (imperativeModeOnly) {
129-
Object.assign(actualOpenEvents, {
130-
mouseenter: false,
131-
focus: false,
132-
click: false,
133-
dblclick: false,
134-
mousedown: false,
135-
})
136-
Object.assign(actualCloseEvents, {
137-
mouseleave: false,
138-
blur: false,
139-
click: false,
140-
dblclick: false,
141-
mouseup: false,
142-
})
143-
Object.assign(actualGlobalCloseEvents, {
144-
escape: false,
145-
scroll: false,
146-
resize: false,
147-
clickOutsideAnchor: false,
148-
})
149-
}
150-
15186
/**
15287
* useLayoutEffect runs before useEffect,
15388
* but should be used carefully because of caveats
@@ -160,27 +95,30 @@ const Tooltip = ({
16095
}
16196
}, [])
16297

163-
const handleShow = (value: boolean) => {
164-
if (!mounted.current) {
165-
return
166-
}
167-
if (value) {
168-
setRendered(true)
169-
}
170-
/**
171-
* wait for the component to render and calculate position
172-
* before actually showing
173-
*/
174-
setTimeout(() => {
98+
const handleShow = useCallback(
99+
(value: boolean) => {
175100
if (!mounted.current) {
176101
return
177102
}
178-
setIsOpen?.(value)
179-
if (isOpen === undefined) {
180-
setShow(value)
103+
if (value) {
104+
setRendered(true)
181105
}
182-
}, 10)
183-
}
106+
/**
107+
* wait for the component to render and calculate position
108+
* before actually showing
109+
*/
110+
setTimeout(() => {
111+
if (!mounted.current) {
112+
return
113+
}
114+
setIsOpen?.(value)
115+
if (isOpen === undefined) {
116+
setShow(value)
117+
}
118+
}, 10)
119+
},
120+
[isOpen, setIsOpen],
121+
)
184122

185123
/**
186124
* this replicates the effect from `handleShow()`
@@ -228,7 +166,7 @@ const Tooltip = ({
228166
// +25ms just to make sure `onTransitionEnd` (if it gets fired) has time to run
229167
}, transitionShowDelay + 25)
230168
}
231-
}, [show])
169+
}, [afterHide, afterShow, show])
232170

233171
const handleComputedPosition = (newComputedPosition: IComputedPosition) => {
234172
setComputedPosition((oldComputedPosition) =>
@@ -238,154 +176,72 @@ const Tooltip = ({
238176
)
239177
}
240178

241-
const handleShowTooltipDelayed = (delay = delayShow) => {
242-
if (tooltipShowDelayTimerRef.current) {
243-
clearTimeout(tooltipShowDelayTimerRef.current)
244-
}
245-
246-
if (rendered) {
247-
// if the tooltip is already rendered, ignore delay
248-
handleShow(true)
249-
return
250-
}
251-
252-
tooltipShowDelayTimerRef.current = setTimeout(() => {
253-
handleShow(true)
254-
}, delay)
255-
}
256-
257-
const handleHideTooltipDelayed = (delay = delayHide) => {
258-
if (tooltipHideDelayTimerRef.current) {
259-
clearTimeout(tooltipHideDelayTimerRef.current)
260-
}
179+
const handleShowTooltipDelayed = useCallback(
180+
(delay = delayShow) => {
181+
if (tooltipShowDelayTimerRef.current) {
182+
clearTimeout(tooltipShowDelayTimerRef.current)
183+
}
261184

262-
tooltipHideDelayTimerRef.current = setTimeout(() => {
263-
if (hoveringTooltip.current) {
185+
if (rendered) {
186+
// if the tooltip is already rendered, ignore delay
187+
handleShow(true)
264188
return
265189
}
266-
handleShow(false)
267-
}, delay)
268-
}
269-
270-
const handleShowTooltip = (event?: Event) => {
271-
if (!event) {
272-
return
273-
}
274-
const target = (event.currentTarget ?? event.target) as HTMLElement | null
275-
if (!target?.isConnected) {
276-
/**
277-
* this happens when the target is removed from the DOM
278-
* at the same time the tooltip gets triggered
279-
*/
280-
setActiveAnchor(null)
281-
return
282-
}
283-
if (delayShow) {
284-
handleShowTooltipDelayed()
285-
} else {
286-
handleShow(true)
287-
}
288-
setActiveAnchor(target)
289190

290-
if (tooltipHideDelayTimerRef.current) {
291-
clearTimeout(tooltipHideDelayTimerRef.current)
292-
}
293-
}
294-
295-
const handleHideTooltip = () => {
296-
if (clickable) {
297-
// allow time for the mouse to reach the tooltip, in case there's a gap
298-
handleHideTooltipDelayed(delayHide || 100)
299-
} else if (delayHide) {
300-
handleHideTooltipDelayed()
301-
} else {
302-
handleShow(false)
303-
}
191+
tooltipShowDelayTimerRef.current = setTimeout(() => {
192+
handleShow(true)
193+
}, delay)
194+
},
195+
[delayShow, handleShow, rendered],
196+
)
304197

305-
if (tooltipShowDelayTimerRef.current) {
306-
clearTimeout(tooltipShowDelayTimerRef.current)
307-
}
308-
}
198+
const handleHideTooltipDelayed = useCallback(
199+
(delay = delayHide) => {
200+
if (tooltipHideDelayTimerRef.current) {
201+
clearTimeout(tooltipHideDelayTimerRef.current)
202+
}
309203

310-
const handleTooltipPosition = ({ x, y }: IPosition) => {
311-
const virtualElement = {
312-
getBoundingClientRect() {
313-
return {
314-
x,
315-
y,
316-
width: 0,
317-
height: 0,
318-
top: y,
319-
left: x,
320-
right: x,
321-
bottom: y,
204+
tooltipHideDelayTimerRef.current = setTimeout(() => {
205+
if (hoveringTooltip.current) {
206+
return
322207
}
323-
},
324-
} as Element
325-
computeTooltipPosition({
326-
place: imperativeOptions?.place ?? place,
327-
offset,
328-
elementReference: virtualElement,
329-
tooltipReference: tooltipRef.current,
330-
tooltipArrowReference: tooltipArrowRef.current,
331-
strategy: positionStrategy,
332-
middlewares,
333-
border,
334-
}).then((computedStylesData) => {
335-
handleComputedPosition(computedStylesData)
336-
})
337-
}
338-
339-
const handlePointerMove = (event?: Event) => {
340-
if (!event) {
341-
return
342-
}
343-
const mouseEvent = event as MouseEvent
344-
const mousePosition = {
345-
x: mouseEvent.clientX,
346-
y: mouseEvent.clientY,
347-
}
348-
handleTooltipPosition(mousePosition)
349-
lastFloatPosition.current = mousePosition
350-
}
351-
352-
const handleClickOutsideAnchors = (event: MouseEvent) => {
353-
if (!show) {
354-
return
355-
}
356-
const target = event.target as HTMLElement
357-
if (!target.isConnected) {
358-
return
359-
}
360-
if (tooltipRef.current?.contains(target)) {
361-
return
362-
}
363-
if (anchorElements.some((anchor) => anchor?.contains(target))) {
364-
return
365-
}
366-
handleShow(false)
367-
if (tooltipShowDelayTimerRef.current) {
368-
clearTimeout(tooltipShowDelayTimerRef.current)
369-
}
370-
}
208+
handleShow(false)
209+
}, delay)
210+
},
211+
[delayHide, handleShow],
212+
)
371213

372-
// debounce handler to prevent call twice when
373-
// mouse enter and focus events being triggered toggether
374-
const internalDebouncedHandleShowTooltip = debounce(handleShowTooltip, 50, true)
375-
const internalDebouncedHandleHideTooltip = debounce(handleHideTooltip, 50, true)
376-
// If either of the functions is called while the other is still debounced,
377-
// reset the timeout. Otherwise if there is a sub-50ms (leave A, enter B, leave B)
378-
// sequence of events, the tooltip will stay open because the hide debounce
379-
// from leave A prevented the leave B event from calling it, leaving the
380-
// tooltip visible.
381-
const debouncedHandleShowTooltip = (e?: Event) => {
382-
internalDebouncedHandleHideTooltip.cancel()
383-
internalDebouncedHandleShowTooltip(e)
384-
}
385-
const debouncedHandleHideTooltip = () => {
386-
internalDebouncedHandleShowTooltip.cancel()
387-
internalDebouncedHandleHideTooltip()
388-
}
214+
const handleTooltipPosition = useCallback(
215+
({ x, y }: IPosition) => {
216+
const virtualElement = {
217+
getBoundingClientRect() {
218+
return {
219+
x,
220+
y,
221+
width: 0,
222+
height: 0,
223+
top: y,
224+
left: x,
225+
right: x,
226+
bottom: y,
227+
}
228+
},
229+
} as Element
230+
computeTooltipPosition({
231+
place: imperativeOptions?.place ?? place,
232+
offset,
233+
elementReference: virtualElement,
234+
tooltipReference: tooltipRef.current,
235+
tooltipArrowReference: tooltipArrowRef.current,
236+
strategy: positionStrategy,
237+
middlewares,
238+
border,
239+
}).then((computedStylesData) => {
240+
handleComputedPosition(computedStylesData)
241+
})
242+
},
243+
[imperativeOptions?.place, place, offset, positionStrategy, middlewares, border],
244+
)
389245

390246
const updateTooltipPosition = useCallback(() => {
391247
const actualPosition = imperativeOptions?.position ?? position
@@ -431,17 +287,17 @@ const Tooltip = ({
431287
handleComputedPosition(computedStylesData)
432288
})
433289
}, [
434-
show,
290+
imperativeOptions?.position,
291+
imperativeOptions?.place,
292+
position,
293+
float,
435294
activeAnchor,
436-
content,
437-
externalStyles,
438295
place,
439-
imperativeOptions?.place,
440296
offset,
441297
positionStrategy,
442-
position,
443-
imperativeOptions?.position,
444-
float,
298+
middlewares,
299+
border,
300+
handleTooltipPosition,
445301
])
446302

447303
useEffect(() => {
@@ -451,15 +307,172 @@ const Tooltip = ({
451307
* - `handleMouseEvents()`
452308
* - `handleGlobalCloseEvents()`
453309
* - `handleAnchorEvents()`
454-
* - ?
310+
* - ...
455311
*/
456312

313+
const handlePointerMove = (event?: Event) => {
314+
if (!event) {
315+
return
316+
}
317+
const mouseEvent = event as MouseEvent
318+
const mousePosition = {
319+
x: mouseEvent.clientX,
320+
y: mouseEvent.clientY,
321+
}
322+
handleTooltipPosition(mousePosition)
323+
lastFloatPosition.current = mousePosition
324+
}
325+
326+
const handleClickOutsideAnchors = (event: MouseEvent) => {
327+
if (!show) {
328+
return
329+
}
330+
const target = event.target as HTMLElement
331+
if (!target.isConnected) {
332+
return
333+
}
334+
if (tooltipRef.current?.contains(target)) {
335+
return
336+
}
337+
if (anchorElements.some((anchor) => anchor?.contains(target))) {
338+
return
339+
}
340+
handleShow(false)
341+
if (tooltipShowDelayTimerRef.current) {
342+
clearTimeout(tooltipShowDelayTimerRef.current)
343+
}
344+
}
345+
346+
const handleShowTooltip = (event?: Event) => {
347+
if (!event) {
348+
return
349+
}
350+
const target = (event.currentTarget ?? event.target) as HTMLElement | null
351+
if (!target?.isConnected) {
352+
/**
353+
* this happens when the target is removed from the DOM
354+
* at the same time the tooltip gets triggered
355+
*/
356+
setActiveAnchor(null)
357+
return
358+
}
359+
if (delayShow) {
360+
handleShowTooltipDelayed()
361+
} else {
362+
handleShow(true)
363+
}
364+
setActiveAnchor(target)
365+
366+
if (tooltipHideDelayTimerRef.current) {
367+
clearTimeout(tooltipHideDelayTimerRef.current)
368+
}
369+
}
370+
371+
const handleHideTooltip = () => {
372+
if (clickable) {
373+
// allow time for the mouse to reach the tooltip, in case there's a gap
374+
handleHideTooltipDelayed(delayHide || 100)
375+
} else if (delayHide) {
376+
handleHideTooltipDelayed()
377+
} else {
378+
handleShow(false)
379+
}
380+
381+
if (tooltipShowDelayTimerRef.current) {
382+
clearTimeout(tooltipShowDelayTimerRef.current)
383+
}
384+
}
385+
386+
// debounce handler to prevent call twice when
387+
// mouse enter and focus events being triggered toggether
388+
const internalDebouncedHandleShowTooltip = debounce(handleShowTooltip, 50, true)
389+
const internalDebouncedHandleHideTooltip = debounce(handleHideTooltip, 50, true)
390+
// If either of the functions is called while the other is still debounced,
391+
// reset the timeout. Otherwise if there is a sub-50ms (leave A, enter B, leave B)
392+
// sequence of events, the tooltip will stay open because the hide debounce
393+
// from leave A prevented the leave B event from calling it, leaving the
394+
// tooltip visible.
395+
const debouncedHandleShowTooltip = (e?: Event) => {
396+
internalDebouncedHandleHideTooltip.cancel()
397+
internalDebouncedHandleShowTooltip(e)
398+
}
399+
const debouncedHandleHideTooltip = () => {
400+
internalDebouncedHandleShowTooltip.cancel()
401+
internalDebouncedHandleHideTooltip()
402+
}
403+
457404
const handleScrollResize = () => {
458405
handleShow(false)
459406
}
460407

461-
const anchorScrollParent = getScrollParent(activeAnchor)
408+
const hasClickEvent =
409+
openOnClick || openEvents?.click || openEvents?.dblclick || openEvents?.mousedown
410+
const actualOpenEvents: AnchorOpenEvents = openEvents
411+
? { ...openEvents }
412+
: {
413+
mouseenter: true,
414+
focus: true,
415+
click: false,
416+
dblclick: false,
417+
mousedown: false,
418+
}
419+
if (!openEvents && openOnClick) {
420+
Object.assign(actualOpenEvents, {
421+
mouseenter: false,
422+
focus: false,
423+
click: true,
424+
})
425+
}
426+
const actualCloseEvents: AnchorCloseEvents = closeEvents
427+
? { ...closeEvents }
428+
: {
429+
mouseleave: true,
430+
blur: true,
431+
click: false,
432+
dblclick: false,
433+
mouseup: false,
434+
}
435+
if (!closeEvents && openOnClick) {
436+
Object.assign(actualCloseEvents, {
437+
mouseleave: false,
438+
blur: false,
439+
})
440+
}
441+
const actualGlobalCloseEvents: GlobalCloseEvents = globalCloseEvents
442+
? { ...globalCloseEvents }
443+
: {
444+
escape: false,
445+
scroll: false,
446+
resize: false,
447+
clickOutsideAnchor: hasClickEvent || false,
448+
}
449+
450+
if (imperativeModeOnly) {
451+
Object.assign(actualOpenEvents, {
452+
mouseenter: false,
453+
focus: false,
454+
click: false,
455+
dblclick: false,
456+
mousedown: false,
457+
})
458+
Object.assign(actualCloseEvents, {
459+
mouseleave: false,
460+
blur: false,
461+
click: false,
462+
dblclick: false,
463+
mouseup: false,
464+
})
465+
Object.assign(actualGlobalCloseEvents, {
466+
escape: false,
467+
scroll: false,
468+
resize: false,
469+
clickOutsideAnchor: false,
470+
})
471+
}
472+
473+
const tooltipElement = tooltipRef.current
462474
const tooltipScrollParent = getScrollParent(tooltipRef.current)
475+
const anchorScrollParent = getScrollParent(activeAnchor)
463476

464477
if (actualGlobalCloseEvents.scroll) {
465478
window.addEventListener('scroll', handleScrollResize)
@@ -567,8 +580,8 @@ const Tooltip = ({
567580
if (clickable && !hasClickEvent) {
568581
// used to keep the tooltip open when hovering content.
569582
// not needed if using click events.
570-
tooltipRef.current?.addEventListener('mouseenter', handleMouseEnterTooltip)
571-
tooltipRef.current?.addEventListener('mouseleave', handleMouseLeaveTooltip)
583+
tooltipElement?.addEventListener('mouseenter', handleMouseEnterTooltip)
584+
tooltipElement?.addEventListener('mouseleave', handleMouseLeaveTooltip)
572585
}
573586

574587
enabledEvents.forEach(({ event, listener }) => {
@@ -595,8 +608,8 @@ const Tooltip = ({
595608
window.removeEventListener('keydown', handleEsc)
596609
}
597610
if (clickable && !hasClickEvent) {
598-
tooltipRef.current?.removeEventListener('mouseenter', handleMouseEnterTooltip)
599-
tooltipRef.current?.removeEventListener('mouseleave', handleMouseLeaveTooltip)
611+
tooltipElement?.removeEventListener('mouseenter', handleMouseEnterTooltip)
612+
tooltipElement?.removeEventListener('mouseleave', handleMouseLeaveTooltip)
600613
}
601614
enabledEvents.forEach(({ event, listener }) => {
602615
anchorElements.forEach((anchor) => {
@@ -610,16 +623,23 @@ const Tooltip = ({
610623
*/
611624
}, [
612625
activeAnchor,
613-
updateTooltipPosition,
614-
rendered,
615626
anchorElements,
616-
// the effect uses the `actual*Events` objects, but this should work
617-
openEvents,
627+
clickable,
618628
closeEvents,
619-
globalCloseEvents,
620-
hasClickEvent,
621-
delayShow,
622629
delayHide,
630+
delayShow,
631+
float,
632+
globalCloseEvents,
633+
handleHideTooltipDelayed,
634+
handleShow,
635+
handleShowTooltipDelayed,
636+
handleTooltipPosition,
637+
imperativeModeOnly,
638+
openEvents,
639+
openOnClick,
640+
setActiveAnchor,
641+
show,
642+
updateTooltipPosition,
623643
])
624644

625645
useEffect(() => {
@@ -749,7 +769,7 @@ const Tooltip = ({
749769
return () => {
750770
documentObserver.disconnect()
751771
}
752-
}, [id, anchorSelect, imperativeOptions?.anchorSelect, activeAnchor])
772+
}, [id, anchorSelect, imperativeOptions?.anchorSelect, activeAnchor, handleShow, setActiveAnchor])
753773

754774
useEffect(() => {
755775
updateTooltipPosition()
@@ -766,7 +786,7 @@ const Tooltip = ({
766786
return () => {
767787
contentObserver.disconnect()
768788
}
769-
}, [content, contentWrapperRef?.current])
789+
}, [content, contentWrapperRef, updateTooltipPosition])
770790

771791
useEffect(() => {
772792
if (!activeAnchor || !anchorElements.includes(activeAnchor)) {
@@ -777,7 +797,7 @@ const Tooltip = ({
777797
*/
778798
setActiveAnchor(anchorElements[0] ?? null)
779799
}
780-
}, [anchorElements, activeAnchor])
800+
}, [anchorElements, activeAnchor, setActiveAnchor])
781801

782802
useEffect(() => {
783803
if (defaultIsOpen) {
@@ -791,7 +811,7 @@ const Tooltip = ({
791811
clearTimeout(tooltipHideDelayTimerRef.current)
792812
}
793813
}
794-
}, [])
814+
}, [defaultIsOpen, handleShow])
795815

796816
useEffect(() => {
797817
let selector = imperativeOptions?.anchorSelect ?? anchorSelect
@@ -815,7 +835,7 @@ const Tooltip = ({
815835
clearTimeout(tooltipShowDelayTimerRef.current)
816836
handleShowTooltipDelayed(delayShow)
817837
}
818-
}, [delayShow])
838+
}, [delayShow, handleShowTooltipDelayed])
819839

820840
const actualContent = imperativeOptions?.content ?? content
821841
const canShow = show && Object.keys(computedPosition.tooltipStyles).length > 0

‎src/components/TooltipController/TooltipController.tsx

+61-48
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useEffect, useRef, useState } from 'react'
1+
import React, { useCallback, useEffect, useRef, useState } from 'react'
22
import { Tooltip } from 'components/Tooltip'
33
import type {
44
PositionStrategy,
@@ -81,51 +81,63 @@ const TooltipController = React.forwardRef<TooltipRefProps, ITooltipController>(
8181
return dataAttributes
8282
}
8383

84-
const applyAllDataAttributesFromAnchorElement = (
85-
dataAttributes: Record<string, string | null>,
86-
) => {
87-
const handleDataAttributes: Record<DataAttribute, (value: string | null) => void> = {
88-
place: (value) => {
89-
setTooltipPlace((value as PlacesType) ?? place)
90-
},
91-
content: (value) => {
92-
setTooltipContent(value ?? content)
93-
},
94-
variant: (value) => {
95-
setTooltipVariant((value as VariantType) ?? variant)
96-
},
97-
offset: (value) => {
98-
setTooltipOffset(value === null ? offset : Number(value))
99-
},
100-
wrapper: (value) => {
101-
setTooltipWrapper((value as WrapperType) ?? wrapper)
102-
},
103-
'position-strategy': (value) => {
104-
setTooltipPositionStrategy((value as PositionStrategy) ?? positionStrategy)
105-
},
106-
'delay-show': (value) => {
107-
setTooltipDelayShow(value === null ? delayShow : Number(value))
108-
},
109-
'delay-hide': (value) => {
110-
setTooltipDelayHide(value === null ? delayHide : Number(value))
111-
},
112-
float: (value) => {
113-
setTooltipFloat(value === null ? float : value === 'true')
114-
},
115-
hidden: (value) => {
116-
setTooltipHidden(value === null ? hidden : value === 'true')
117-
},
118-
'class-name': (value) => {
119-
setTooltipClassName(value)
120-
},
121-
}
122-
// reset unset data attributes to default values
123-
// without this, data attributes from the last active anchor will still be used
124-
Object.values(handleDataAttributes).forEach((handler) => handler(null))
125-
Object.entries(dataAttributes).forEach(([key, value]) => {
126-
handleDataAttributes[key as DataAttribute]?.(value)
127-
})
128-
}
84+
const applyAllDataAttributesFromAnchorElement = useCallback(
85+
(dataAttributes: Record<string, string | null>) => {
86+
const handleDataAttributes: Record<DataAttribute, (value: string | null) => void> = {
87+
place: (value) => {
88+
setTooltipPlace((value as PlacesType) ?? place)
89+
},
90+
content: (value) => {
91+
setTooltipContent(value ?? content)
92+
},
93+
variant: (value) => {
94+
setTooltipVariant((value as VariantType) ?? variant)
95+
},
96+
offset: (value) => {
97+
setTooltipOffset(value === null ? offset : Number(value))
98+
},
99+
wrapper: (value) => {
100+
setTooltipWrapper((value as WrapperType) ?? wrapper)
101+
},
102+
'position-strategy': (value) => {
103+
setTooltipPositionStrategy((value as PositionStrategy) ?? positionStrategy)
104+
},
105+
'delay-show': (value) => {
106+
setTooltipDelayShow(value === null ? delayShow : Number(value))
107+
},
108+
'delay-hide': (value) => {
109+
setTooltipDelayHide(value === null ? delayHide : Number(value))
110+
},
111+
float: (value) => {
112+
setTooltipFloat(value === null ? float : value === 'true')
113+
},
114+
hidden: (value) => {
115+
setTooltipHidden(value === null ? hidden : value === 'true')
116+
},
117+
'class-name': (value) => {
118+
setTooltipClassName(value)
119+
},
120+
}
121+
// reset unset data attributes to default values
122+
// without this, data attributes from the last active anchor will still be used
123+
Object.values(handleDataAttributes).forEach((handler) => handler(null))
124+
Object.entries(dataAttributes).forEach(([key, value]) => {
125+
handleDataAttributes[key as DataAttribute]?.(value)
126+
})
127+
},
128+
[
129+
content,
130+
delayHide,
131+
delayShow,
132+
float,
133+
hidden,
134+
offset,
135+
place,
136+
positionStrategy,
137+
variant,
138+
wrapper,
139+
],
140+
)
129141

130142
useEffect(() => {
131143
setTooltipContent(content)
@@ -186,6 +198,7 @@ const TooltipController = React.forwardRef<TooltipRefProps, ITooltipController>(
186198
}),
187199
)
188200
}
201+
// eslint-disable-next-line react-hooks/exhaustive-deps
189202
}, [])
190203

191204
useEffect(() => {
@@ -222,7 +235,7 @@ const TooltipController = React.forwardRef<TooltipRefProps, ITooltipController>(
222235
// Remove the observer when the tooltip is destroyed
223236
observer.disconnect()
224237
}
225-
}, [activeAnchor, anchorSelect])
238+
}, [activeAnchor, anchorSelect, applyAllDataAttributesFromAnchorElement])
226239

227240
useEffect(() => {
228241
/* c8 ignore start */
@@ -246,7 +259,7 @@ const TooltipController = React.forwardRef<TooltipRefProps, ITooltipController>(
246259
// eslint-disable-next-line no-console
247260
console.warn(`[react-tooltip] "${opacity}" is not a valid \`opacity\`.`)
248261
}
249-
}, [])
262+
}, [border, opacity, style?.border, style?.opacity])
250263

251264
/**
252265
* content priority: children < render or content < html

0 commit comments

Comments
 (0)
Please sign in to comment.