import * as React from "react";
import { createComponent } from "reakit-system/createComponent";
import { createHook } from "reakit-system/createHook";
import { isButton } from "reakit-utils/isButton";
import { useLiveRef } from "reakit-utils/useLiveRef";
import { isSelfTarget } from "reakit-utils/isSelfTarget";
import {
TabbableOptions,
TabbableHTMLProps,
useTabbable,
} from "../Tabbable/Tabbable";
import { CLICKABLE_KEYS } from "./__keys";
export type ClickableOptions = TabbableOptions & {
/**
* Whether or not trigger click on pressing Enter.
* @private
*/
unstable_clickOnEnter?: boolean;
/**
* Whether or not trigger click on pressing Space.
* @private
*/
unstable_clickOnSpace?: boolean;
};
export type ClickableHTMLProps = TabbableHTMLProps;
export type ClickableProps = ClickableOptions & ClickableHTMLProps;
function isNativeClick(event: React.KeyboardEvent) {
const element = event.currentTarget;
if (!event.isTrusted) return false;
// istanbul ignore next: can't test trusted events yet
return (
isButton(element) ||
element.tagName === "INPUT" ||
element.tagName === "TEXTAREA" ||
element.tagName === "A" ||
element.tagName === "SELECT"
);
}
export const useClickable = createHook({
name: "Clickable",
compose: useTabbable,
keys: CLICKABLE_KEYS,
useOptions({
unstable_clickOnEnter = true,
unstable_clickOnSpace = true,
...options
}) {
return {
unstable_clickOnEnter,
unstable_clickOnSpace,
...options,
};
},
useProps(
options,
{ onKeyDown: htmlOnKeyDown, onKeyUp: htmlOnKeyUp, ...htmlProps }
) {
const [active, setActive] = React.useState(false);
const onKeyDownRef = useLiveRef(htmlOnKeyDown);
const onKeyUpRef = useLiveRef(htmlOnKeyUp);
const onKeyDown = React.useCallback(
(event: React.KeyboardEvent) => {
onKeyDownRef.current?.(event);
if (event.defaultPrevented) return;
if (options.disabled) return;
if (event.metaKey) return;
if (!isSelfTarget(event)) return;
const isEnter = options.unstable_clickOnEnter && event.key === "Enter";
const isSpace = options.unstable_clickOnSpace && event.key === " ";
if (isEnter || isSpace) {
if (isNativeClick(event)) return;
event.preventDefault();
if (isEnter) {
event.currentTarget.click();
} else if (isSpace) {
setActive(true);
}
}
},
[
options.disabled,
options.unstable_clickOnEnter,
options.unstable_clickOnSpace,
]
);
const onKeyUp = React.useCallback(
(event: React.KeyboardEvent) => {
onKeyUpRef.current?.(event);
if (event.defaultPrevented) return;
if (options.disabled) return;
if (event.metaKey) return;
const isSpace = options.unstable_clickOnSpace && event.key === " ";
if (active && isSpace) {
setActive(false);
event.currentTarget.click();
}
},
[options.disabled, options.unstable_clickOnSpace, active]
);
return {
"data-active": active || undefined,
onKeyDown,
onKeyUp,
...htmlProps,
};
},
});
export const Clickable = createComponent({
as: "button",
memo: true,
useHook: useClickable,
});