Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(tooltip): clean up focus handling #273

Merged
merged 1 commit into from
Aug 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 23 additions & 13 deletions src/components/entries/Tooltip.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,23 +38,30 @@
} = props;

const [ visible, setShow ] = useState(false);
const [ focusedViaKeyboard, setFocusedViaKeyboard ] = useState(false);

let timeout = null;

const wrapperRef = useRef(null);
const tooltipRef = useRef(null);

const showTooltip = async (event) => {
const show = () => !visible && setShow(true);

if (event instanceof MouseEvent) {
timeout = setTimeout(show, 200);
} else {
show();
const show = () => setShow(true);

if (!visible && !timeout) {
if (event instanceof MouseEvent) {
timeout = setTimeout(show, 200);
} else {
show();
setFocusedViaKeyboard(true);
}
}
};

const hideTooltip = () => setShow(false);
const hideTooltip = () => {
setShow(false);
setFocusedViaKeyboard(false);
};

const hideTooltipViaEscape = (e) => {
e.code === 'Escape' && hideTooltip();
Expand All @@ -81,16 +88,17 @@
const isFocused = document.activeElement === wrapperRef.current
|| document.activeElement.closest('.bio-properties-panel-tooltip');

if (visible && !isTooltipHovered({ x: e.x, y: e.y }) && !isFocused) {
if (visible && !isTooltipHovered({ x: e.x, y: e.y }) && !(isFocused && focusedViaKeyboard)) {
hideTooltip();
}
};

const hideFocusedTooltip = (e) => {
const { relatedTarget } = e;
const isTooltipChild = (el) => el && !!el.closest('.bio-properties-panel-tooltip');
const isTooltipChild = (el) => !!el.closest('.bio-properties-panel-tooltip');


if (visible && !isHovered(wrapperRef.current) && !isTooltipChild(relatedTarget)) {
if (visible && !isHovered(wrapperRef.current) && relatedTarget && !isTooltipChild(relatedTarget)) {
hideTooltip();
}
};
Expand All @@ -104,7 +112,7 @@
document.removeEventListener('mousemove', hideHoveredTooltip);
document.removeEventListener('focusout', hideFocusedTooltip);
};
}, [ wrapperRef.current, visible ]);
}, [ wrapperRef.current, visible, focusedViaKeyboard ]);

const renderTooltip = () => {
return (
Expand All @@ -128,10 +136,12 @@
<div class="bio-properties-panel-tooltip-wrapper" tabIndex="0"
ref={ wrapperRef }
onMouseEnter={ showTooltip }
onMouseLeave={ ()=> clearTimeout(timeout) }
onMouseLeave={ ()=> {
clearTimeout(timeout);
timeout = null;

Check warning on line 141 in src/components/entries/Tooltip.js

View check run for this annotation

Codecov / codecov/patch

src/components/entries/Tooltip.js#L140-L141

Added lines #L140 - L141 were not covered by tests
} }
onFocus={ showTooltip }
onKeyDown={ hideTooltipViaEscape }
onMouseDown={ (e)=> {e.preventDefault();} }
>
{props.children}
{visible ?
Expand Down
143 changes: 133 additions & 10 deletions test/spec/components/Tooltip.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ describe('<Tooltip>', function() {
clock.restore();
});

function openTooltip(element) {
function openTooltip(element, viaFocus = false) {
return act(() => {
fireEvent.mouseEnter(element);
viaFocus ? element.focus() : fireEvent.mouseEnter(element);
clock.tick(200);
});
}
Expand Down Expand Up @@ -127,14 +127,6 @@ describe('<Tooltip>', function() {
it('should render inside parent if defined', async function() {

// given
const TooltipWithParent = () => {
const ref = useRef();

return <div id="parent" ref={ ref }>
<TooltipComponent parent={ ref } />
</div>;
};

render(<TooltipWithParent />,{ container });

const element = domQuery('.bio-properties-panel-tooltip-wrapper', container);
Expand Down Expand Up @@ -279,6 +271,128 @@ describe('<Tooltip>', function() {

});


describe('focus', function() {

describe('trigger element', function() {

it('should not persist tooltip when opened with mouse', async function() {

// given
createTooltip({ container });
const wrapper = domQuery('.bio-properties-panel-tooltip-wrapper', container);
await openTooltip(wrapper);

// when
fireEvent.mouseMove(container);

// then
expect(domQuery('.bio-properties-panel-tooltip')).to.not.exist;
});


it('should not persist tooltip when opened with mouse - render portal', async function() {

// given
render(<TooltipWithParent />,{ container });

const wrapper = domQuery('.bio-properties-panel-tooltip-wrapper', container);

// when
await openTooltip(wrapper);

// when
wrapper.focus();
fireEvent.mouseMove(container);

// then
expect(domQuery('.bio-properties-panel-tooltip')).to.not.exist;
});


it('should persist tooltip when opened with keyboard focus', async function() {

// given
createTooltip({ container });
const wrapper = domQuery('.bio-properties-panel-tooltip-wrapper', container);
await openTooltip(wrapper, true);

// when
fireEvent.mouseMove(wrapper);

// then
expect(domQuery('.bio-properties-panel-tooltip')).to.exist;
});


it('should persist tooltip when opened with keyboard focus - render portal', async function() {

// given
render(<TooltipWithParent />,{ container });

const wrapper = domQuery('.bio-properties-panel-tooltip-wrapper', container);

// when
await openTooltip(wrapper, true);

// when
fireEvent.mouseMove(container);

// then
expect(domQuery('.bio-properties-panel-tooltip')).to.exist;
});

});


describe('tooltip content', function() {

it('should not persist tooltip - mouse focus', async function() {

// given
const tooltipContent = <div>
<a id="link" href="#">some link</a>
</div>;

createTooltip({ container, value: tooltipContent });
const wrapper = domQuery('.bio-properties-panel-tooltip-wrapper', container);
await openTooltip(wrapper);

// when
const link = domQuery('#link', container);

link.focus();
fireEvent.mouseMove(container);

// then
expect(domQuery('.bio-properties-panel-tooltip')).to.not.exist;
});


it('should persist tooltip - keyboard focus', async function() {

// given
const tooltipContent = <div>
<a id="link" href="#">some link</a>
</div>;

createTooltip({ container, value: tooltipContent });
const wrapper = domQuery('.bio-properties-panel-tooltip-wrapper', container);
await openTooltip(wrapper, true);

// when
const link = domQuery('#link', container);
link.focus();
fireEvent.mouseMove(wrapper);

// then
expect(domQuery('.bio-properties-panel-tooltip')).to.exist;
});

});

});

});


Expand Down Expand Up @@ -307,6 +421,15 @@ function TooltipComponent(props) {
);
}

function TooltipWithParent() {
const ref = useRef();

return <div id="parent" ref={ ref }>
<TooltipComponent parent={ ref } />
</div>;
}


function createTooltip(options = {}, renderFn = render) {
const {
container
Expand Down
Loading