/* ! * (c) Copyright 2024 Palantir Technologies Inc. All rights reserved. */ import { useCallback, useRef, useState } from "react"; import { mergeRefs, Utils } from "../common"; type InteractiveHTMLAttributes = Pick< React.HTMLAttributes, "onBlur" | "onClick" | "onFocus" | "onKeyDown" | "onKeyUp" | "tabIndex" >; interface InteractiveComponentProps extends InteractiveHTMLAttributes { active?: boolean | undefined; } interface InteractiveAttributes extends InteractiveHTMLAttributes { ref: React.Ref; } export interface UseInteractiveAttributesOptions { defaultTabIndex: number | undefined; disabledTabIndex: number | undefined; } const DEFAULT_OPTIONS: UseInteractiveAttributesOptions = { defaultTabIndex: undefined, disabledTabIndex: -1 }; export function useInteractiveAttributes( interactive: boolean, props: InteractiveComponentProps, ref: React.Ref, options: UseInteractiveAttributesOptions = DEFAULT_OPTIONS, ): [active: boolean, interactiveProps: InteractiveAttributes] { const { defaultTabIndex, disabledTabIndex } = options; const { active, onClick, onFocus, onKeyDown, onKeyUp, onBlur, tabIndex = defaultTabIndex } = props; // the current key being pressed const [currentKeyPressed, setCurrentKeyPressed] = useState(); // whether the button is in "active" state const [isActive, setIsActive] = useState(false); // our local ref for the interactive element, merged with the consumer's own ref in this hook's return value const elementRef = useRef(null); const handleBlur = useCallback( (e: React.FocusEvent) => { if (isActive) { setIsActive(false); } onBlur?.(e); }, [isActive, onBlur], ); const handleKeyDown = useCallback( (e: React.KeyboardEvent) => { if (Utils.isKeyboardClick(e)) { e.preventDefault(); if (e.key !== currentKeyPressed) { setIsActive(true); } } setCurrentKeyPressed(e.key); onKeyDown?.(e); }, [currentKeyPressed, onKeyDown], ); const handleKeyUp = useCallback( (e: React.KeyboardEvent) => { if (Utils.isKeyboardClick(e)) { setIsActive(false); elementRef.current?.click(); } setCurrentKeyPressed(undefined); onKeyUp?.(e); }, [onKeyUp, elementRef], ); const resolvedActive = interactive && (active || isActive); return [ resolvedActive, { onBlur: handleBlur, onClick: interactive ? onClick : undefined, onFocus: interactive ? onFocus : undefined, onKeyDown: handleKeyDown, onKeyUp: handleKeyUp, ref: mergeRefs(elementRef, ref), tabIndex: interactive ? tabIndex : disabledTabIndex, }, ]; }