Skip to content

Commit

Permalink
fix(utils): Click Behavior for Hover Mode
Browse files Browse the repository at this point in the history
Need to start the disable timer after a click since clicking an element
_might_ cause the element to disappear or the element to no longer be
under the mouse. When this happens, the `onMouseLeave` event will never
be triggered as well.

The `useTooltip` was also updated to set the visibility to false on
click while the `useHoverMode` only starts the disable timer.
  • Loading branch information
mlaursen committed Apr 21, 2021
1 parent 0a6aed9 commit d0fda80
Show file tree
Hide file tree
Showing 5 changed files with 50 additions and 24 deletions.
3 changes: 3 additions & 0 deletions packages/tooltip/src/Tooltipped.tsx
Expand Up @@ -36,6 +36,7 @@ type ChildProps = Partial<Omit<TooltippedProvidedProps, "tooltip">>;
type ChildElement = ReactElement<ChildProps>;

const MERGABLE_PROPS: (keyof TooltippedElementEventHandlers<HTMLElement>)[] = [
"onClick",
"onMouseEnter",
"onMouseLeave",
"onTouchStart",
Expand Down Expand Up @@ -140,6 +141,7 @@ export function Tooltipped({
position: propPosition,
positionThreshold,
threshold = positionThreshold ?? DEFAULT_TOOLTIP_THRESHOLD,
onClick,
onMouseEnter,
onMouseLeave,
onTouchStart,
Expand Down Expand Up @@ -178,6 +180,7 @@ export function Tooltipped({
onFocus,
onBlur,
onKeyDown,
onClick,
onMouseEnter,
onMouseLeave,
onTouchStart,
Expand Down
4 changes: 4 additions & 0 deletions packages/tooltip/src/__tests__/Tooltipped.tsx
Expand Up @@ -258,6 +258,10 @@ describe("Tooltipped", () => {
const { getByRole } = render(<Test />);
const button = getByRole("button");

// force into "touch mode"
fireEvent.touchStart(document);
fireEvent.touchEnd(document);

fireEvent.touchStart(button);
expect(() => getByRole("tooltip")).toThrow();

Expand Down
18 changes: 14 additions & 4 deletions packages/tooltip/src/useTooltip.ts
Expand Up @@ -313,6 +313,7 @@ export function useTooltip<E extends HTMLElement>({
onFocus: propOnFocus,
onBlur: propOnBlur,
onKeyDown: propOnKeyDown,
onClick,
onMouseEnter,
onMouseLeave,
onTouchStart: propOnTouchStart,
Expand Down Expand Up @@ -343,6 +344,15 @@ export function useTooltip<E extends HTMLElement>({
...others
} = useHoverMode<E>({
disabled,
onClick: (event) => {
onClick?.(event);
if (event.isPropagationStopped()) {
return;
}

setVisible(false);
setInitiatedBy(null);
},
onMouseEnter: (event) => {
onMouseEnter?.(event);
if (initiatedBy !== null) {
Expand Down Expand Up @@ -473,11 +483,11 @@ export function useTooltip<E extends HTMLElement>({
return;
}

window.addEventListener("touchmove", hide);
window.addEventListener("touchend", hide);
window.addEventListener("touchmove", hide, true);
window.addEventListener("touchend", hide, true);
return () => {
window.removeEventListener("touchmove", hide);
window.removeEventListener("touchend", hide);
window.removeEventListener("touchmove", hide, true);
window.removeEventListener("touchend", hide, true);
};
}, [hide, initiatedBy, setVisible]);

Expand Down
1 change: 1 addition & 0 deletions packages/utils/src/hover/__tests__/HoverMode.tsx
@@ -1,3 +1,4 @@
// comment
import React, { MouseEventHandler } from "react";
import { act, fireEvent, render } from "@testing-library/react";
import {
Expand Down
48 changes: 28 additions & 20 deletions packages/utils/src/hover/useHoverMode.ts
Expand Up @@ -27,29 +27,21 @@ export interface HoverModeEventHandlers<E extends HTMLElement> {
* If this function calls `event.stopPropagation()`, the hover mode behavior
* will be disabled.
*/
onMouseEnter?: MouseEventHandler<E>;
onClick?: MouseEventHandler<E>;

/**
* An optional event handler to merge with the hover mode visibility hander.
* If this function calls `event.stopPropagation()`, the hover mode behavior
* will be disabled.
*/
onMouseLeave?: MouseEventHandler<E>;
}
onMouseEnter?: MouseEventHandler<E>;

/**
* Event handlers that should be used when the {@link HoverModeOptions.sticky}
* is enabled to temporarily disable the mouse leave behavior.
*
* @remarks \@since 2.8.0
*/
export interface StickyHoverModeEventHandlers<E extends HTMLElement>
extends HoverModeEventHandlers<E> {
/**
* An optional event handler to merge with the "sticky" mode's `onClick`
* behavior.
* An optional event handler to merge with the hover mode visibility hander.
* If this function calls `event.stopPropagation()`, the hover mode behavior
* will be disabled.
*/
onClick?: MouseEventHandler<E>;
onMouseLeave?: MouseEventHandler<E>;
}

/** @remarks \@since 2.8.0 */
Expand Down Expand Up @@ -83,7 +75,7 @@ export interface HoverModeOnlyOptions<E extends HTMLElement>
/** @remarks \@since 2.8.0 */
export interface HoverModeOptions<E extends HTMLElement>
extends HoverModeOnlyOptions<E>,
StickyHoverModeEventHandlers<E> {
HoverModeEventHandlers<E> {
/**
* Boolean if the hover mode should also provide a "sticky" mode which allows
* the exit behavior to be disabled if the element is clicked.
Expand Down Expand Up @@ -124,7 +116,7 @@ export interface HoverModeReturnValue<E extends HTMLElement>
stuck?: boolean;

/** {@inheritDoc StickyHoverModeEventHandlers} */
stickyHandlers?: Required<StickyHoverModeEventHandlers<E>>;
stickyHandlers?: Required<HoverModeEventHandlers<E>>;
}

/**
Expand Down Expand Up @@ -225,7 +217,7 @@ export function useHoverMode<E extends HTMLElement>(
options: HoverModeOptions<E> & { sticky: true }
): HoverModeReturnValue<E> & {
stuck: boolean;
stickyHandlers: Required<StickyHoverModeEventHandlers<E>>;
stickyHandlers: Required<HoverModeEventHandlers<E>>;
};
export function useHoverMode<E extends HTMLElement>({
disabled = false,
Expand Down Expand Up @@ -321,7 +313,19 @@ export function useHoverMode<E extends HTMLElement>({
const onClick = useCallback(
(event: MouseEvent<E>) => {
propOnClick?.(event);
if (disabled || event.isPropagationStopped()) {
if (event.isPropagationStopped() || disabled) {
return;
}

startDisableTimer();
},
[disabled, propOnClick, startDisableTimer]
);

const onStickyClick = useCallback(
(event: MouseEvent<E>) => {
propOnClick?.(event);
if (event.isPropagationStopped() || disabled) {
return;
}

Expand All @@ -338,13 +342,17 @@ export function useHoverMode<E extends HTMLElement>({
);

const handlers: Required<HoverModeEventHandlers<E>> = {
onClick,
onMouseEnter,
onMouseLeave,
};

let stickyHandlers: Required<StickyHoverModeEventHandlers<E>> | undefined;
let stickyHandlers: Required<HoverModeEventHandlers<E>> | undefined;
if (sticky) {
stickyHandlers = { ...handlers, onClick };
stickyHandlers = {
...handlers,
onClick: onStickyClick,
};
}

return {
Expand Down

0 comments on commit d0fda80

Please sign in to comment.