Skip to content

Commit

Permalink
fix(Tooltip): prevent focus hijacking on tooltip dismissal (#7319)
Browse files Browse the repository at this point in the history
* fix(Tooltip): prevent tooltip ID overwriting itself

* fix(Tooltip): prevent focus on trigger element on non-Esc key dismissal

* fix(Tooltip): check tooltip identity before setting dismissed status

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
  • Loading branch information
emyarod and kodiakhq[bot] committed Dec 9, 2020
1 parent 73f18a1 commit b92e816
Showing 1 changed file with 23 additions and 4 deletions.
27 changes: 23 additions & 4 deletions packages/react/src/components/Tooltip/Tooltip.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ import FloatingMenu, {
import ClickListener from '../../internal/ClickListener';
import mergeRefs from '../../tools/mergeRefs';
import { keys, matches as keyDownMatch } from '../../internal/keyboard';
import {
selectorFocusable,
selectorTabbable,
} from '../../internal/keyboard/navigation';
import isRequiredOneOf from '../../prop-types/isRequiredOneOf';
import requiredIfValueExists from '../../prop-types/requiredIfValueExists';
import { useControlledStateWithValue } from '../../internal/FeatureFlags';
Expand Down Expand Up @@ -244,6 +248,7 @@ class Tooltip extends Component {
* @private
*/
_tooltipId =
this.props.id ||
this.props.tooltipId ||
`__carbon-tooltip_${Math.random().toString(36).substr(2)}`;

Expand Down Expand Up @@ -295,7 +300,19 @@ class Tooltip extends Component {
}
if (!open && tooltipBody && tooltipBody.id === this._tooltipId) {
this._tooltipDismissed = true;
this._triggerRef?.current.focus();
const primaryFocusNode = tooltipBody.querySelector(
this.props.selectorPrimaryFocus || null
);
const tabbableNode = tooltipBody.querySelector(selectorTabbable);
const focusableNode = tooltipBody.querySelector(selectorFocusable);
const focusTarget =
primaryFocusNode || // User defined focusable node
tabbableNode || // First sequentially focusable node
focusableNode || // First programmatic focusable node
tooltipBody;
if (focusTarget !== tooltipBody) {
this._triggerRef?.current.focus();
}
}
});
};
Expand All @@ -306,7 +323,10 @@ class Tooltip extends Component {
* @param {Element} [evt] For handing `mouseout` event, indicates where the mouse pointer is gone.
*/
_handleFocus = (state, evt) => {
const { relatedTarget } = evt;
const { currentTarget, relatedTarget } = evt;
if (currentTarget !== relatedTarget) {
this._tooltipDismissed = false;
}
if (state === 'over') {
if (!this._tooltipDismissed) {
this._handleUserInputOpenClose(evt, { open: true });
Expand Down Expand Up @@ -506,9 +526,9 @@ class Tooltip extends Component {
this._tooltipEl = node;
}}>
<div
id={this._tooltipId}
className={tooltipClasses}
{...other}
id={this._tooltipId}
data-floating-menu-direction={direction}
onMouseOver={this.handleMouse}
onMouseOut={this.handleMouse}
Expand All @@ -519,7 +539,6 @@ class Tooltip extends Component {
<span className={`${prefix}--tooltip__caret`} />
<div
className={`${prefix}--tooltip__content`}
tabIndex="-1"
role="dialog"
aria-describedby={tooltipBodyId}
aria-labelledby={triggerId}>
Expand Down

0 comments on commit b92e816

Please sign in to comment.