UNPKG

react-plot

Version:

Library of React components to render SVG 2D plots.

119 lines (105 loc) 3.38 kB
import type { RefObject } from 'react'; import { useMemo, useRef } from 'react'; import type { TrackingResult } from '../../components/Tracking.js'; type EventHandler<NativeEventType extends MouseEvent> = ( result: TrackingResult<NativeEventType>, ) => 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<PointerEventName, EventHandler<PointerEvent>> >; type ClickEventHandlers = Partial< Record<ClickEventName, EventHandler<MouseEvent>> >; type WheelEventHandlers = Partial< Record<WheelEventName, EventHandler<WheelEvent>> >; export type EventsHandlers = PointerEventHandlers & ClickEventHandlers & WheelEventHandlers; type EventMap = Record<PointerEventName, PointerEvent> & Record<ClickEventName, MouseEvent> & Record<WheelEventName, WheelEvent>; export interface PlotEventsUserActions { registerHandlers: (handlersRef: RefObject<EventsHandlers>) => void; unregisterHandlers: (handlersRef: RefObject<EventsHandlers>) => void; } export interface PlotEventsPlotActions { registerPlot: (plotId: string) => void; unregisterPlot: (plotId: string) => void; handleEvent: <T extends EventName>( plotId: string, eventName: T, eventData: TrackingResult<EventMap[T]>, ) => void; } interface PlotEventsState { currentPlot: string | null; plots: Set<string>; handlers: Set<RefObject<EventsHandlers>>; } export function usePlotEventsState() { const plotEvents = useRef<PlotEventsState>({ currentPlot: null, plots: new Set(), handlers: new Set(), }); const userActions = useMemo<PlotEventsUserActions>( () => ({ registerHandlers(handlersRef: RefObject<EventsHandlers>) { plotEvents.current.handlers.add(handlersRef); }, unregisterHandlers(handlersRef: RefObject<EventsHandlers>) { plotEvents.current.handlers.delete(handlersRef); }, }), [], ); const plotActions = useMemo<PlotEventsPlotActions>(() => { return { registerPlot(plotId: string) { plotEvents.current.plots.add(plotId); }, unregisterPlot(plotId: string) { plotEvents.current.plots.delete(plotId); }, handleEvent<T extends EventName>( plotId: string, eventName: T, eventData: TrackingResult<EventMap[T]>, ) { 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 }; }