import { useCallback, useMemo, useState } from "react"; import type { OrganId, OrganInteractionHandlers, OrganInteractionState } from "./types"; export interface UseOrganInteractionOptions { /** Controlled hover state from parent */ hoveredOrgan?: OrganId | null; /** Controlled selected state from parent */ selectedOrgan?: OrganId | null; /** Callback when hover state changes */ onHover?: (organId: OrganId | null) => void; /** Callback when selected state changes */ onSelect?: (organId: OrganId | null) => void; } export interface UseOrganInteractionResult { /** Current interaction state */ state: OrganInteractionState; /** Event handlers for organs */ handlers: OrganInteractionHandlers; /** Check if an organ is currently highlighted (hovered or selected) */ isHighlighted: (organId: OrganId) => boolean; /** Check if an organ is currently selected */ isSelected: (organId: OrganId) => boolean; /** Check if an organ is currently hovered */ isHovered: (organId: OrganId) => boolean; } /** * Hook to manage organ hover and click interactions * * Behavior: * - Mouse enter on organ updates hoveredOrgan and calls onHover * - Click on organ sets selectedOrgan and calls onSelect * - Click on already selected organ deselects it * - Click on different organ changes selection * - Deselect keeps hover state when still over the organ * - Hover effects work independently even when an organ is selected */ export function useOrganInteraction( options: UseOrganInteractionOptions = {}, ): UseOrganInteractionResult { const { hoveredOrgan: controlledHovered, selectedOrgan: controlledSelected, onHover, onSelect, } = options; // Internal state for uncontrolled mode const [internalHovered, setInternalHovered] = useState(null); const [internalSelected, setInternalSelected] = useState(null); // Determine if we're in controlled mode const isHoverControlled = controlledHovered !== undefined; const isSelectControlled = controlledSelected !== undefined; // Current state values const hoveredOrgan = isHoverControlled ? controlledHovered : internalHovered; const selectedOrgan = isSelectControlled ? controlledSelected : internalSelected; const handleMouseEnter = useCallback( (organId: OrganId) => { if (!isHoverControlled) { setInternalHovered(organId); } onHover?.(organId); }, [isHoverControlled, onHover], ); const handleMouseLeave = useCallback(() => { if (!isHoverControlled) { setInternalHovered(null); } onHover?.(null); }, [isHoverControlled, onHover]); const handleClick = useCallback( (organId: OrganId) => { const newSelected = selectedOrgan === organId ? null : organId; if (!isSelectControlled) { setInternalSelected(newSelected); } onSelect?.(newSelected); }, [selectedOrgan, isSelectControlled, onSelect], ); const state: OrganInteractionState = useMemo( () => ({ hoveredOrgan, selectedOrgan, }), [hoveredOrgan, selectedOrgan], ); const handlers: OrganInteractionHandlers = useMemo( () => ({ handleMouseEnter, handleMouseLeave, handleClick, }), [handleMouseEnter, handleMouseLeave, handleClick], ); const isSelected = useCallback((organId: OrganId) => selectedOrgan === organId, [selectedOrgan]); const isHovered = useCallback((organId: OrganId) => hoveredOrgan === organId, [hoveredOrgan]); const isHighlighted = useCallback( (organId: OrganId) => isSelected(organId) || isHovered(organId), [isSelected, isHovered], ); return { state, handlers, isHighlighted, isSelected, isHovered, }; }