From 8878931115b67e65a01fa06b95de3ac65feffeb7 Mon Sep 17 00:00:00 2001 From: Caleb Hardin Date: Wed, 17 Sep 2025 14:25:51 -0400 Subject: [PATCH 1/4] Add aria-describedby automatically --- src/App.tsx | 12 +++++++- src/components/Tooltip/Tooltip.tsx | 30 +++++++++++++++++++ src/components/Tooltip/TooltipTypes.d.ts | 1 + .../TooltipController/TooltipController.tsx | 9 +++++- .../tooltip-attributes.spec.js.snap | 3 ++ .../__snapshots__/tooltip-props.spec.js.snap | 8 +++++ 6 files changed, 61 insertions(+), 2 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 18a48af9..fab130f0 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -47,8 +47,15 @@ function App() { > My button - + Tooltip content { + if (!id) return + + function getAriaDescribedBy(element: HTMLElement | null) { + return element?.getAttribute('aria-describedby')?.split(' ') || [] + } + + function removeAriaDescribedBy(element: HTMLElement | null) { + const newDescribedBy = getAriaDescribedBy(element).filter((s) => s !== id) + if (newDescribedBy.length) { + element?.setAttribute('aria-describedby', newDescribedBy.join(' ')) + } else { + element?.removeAttribute('aria-describedby') + } + } + + if (show) { + removeAriaDescribedBy(previousActiveAnchor) + const currentDescribedBy = getAriaDescribedBy(activeAnchor) + const describedBy = [...new Set([...currentDescribedBy, id])].filter(Boolean).join(' ') + activeAnchor?.setAttribute('aria-describedby', describedBy) + } else { + removeAriaDescribedBy(activeAnchor) + } + }, [activeAnchor, show]) + /** * this replicates the effect from `handleShow()` * when `isOpen` is changed from outside diff --git a/src/components/Tooltip/TooltipTypes.d.ts b/src/components/Tooltip/TooltipTypes.d.ts index ebb75b90..13904d18 100644 --- a/src/components/Tooltip/TooltipTypes.d.ts +++ b/src/components/Tooltip/TooltipTypes.d.ts @@ -153,6 +153,7 @@ export interface ITooltip { afterShow?: () => void afterHide?: () => void disableTooltip?: (anchorRef: HTMLElement | null) => boolean + previousActiveAnchor: HTMLElement | null activeAnchor: HTMLElement | null setActiveAnchor: (anchor: HTMLElement | null) => void border?: CSSProperties['border'] diff --git a/src/components/TooltipController/TooltipController.tsx b/src/components/TooltipController/TooltipController.tsx index fd70a431..2857c296 100644 --- a/src/components/TooltipController/TooltipController.tsx +++ b/src/components/TooltipController/TooltipController.tsx @@ -81,6 +81,7 @@ const TooltipController = React.forwardRef( const [tooltipPositionStrategy, setTooltipPositionStrategy] = useState(positionStrategy) const [tooltipClassName, setTooltipClassName] = useState(null) const [activeAnchor, setActiveAnchor] = useState(null) + const previousActiveAnchorRef = useRef(null) const styleInjectionRef = useRef(disableStyleInjection) /** * @todo Remove this in a future version (provider/wrapper method is deprecated) @@ -375,7 +376,13 @@ const TooltipController = React.forwardRef( afterHide, disableTooltip, activeAnchor, - setActiveAnchor: (anchor: HTMLElement | null) => setActiveAnchor(anchor), + previousActiveAnchor: previousActiveAnchorRef.current, + setActiveAnchor: (anchor: HTMLElement | null) => { + if (!anchor?.isSameNode(activeAnchor)) { + previousActiveAnchorRef.current = activeAnchor + } + setActiveAnchor(anchor) + }, role, } diff --git a/src/test/__snapshots__/tooltip-attributes.spec.js.snap b/src/test/__snapshots__/tooltip-attributes.spec.js.snap index c4b2b54a..007f89b4 100644 --- a/src/test/__snapshots__/tooltip-attributes.spec.js.snap +++ b/src/test/__snapshots__/tooltip-attributes.spec.js.snap @@ -3,6 +3,7 @@ exports[`tooltip attributes basic tooltip 1`] = `
@@ -26,6 +27,7 @@ exports[`tooltip attributes basic tooltip 1`] = ` exports[`tooltip attributes tooltip with class name 1`] = `
Lorem Ipsum @@ -25,6 +26,7 @@ exports[`tooltip props basic tooltip 1`] = ` exports[`tooltip props clickable tooltip 1`] = `
Lorem Ipsum @@ -49,6 +51,7 @@ exports[`tooltip props clickable tooltip 1`] = ` exports[`tooltip props tooltip with custom position 1`] = `
Lorem Ipsum @@ -81,6 +84,7 @@ exports[`tooltip props tooltip with delay hide 1`] = ` exports[`tooltip props tooltip with delay show 1`] = `
Lorem Ipsum @@ -103,6 +107,7 @@ exports[`tooltip props tooltip with delay show 1`] = ` exports[`tooltip props tooltip with disableTooltip return false 1`] = `
Lorem Ipsum @@ -125,6 +130,7 @@ exports[`tooltip props tooltip with disableTooltip return false 1`] = ` exports[`tooltip props tooltip with float 1`] = `
Lorem Ipsum @@ -147,6 +153,7 @@ exports[`tooltip props tooltip with float 1`] = ` exports[`tooltip props tooltip with html 1`] = `
Lorem Ipsum @@ -174,6 +181,7 @@ exports[`tooltip props tooltip with html 1`] = ` exports[`tooltip props tooltip with place 1`] = `
Lorem Ipsum From 37cab518cede70c5d64e856489334224874db926 Mon Sep 17 00:00:00 2001 From: Caleb Hardin Date: Wed, 17 Sep 2025 14:56:28 -0400 Subject: [PATCH 2/4] Minor adjustments --- src/components/Tooltip/Tooltip.tsx | 2 +- src/components/TooltipController/TooltipController.tsx | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/components/Tooltip/Tooltip.tsx b/src/components/Tooltip/Tooltip.tsx index bec3db20..185c52c5 100644 --- a/src/components/Tooltip/Tooltip.tsx +++ b/src/components/Tooltip/Tooltip.tsx @@ -233,7 +233,7 @@ const Tooltip = ({ } else { removeAriaDescribedBy(activeAnchor) } - }, [activeAnchor, show]) + }, [activeAnchor, show, id, previousActiveAnchor]) /** * this replicates the effect from `handleShow()` diff --git a/src/components/TooltipController/TooltipController.tsx b/src/components/TooltipController/TooltipController.tsx index 2857c296..0276a9d8 100644 --- a/src/components/TooltipController/TooltipController.tsx +++ b/src/components/TooltipController/TooltipController.tsx @@ -378,10 +378,12 @@ const TooltipController = React.forwardRef( activeAnchor, previousActiveAnchor: previousActiveAnchorRef.current, setActiveAnchor: (anchor: HTMLElement | null) => { - if (!anchor?.isSameNode(activeAnchor)) { - previousActiveAnchorRef.current = activeAnchor - } - setActiveAnchor(anchor) + setActiveAnchor((prev) => { + if (!anchor?.isSameNode(prev)) { + previousActiveAnchorRef.current = prev + } + return anchor + }) }, role, } From c208987b1a90bd0902f01643523d7e12685d2682 Mon Sep 17 00:00:00 2001 From: Caleb Hardin Date: Wed, 17 Sep 2025 15:47:22 -0400 Subject: [PATCH 3/4] Add cleanup function --- src/components/Tooltip/Tooltip.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/components/Tooltip/Tooltip.tsx b/src/components/Tooltip/Tooltip.tsx index 185c52c5..95fcb974 100644 --- a/src/components/Tooltip/Tooltip.tsx +++ b/src/components/Tooltip/Tooltip.tsx @@ -233,6 +233,13 @@ const Tooltip = ({ } else { removeAriaDescribedBy(activeAnchor) } + + // eslint-disable-next-line consistent-return + return () => { + // cleanup aria-describedby when the tooltip is closed + removeAriaDescribedBy(activeAnchor) + removeAriaDescribedBy(previousActiveAnchor) + } }, [activeAnchor, show, id, previousActiveAnchor]) /** From 970f7ca19f670aa7dc114cc93718178cc39c6c90 Mon Sep 17 00:00:00 2001 From: gabrieljablonski Date: Fri, 10 Oct 2025 20:13:17 -0300 Subject: [PATCH 4/4] test: update snapshots --- src/test/__snapshots__/tooltip-attributes.spec.js.snap | 6 +++--- src/test/__snapshots__/tooltip-props.spec.js.snap | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/test/__snapshots__/tooltip-attributes.spec.js.snap b/src/test/__snapshots__/tooltip-attributes.spec.js.snap index 007f89b4..57f73998 100644 --- a/src/test/__snapshots__/tooltip-attributes.spec.js.snap +++ b/src/test/__snapshots__/tooltip-attributes.spec.js.snap @@ -60,15 +60,15 @@ exports[`tooltip attributes tooltip with place 1`] = ` Lorem Ipsum diff --git a/src/test/__snapshots__/tooltip-props.spec.js.snap b/src/test/__snapshots__/tooltip-props.spec.js.snap index fb9a9da4..a0eb9472 100644 --- a/src/test/__snapshots__/tooltip-props.spec.js.snap +++ b/src/test/__snapshots__/tooltip-props.spec.js.snap @@ -187,15 +187,15 @@ exports[`tooltip props tooltip with place 1`] = ` Lorem Ipsum