import type { RefObject } from 'react'; import { useMemo, useRef } from 'react'; import type { TrackingResult } from '../../components/Tracking.js'; type EventHandler = ( result: TrackingResult, ) => void; type PointerEventName = | 'onPointerEnter' | 'onPointerDown' | 'onPointerMove' | 'onPointerUp' | 'onPointerLeave'; type ClickEventName = 'onClick' | 'onDoubleClick'; type WheelEventName = 'onWheel'; export type EventName = PointerEventName | ClickEventName | WheelEventName; type PointerEventHandlers = Partial< Record> >; type ClickEventHandlers = Partial< Record> >; type WheelEventHandlers = Partial< Record> >; export type EventsHandlers = PointerEventHandlers & ClickEventHandlers & WheelEventHandlers; type EventMap = Record & Record & Record; export interface PlotEventsUserActions { registerHandlers: (handlersRef: RefObject) => void; unregisterHandlers: (handlersRef: RefObject) => void; } export interface PlotEventsPlotActions { registerPlot: (plotId: string) => void; unregisterPlot: (plotId: string) => void; handleEvent: ( plotId: string, eventName: T, eventData: TrackingResult, ) => void; } interface PlotEventsState { currentPlot: string | null; plots: Set; handlers: Set>; } export function usePlotEventsState() { const plotEvents = useRef({ currentPlot: null, plots: new Set(), handlers: new Set(), }); const userActions = useMemo( () => ({ registerHandlers(handlersRef: RefObject) { plotEvents.current.handlers.add(handlersRef); }, unregisterHandlers(handlersRef: RefObject) { plotEvents.current.handlers.delete(handlersRef); }, }), [], ); const plotActions = useMemo(() => { return { registerPlot(plotId: string) { plotEvents.current.plots.add(plotId); }, unregisterPlot(plotId: string) { plotEvents.current.plots.delete(plotId); }, handleEvent( plotId: string, eventName: T, eventData: TrackingResult, ) { if (!plotEvents.current.plots.has(plotId)) { return; } if (eventName === 'onPointerDown') { plotEvents.current.currentPlot = plotId; } if (eventName === 'onPointerUp') { plotEvents.current.currentPlot = null; } if ( plotEvents.current.currentPlot !== null && plotEvents.current.currentPlot !== plotId ) { // Ignore events on other plots. return; } for (const handler of plotEvents.current.handlers) { if (handler.current?.[eventName]) { // @ts-expect-error eventData is guaranteed to be compatible with eventName. handler.current[eventName](eventData); } } }, }; }, []); return { userActions, plotActions }; }