import { Box, IconButton, useTheme } from '@mui/material' import { SubdirectoryArrowLeftOutlined, ZoomInOutlined, } from '@mui/icons-material' import { useEffect, useCallback } from 'react' import { widgetStoreActions } from '../../stores/widget-store' import type { ZoomToggleProps, ZoomState } from './types' import { styles } from './style' import { Tooltip } from '../../../components' import { getEChartZoomConfig } from '../../echart/utils' import type { EchartOptionsProps } from '../../echart/types' import { useWidgetSelector } from '../../stores/use-widget-selector' export const ZOOM_TOGGLE_TOOL_ID = 'zoom-toggle' /** * Widget action to toggle EChart zoom functionality. * * Registers as a config pipeline tool so that zoom configuration is automatically * re-applied when the base config is updated (e.g., by WidgetLoader). * * Zoom state (enabled, range) is stored in the widget store root, and the tool * derives its `enabled` flag from the store. This keeps state accessible across * component instances. * * When zoom is active, displays an inline reset button to disable zoom. * Only intended for use in fullscreen ToolbarActions for bar and histogram widgets. * * @example * ```tsx * * ``` */ export function ZoomToggle({ id, defaultZoom = false, defaultZoomStart = 0, defaultZoomEnd = 100, labels, Icon, ResetIcon, IconButtonProps, }: ZoomToggleProps) { const theme = useTheme() // Read zoom state from widget store root — single source of truth const { zoom, zoomStart, zoomEnd } = useWidgetSelector(id, (w) => ({ zoom: (w as ZoomState | undefined)?.zoom ?? defaultZoom, zoomStart: (w as ZoomState | undefined)?.zoomStart ?? defaultZoomStart, zoomEnd: (w as ZoomState | undefined)?.zoomEnd ?? defaultZoomEnd, })) // Initialize store with default values on mount useEffect(() => { const current = widgetStoreActions.getWidget(id) if (current?.zoom === undefined) { widgetStoreActions.setWidget(id, { zoom: defaultZoom, zoomStart: defaultZoomStart, zoomEnd: defaultZoomEnd, }) } }, [id, defaultZoom, defaultZoomStart, defaultZoomEnd]) // Handle dataZoom event — update store (source of truth) const handleDataZoom = useCallback( (event: unknown) => { const zoomEvent = event as { start?: number end?: number batch?: { start?: number end?: number }[] } const start = zoomEvent.start ?? zoomEvent.batch?.[0]?.start const end = zoomEvent.end ?? zoomEvent.batch?.[0]?.end if (start !== undefined && end !== undefined) { widgetStoreActions.setWidget(id, { zoom: true, zoomStart: start, zoomEnd: end, }) } }, [id], ) // Register config tool once — fn reads zoom range from the store at execution time. useEffect(() => { widgetStoreActions.registerTool(id, { id: ZOOM_TOGGLE_TOOL_ID, type: 'config', order: 20, enabled: defaultZoom, fn: (currentConfig) => { const config = currentConfig as Record const option = config.option as EchartOptionsProps | undefined const currentOnEvents = (config.onEvents as Record | undefined) ?? {} const widget = widgetStoreActions.getWidget(id) const start = widget?.zoomStart ?? 0 const end = widget?.zoomEnd ?? 100 const legend = option?.legend as { show?: boolean } | undefined const hasLegend = legend?.show !== false && legend !== undefined const sliderHeight = parseInt(theme?.spacing?.(4) ?? '32') const sliderGap = 8 const legendBottomOffset = hasLegend ? 28 : 0 const zoomConfig = getEChartZoomConfig( { start, end }, { inside: true, xSlider: true, ySlider: false, bottomOffset: legendBottomOffset, }, theme, ) const grid = option?.grid as { bottom?: number | string } | undefined const currentGridBottom = typeof grid?.bottom === 'number' ? grid.bottom : parseInt(grid?.bottom ?? '24') const gridBottom = currentGridBottom + sliderHeight + sliderGap const onEvents = { ...currentOnEvents, dataZoom: handleDataZoom } return { ...config, option: { ...option, ...zoomConfig, grid: { ...grid, bottom: gridBottom }, }, onEvents, } }, }) return () => widgetStoreActions.unregisterTool(id, ZOOM_TOGGLE_TOOL_ID) }, [id, theme, handleDataZoom, defaultZoom, defaultZoomStart, defaultZoomEnd]) // Sync enabled from store — lightweight, no re-registration useEffect(() => { widgetStoreActions.setToolEnabled(id, ZOOM_TOGGLE_TOOL_ID, zoom) }, [id, zoom]) // Trigger pipeline when zoom range changes — fn reads from store at execution time, // but the pipeline only re-runs when registeredTools reference changes. // setToolEnabled handles the zoom on/off case, this handles range-only changes. useEffect(() => { widgetStoreActions.triggerToolPipeline(id) }, [id, zoomStart, zoomEnd]) const handleToggle = () => { const newZoom = !zoom widgetStoreActions.setWidget(id, { zoom: newZoom, zoomStart: newZoom ? zoomStart : 0, zoomEnd: newZoom ? zoomEnd : 100, }) } const handleReset = () => { widgetStoreActions.setWidget(id, { zoom: true, zoomStart: defaultZoomStart, zoomEnd: defaultZoomEnd, }) } const enableLabel = labels?.enable ?? 'Enable zoom' const disableLabel = labels?.disable ?? 'Disable zoom' const resetLabel = labels?.reset ?? 'Reset zoom' const tooltipLabel = zoom ? disableLabel : enableLabel return ( {Icon ?? } {zoom && ( {ResetIcon ?? } )} ) }