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 ?? }
)}
)
}