Skip to content

Instantly share code, notes, and snippets.

@Akiyamka
Last active February 13, 2024 13:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Akiyamka/f2b43cfe866e2f6f0b717409e975c61d to your computer and use it in GitHub Desktop.
Save Akiyamka/f2b43cfe866e2f6f0b717409e975c61d to your computer and use it in GitHub Desktop.
@floating-ui/react provide hook that can listen left button mouse click. This exatended version do the same but allow you to specify left, right or middle click you want to listen
import { useMemo, useRef } from 'react';
import type { ElementProps, FloatingContext, ReferenceType } from '@floating-ui/react';
import { isMouseLikePointerType, isTypeableElement } from '@floating-ui/react/utils';
import { isHTMLElement } from '@floating-ui/utils/dom';
function isButtonTarget(event: React.KeyboardEvent<Element>) {
return isHTMLElement(event.target) && event.target.tagName === 'BUTTON';
}
function isSpaceIgnored(element: Element | null) {
return isTypeableElement(element);
}
export interface UseClickProps {
/**
* Whether the Hook is enabled, including all internal Effects and event
* handlers.
* @default true
*/
enabled?: boolean;
/**
* The type of event to use to determine a “click” with mouse input.
* Keyboard clicks work as normal.
* @default 'click'
*/
event?: MouseButtons | 'mousedown';
/**
* Whether to toggle the open state with repeated clicks.
* @default true
*/
toggle?: boolean;
/**
* Whether to ignore the logic for mouse input (for example, if `useHover()`
* is also being used).
* When `useHover()` and `useClick()` are used together, clicking the
* reference element after hovering it will keep the floating element open
* even once the cursor leaves. This may be not be desirable in some cases.
* @default false
*/
ignoreMouse?: boolean;
/**
* Whether to add keyboard handlers (Enter and Space key functionality) for
* non-button elements (to open/close the floating element via keyboard
* “click”).
* @default true
*/
keyboardHandlers?: boolean;
}
/**
* Opens or closes the floating element when clicking the reference element.
* @param context
* @param props
* @see https://floating-ui.com/docs/useClick
*/
const MOUSE_BUTTONS = ['click', 'middle-click', 'right-click'];
type MouseButtons = 'click' | 'middle-click' | 'right-click';
export function useExtendedClick<RT extends ReferenceType = ReferenceType>(context: FloatingContext<RT>, props: UseClickProps = {}): ElementProps {
const {
open,
onOpenChange,
dataRef,
elements: { domReference },
} = context;
const { enabled = true, event: eventOption = 'click', toggle = true, ignoreMouse = false, keyboardHandlers = true } = props;
const pointerTypeRef = useRef<'mouse' | 'pen' | 'touch'>();
const didKeyDownRef = useRef(false);
return useMemo(() => {
if (!enabled) return {};
return {
reference: {
onPointerDown(event) {
pointerTypeRef.current = event.pointerType;
},
onMouseDown(event) {
// Ignore all buttons except for the "main" button.
// https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button
if (event.button !== MOUSE_BUTTONS.indexOf(eventOption)) {
return;
}
if (isMouseLikePointerType(pointerTypeRef.current, true) && ignoreMouse) {
return;
}
if (MOUSE_BUTTONS.includes(eventOption)) {
return;
}
if (open && toggle && (dataRef.current.openEvent ? dataRef.current.openEvent.type === 'mousedown' : true)) {
onOpenChange(false, event.nativeEvent, 'click');
} else {
// Prevent stealing focus from the floating element
event.preventDefault();
onOpenChange(true, event.nativeEvent, 'click');
}
},
onClick(event) {
if (eventOption === 'mousedown' && pointerTypeRef.current) {
pointerTypeRef.current = undefined;
return;
}
if (isMouseLikePointerType(pointerTypeRef.current, true) && ignoreMouse) {
return;
}
if (open && toggle && (dataRef.current.openEvent ? dataRef.current.openEvent.type === 'click' : true)) {
onOpenChange(false, event.nativeEvent, 'click');
} else {
onOpenChange(true, event.nativeEvent, 'click');
}
},
onKeyDown(event) {
pointerTypeRef.current = undefined;
if (event.defaultPrevented || !keyboardHandlers || isButtonTarget(event)) {
return;
}
if (event.key === ' ' && !isSpaceIgnored(domReference)) {
// Prevent scrolling
event.preventDefault();
didKeyDownRef.current = true;
}
if (event.key === 'Enter') {
if (open && toggle) {
onOpenChange(false, event.nativeEvent, 'click');
} else {
onOpenChange(true, event.nativeEvent, 'click');
}
}
},
onKeyUp(event) {
if (event.defaultPrevented || !keyboardHandlers || isButtonTarget(event) || isSpaceIgnored(domReference)) {
return;
}
if (event.key === ' ' && didKeyDownRef.current) {
didKeyDownRef.current = false;
if (open && toggle) {
onOpenChange(false, event.nativeEvent, 'click');
} else {
onOpenChange(true, event.nativeEvent, 'click');
}
}
},
},
};
}, [enabled, dataRef, eventOption, ignoreMouse, keyboardHandlers, domReference, toggle, open, onOpenChange]);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment