import { Box, IconButton } from '@mui/material' import { HighlightAltOutlined } from '@mui/icons-material' import { useCallback, useEffect, useRef } from 'react' import { widgetStoreActions } from '../../stores/widget-store' import type { BrushSelectedItems, BrushState, BrushToggleProps } from './types' import { styles } from './style' import { Tooltip } from '../../../components' import { useWidgetSelector } from '../../stores/use-widget-selector' import { BrushOverlay } from './brush-overlay' /** * Kept for backwards compatibility. The tool is no longer registered with the * widget store — brush state is now owned directly by this component and the * associated DOM overlay instead of being driven through the config pipeline. * Existing code that looks this constant up in `registeredTools` will simply * find no match. */ export const BRUSH_TOGGLE_TOOL_ID = 'brush-toggle' /** * Widget action to toggle brush-style selection on bar and histogram widgets. * * Renders a toggle button and a portaled overlay rendered inside the chart's * `
`. The overlay handles all pointer interactions, draws selection * rectangles in DOM, and hit-tests against the widget data. All state lives * in the widget store — rectangles survive config-pipeline re-renders * (`setOption({ notMerge: true })`) automatically. * * Only intended for bar and histogram widgets (both use `xAxis.type === 'category'`). * * @example * ```tsx * console.log(items)} * /> * ``` */ export function BrushToggle({ id, onBrushSelected, multiBrush, selections, defaultEnabled = false, labels, Icon, IconButtonProps, }: BrushToggleProps) { // Single subscription for everything this component cares about. const { brush, selection } = useWidgetSelector(id, (w) => { const s = w as BrushState | undefined return { brush: s?.brush ?? defaultEnabled, selection: s?.brushSelection, } }) // Seed the widget store with `defaultEnabled` on first mount. Guarded so // remounts don't stomp whatever the user has toggled to. useEffect(() => { const current = widgetStoreActions.getWidget(id)?.brush if (current === undefined) { widgetStoreActions.setWidget(id, { brush: defaultEnabled }) } }, [id, defaultEnabled]) const handleToggle = useCallback(() => { if (brush) { // Disabling: keep rectangles visible so the consumer's selection stays // in sync. Re-enabling clears them below (fresh slate each activation). widgetStoreActions.setWidget(id, { brush: false }) } else { widgetStoreActions.setWidget(id, { brush: true, brushRects: [], brushSelection: { dataIndex: [], seriesIndex: 0 }, }) // Mirror the clear to the consumer so its selection drops with the // visual rectangles. onBrushSelected?.({ dataIndex: [], seriesIndex: 0 }) } }, [brush, id, onBrushSelected]) // Invoke the consumer's callback whenever the overlay writes a new // selection to the store. `selection` is a new object reference on each // pointerup so a simple reference check is enough. const lastSelectionRef = useRef(undefined) useEffect(() => { if (!selection) return if (lastSelectionRef.current === selection) return lastSelectionRef.current = selection onBrushSelected?.(selection) }, [selection, onBrushSelected]) // Consumer-driven clear: when `selections` transitions to 0 (e.g. the user // presses the clear button in `WidgetSelectionSummary`), drop all drawn // rectangles and any stored selection. useEffect(() => { if (selections !== 0) return const state = widgetStoreActions.getWidget(id) const hasRects = (state?.brushRects?.length ?? 0) > 0 const hasSelection = (state?.brushSelection?.dataIndex?.length ?? 0) > 0 if (!hasRects && !hasSelection) return widgetStoreActions.setWidget(id, { brushRects: [], brushSelection: { dataIndex: [], seriesIndex: 0 }, }) }, [selections, id]) const enableLabel = labels?.enable ?? 'Enable brush selection' const disableLabel = labels?.disable ?? 'Disable brush selection' const tooltipLabel = brush ? disableLabel : enableLabel return ( <> {Icon ?? } ) }