@carto/ps-react-ui 4.3.6 → 4.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/dist/components.js +123 -123
  2. package/dist/components.js.map +1 -1
  3. package/dist/error-CEkRPccv.js +39 -0
  4. package/dist/error-CEkRPccv.js.map +1 -0
  5. package/dist/{lasso-tool-BctzdzBu.js → lasso-tool-jl4YK02H.js} +19 -19
  6. package/dist/lasso-tool-jl4YK02H.js.map +1 -0
  7. package/dist/no-data-hR3KcJ-_.js +60 -0
  8. package/dist/no-data-hR3KcJ-_.js.map +1 -0
  9. package/dist/{row-D3uVFImu.js → row-BKmVAUN5.js} +2 -2
  10. package/dist/{row-D3uVFImu.js.map → row-BKmVAUN5.js.map} +1 -1
  11. package/dist/{series-BAImrSBo.js → series-D1pynfeh.js} +3 -3
  12. package/dist/{series-BAImrSBo.js.map → series-D1pynfeh.js.map} +1 -1
  13. package/dist/{styles-CCZnY17y.js → styles-DrPyd0y5.js} +28 -22
  14. package/dist/styles-DrPyd0y5.js.map +1 -0
  15. package/dist/types/components/lasso-tool/types.d.ts +1 -1
  16. package/dist/types/widgets/_shared/chart-config/index.d.ts +1 -1
  17. package/dist/types/widgets/_shared/chart-config/option-builders.d.ts +7 -0
  18. package/dist/types/widgets/_shared/chart-config/option-builders.test.d.ts +1 -0
  19. package/dist/types/widgets/actions/index.d.ts +2 -2
  20. package/dist/types/widgets/actions/lock-selection/types.d.ts +0 -13
  21. package/dist/types/widgets/actions/relative-data/types.d.ts +0 -4
  22. package/dist/types/widgets/actions/searcher/types.d.ts +0 -2
  23. package/dist/types/widgets/actions/stack-toggle/types.d.ts +0 -4
  24. package/dist/types/widgets/echart/types.d.ts +0 -4
  25. package/dist/types/widgets/echart/utils.d.ts +2 -1
  26. package/dist/types/widgets/error/error.d.ts +1 -1
  27. package/dist/types/widgets/error/types.d.ts +8 -0
  28. package/dist/types/widgets/loader/loader.d.ts +1 -1
  29. package/dist/types/widgets/loader/types.d.ts +1 -1
  30. package/dist/types/widgets/stores/types.d.ts +1 -1
  31. package/dist/{use-widget-ref-B8x4sHIj.js → use-widget-ref-P-2i0MJG.js} +2 -2
  32. package/dist/{use-widget-ref-B8x4sHIj.js.map → use-widget-ref-P-2i0MJG.js.map} +1 -1
  33. package/dist/{utils-D3-eQyDR.js → utils-idmvq0Oa.js} +17 -16
  34. package/dist/utils-idmvq0Oa.js.map +1 -0
  35. package/dist/{widget-store-Dn0Bnc4h.js → widget-store-CzDt8oSK.js} +31 -46
  36. package/dist/widget-store-CzDt8oSK.js.map +1 -0
  37. package/dist/widgets/actions.js +690 -716
  38. package/dist/widgets/actions.js.map +1 -1
  39. package/dist/widgets/bar.js +67 -63
  40. package/dist/widgets/bar.js.map +1 -1
  41. package/dist/widgets/category.js +250 -241
  42. package/dist/widgets/category.js.map +1 -1
  43. package/dist/widgets/echart.js +93 -100
  44. package/dist/widgets/echart.js.map +1 -1
  45. package/dist/widgets/error.js +1 -1
  46. package/dist/widgets/formula.js +64 -72
  47. package/dist/widgets/formula.js.map +1 -1
  48. package/dist/widgets/histogram.js +75 -73
  49. package/dist/widgets/histogram.js.map +1 -1
  50. package/dist/widgets/loader.js +1 -1
  51. package/dist/widgets/loader.js.map +1 -1
  52. package/dist/widgets/markdown.js +2 -2
  53. package/dist/widgets/no-data.js +1 -1
  54. package/dist/widgets/pie.js +4 -4
  55. package/dist/widgets/range.js +97 -105
  56. package/dist/widgets/range.js.map +1 -1
  57. package/dist/widgets/scatterplot.js +8 -8
  58. package/dist/widgets/skeleton-loader.js +1 -1
  59. package/dist/widgets/spread.js +84 -100
  60. package/dist/widgets/spread.js.map +1 -1
  61. package/dist/widgets/stores.js +1 -1
  62. package/dist/widgets/table.js +493 -485
  63. package/dist/widgets/table.js.map +1 -1
  64. package/dist/widgets/timeseries.js +4 -4
  65. package/dist/widgets/wrapper.js +156 -156
  66. package/dist/widgets/wrapper.js.map +1 -1
  67. package/dist/widgets.js +4 -4
  68. package/package.json +3 -3
  69. package/src/components/lasso-tool/lasso-tool-inline.tsx +19 -17
  70. package/src/components/lasso-tool/lasso-tool.tsx +22 -20
  71. package/src/components/lasso-tool/types.ts +4 -3
  72. package/src/widgets/_shared/chart-config/index.ts +1 -0
  73. package/src/widgets/_shared/chart-config/option-builders.test.ts +40 -0
  74. package/src/widgets/_shared/chart-config/option-builders.ts +12 -0
  75. package/src/widgets/actions/fullscreen/fullscreen.tsx +5 -8
  76. package/src/widgets/actions/index.ts +2 -5
  77. package/src/widgets/actions/lock-selection/lock-selection.test.tsx +28 -30
  78. package/src/widgets/actions/lock-selection/lock-selection.tsx +25 -26
  79. package/src/widgets/actions/lock-selection/types.ts +0 -17
  80. package/src/widgets/actions/relative-data/relative-data.test.tsx +13 -13
  81. package/src/widgets/actions/relative-data/relative-data.tsx +18 -21
  82. package/src/widgets/actions/relative-data/types.ts +0 -7
  83. package/src/widgets/actions/searcher/searcher.tsx +40 -22
  84. package/src/widgets/actions/searcher/types.ts +0 -2
  85. package/src/widgets/actions/stack-toggle/stack-toggle.test.tsx +19 -9
  86. package/src/widgets/actions/stack-toggle/stack-toggle.tsx +32 -22
  87. package/src/widgets/actions/stack-toggle/types.ts +0 -8
  88. package/src/widgets/actions/zoom-toggle/zoom-toggle.tsx +113 -95
  89. package/src/widgets/bar/config.ts +37 -28
  90. package/src/widgets/category/category-ui.tsx +25 -22
  91. package/src/widgets/echart/echart-ui.test.tsx +3 -18
  92. package/src/widgets/echart/echart-ui.tsx +4 -22
  93. package/src/widgets/echart/echart.test.tsx +9 -25
  94. package/src/widgets/echart/echart.tsx +36 -29
  95. package/src/widgets/echart/types.ts +0 -4
  96. package/src/widgets/echart/utils.ts +3 -1
  97. package/src/widgets/error/error.tsx +17 -14
  98. package/src/widgets/error/types.ts +10 -0
  99. package/src/widgets/formula/components/value.tsx +13 -13
  100. package/src/widgets/histogram/config.ts +36 -29
  101. package/src/widgets/loader/loader.tsx +3 -1
  102. package/src/widgets/loader/types.ts +3 -1
  103. package/src/widgets/no-data/no-data.tsx +8 -11
  104. package/src/widgets/range/components/range-item.tsx +9 -13
  105. package/src/widgets/spread/components/max-value.tsx +13 -13
  106. package/src/widgets/spread/components/min-value.tsx +13 -13
  107. package/src/widgets/stores/types.ts +1 -4
  108. package/src/widgets/stores/widget-store.ts +1 -27
  109. package/src/widgets/table/hooks/use-pagination.ts +44 -35
  110. package/src/widgets/table/hooks/use-sort.ts +25 -23
  111. package/src/widgets/wrapper/wrapper-ui.tsx +16 -17
  112. package/dist/error-piB8FwYO.js +0 -38
  113. package/dist/error-piB8FwYO.js.map +0 -1
  114. package/dist/lasso-tool-BctzdzBu.js.map +0 -1
  115. package/dist/no-data-jdlbMef0.js +0 -61
  116. package/dist/no-data-jdlbMef0.js.map +0 -1
  117. package/dist/styles-CCZnY17y.js.map +0 -1
  118. package/dist/utils-D3-eQyDR.js.map +0 -1
  119. package/dist/widget-store-Dn0Bnc4h.js.map +0 -1
@@ -39,14 +39,17 @@ describe('StackToggle', () => {
39
39
  expect(button).toBeTruthy()
40
40
  })
41
41
 
42
- test('toggles to stacked mode and updates widget store', () => {
42
+ test('toggles to stacked mode and updates tool config', () => {
43
43
  render(<StackToggle id={widgetId} />)
44
44
 
45
45
  const button = screen.getByRole('button')
46
46
  fireEvent.click(button)
47
47
 
48
48
  const widget = useWidgetStore.getState().getWidget(widgetId)
49
- expect((widget as { isStacked?: boolean })?.isStacked).toBe(true)
49
+ const tool = widget?.registeredTools?.find(
50
+ (t) => t.id === STACK_TOGGLE_TOOL_ID,
51
+ )
52
+ expect(tool?.config?.stacked).toBe(true)
50
53
  })
51
54
 
52
55
  test('toggles back to unstacked mode', () => {
@@ -61,7 +64,10 @@ describe('StackToggle', () => {
61
64
  fireEvent.click(button)
62
65
 
63
66
  const widget = useWidgetStore.getState().getWidget(widgetId)
64
- expect((widget as { isStacked?: boolean })?.isStacked).toBe(false)
67
+ const tool = widget?.registeredTools?.find(
68
+ (t) => t.id === STACK_TOGGLE_TOOL_ID,
69
+ )
70
+ expect(tool?.config?.stacked).toBe(false)
65
71
  })
66
72
 
67
73
  test('has active state when in stacked mode', () => {
@@ -99,18 +105,24 @@ describe('StackToggle', () => {
99
105
  expect(button).toBeTruthy()
100
106
  })
101
107
 
102
- test('initializes store with default values on mount', () => {
108
+ test('initializes tool config with default values on mount', () => {
103
109
  render(<StackToggle id={widgetId} />)
104
110
 
105
111
  const widget = useWidgetStore.getState().getWidget(widgetId)
106
- expect((widget as { isStacked?: boolean })?.isStacked).toBe(false)
112
+ const tool = widget?.registeredTools?.find(
113
+ (t) => t.id === STACK_TOGGLE_TOOL_ID,
114
+ )
115
+ expect(tool?.config?.stacked).toBe(false)
107
116
  })
108
117
 
109
- test('initializes store with stacked values when defaultIsStacked is true', () => {
118
+ test('initializes tool config with stacked values when defaultIsStacked is true', () => {
110
119
  render(<StackToggle id={widgetId} defaultIsStacked />)
111
120
 
112
121
  const widget = useWidgetStore.getState().getWidget(widgetId)
113
- expect((widget as { isStacked?: boolean })?.isStacked).toBe(true)
122
+ const tool = widget?.registeredTools?.find(
123
+ (t) => t.id === STACK_TOGGLE_TOOL_ID,
124
+ )
125
+ expect(tool?.config?.stacked).toBe(true)
114
126
  })
115
127
 
116
128
  test('applies stack to EChart series via config pipeline when toggling to stacked', async () => {
@@ -279,7 +291,6 @@ describe('StackToggle', () => {
279
291
  test('config tool re-applies stack when config pipeline re-runs', async () => {
280
292
  // Start with stacked widget
281
293
  useWidgetStore.getState().setWidget(widgetId, {
282
- isStacked: true,
283
294
  option: {
284
295
  series: [
285
296
  { name: 'Series 1', type: 'bar', stack: DEFAULT_STACK_GROUP },
@@ -313,7 +324,6 @@ describe('StackToggle', () => {
313
324
  test('config tool does not apply stack when unstacked and pipeline re-runs', async () => {
314
325
  // Start with unstacked widget
315
326
  useWidgetStore.getState().setWidget(widgetId, {
316
- isStacked: false,
317
327
  option: {
318
328
  series: [
319
329
  { name: 'Series 1', type: 'bar' },
@@ -1,7 +1,7 @@
1
1
  import { IconButton } from '@mui/material'
2
2
  import { useCallback, useEffect, useMemo } from 'react'
3
3
  import { useWidgetStore } from '../../stores/widget-store'
4
- import type { StackToggleProps, StackToggleState } from './types'
4
+ import type { StackToggleProps } from './types'
5
5
  import { actionButtonStyles } from '../shared/styles'
6
6
  import { Tooltip } from '../../../components'
7
7
  import { GroupedBarChartIcon } from './grouped-bar-chart-icon'
@@ -34,14 +34,17 @@ export function StackToggle({
34
34
  Icon,
35
35
  IconButtonProps,
36
36
  }: StackToggleProps) {
37
- const setWidget = useWidgetStore((state) => state.setWidget)
37
+ const getWidget = useWidgetStore((state) => state.getWidget)
38
38
  const registerTool = useWidgetStore((state) => state.registerTool)
39
39
  const unregisterTool = useWidgetStore((state) => state.unregisterTool)
40
40
  const setToolEnabled = useWidgetStore((state) => state.setToolEnabled)
41
41
  const updateToolConfig = useWidgetStore((state) => state.updateToolConfig)
42
42
 
43
- const storeIsStacked = useWidgetStore(
44
- useShallow((state) => state.getWidget<StackToggleState>(id)?.isStacked),
43
+ const stackTool = useWidgetStore(
44
+ useShallow((state) => {
45
+ const tools = state.getWidget(id)?.registeredTools ?? []
46
+ return tools.find((tool) => tool.id === STACK_TOGGLE_TOOL_ID)
47
+ }),
45
48
  )
46
49
 
47
50
  const option = useWidgetStore(
@@ -61,15 +64,25 @@ export function StackToggle({
61
64
 
62
65
  // If series already has stack defined, default to stacked=true
63
66
  const effectiveDefaultIsStacked = hasStackInSeries || defaultIsStacked
64
- const isStacked = storeIsStacked ?? effectiveDefaultIsStacked
67
+ const isStacked =
68
+ (stackTool?.config?.stacked as boolean | undefined) ??
69
+ effectiveDefaultIsStacked
65
70
 
66
71
  // Register config tool on mount
67
72
  useEffect(() => {
73
+ const existingTool = getWidget(id)?.registeredTools?.find(
74
+ (tool) => tool.id === STACK_TOGGLE_TOOL_ID,
75
+ )
76
+
77
+ const initialStacked =
78
+ (existingTool?.config?.stacked as boolean | undefined) ??
79
+ effectiveDefaultIsStacked
80
+
68
81
  registerTool(id, {
69
82
  id: STACK_TOGGLE_TOOL_ID,
70
83
  type: 'config',
71
84
  order: 10,
72
- enabled: isStacked && hasMultiSeries,
85
+ enabled: initialStacked && hasMultiSeries,
73
86
  fn: (currentConfig, toolConfig) => {
74
87
  const config = currentConfig as Record<string, unknown>
75
88
  const option = config.option as EchartOptionsProps | undefined
@@ -90,26 +103,23 @@ export function StackToggle({
90
103
 
91
104
  return { ...config, option: { ...option, series: updatedSeries } }
92
105
  },
93
- config: { stacked: isStacked },
106
+ config: { stacked: initialStacked },
94
107
  })
95
108
  return () => unregisterTool(id, STACK_TOGGLE_TOOL_ID)
96
- }, [id, registerTool, unregisterTool, isStacked, hasMultiSeries])
97
-
98
- // Sync tool enabled/config when state changes
99
- useEffect(() => {
100
- setToolEnabled(id, STACK_TOGGLE_TOOL_ID, isStacked && hasMultiSeries)
101
- updateToolConfig(id, STACK_TOGGLE_TOOL_ID, { stacked: isStacked })
102
- }, [id, isStacked, hasMultiSeries, setToolEnabled, updateToolConfig])
103
-
104
- // Initialize store with default value only if not already configured
105
- useEffect(() => {
106
- if (storeIsStacked !== undefined) return
107
- setWidget(id, { isStacked: effectiveDefaultIsStacked })
108
- }, [effectiveDefaultIsStacked, id, setWidget, storeIsStacked])
109
+ }, [
110
+ id,
111
+ registerTool,
112
+ unregisterTool,
113
+ effectiveDefaultIsStacked,
114
+ hasMultiSeries,
115
+ getWidget,
116
+ ])
109
117
 
110
118
  const handleToggle = useCallback(() => {
111
- setWidget(id, { isStacked: !isStacked })
112
- }, [isStacked, id, setWidget])
119
+ const newStacked = !isStacked
120
+ setToolEnabled(id, STACK_TOGGLE_TOOL_ID, newStacked && hasMultiSeries)
121
+ updateToolConfig(id, STACK_TOGGLE_TOOL_ID, { stacked: newStacked })
122
+ }, [isStacked, hasMultiSeries, id, setToolEnabled, updateToolConfig])
113
123
 
114
124
  const tooltipLabel = isStacked
115
125
  ? (labels?.unstacked ?? 'Disable stacking')
@@ -1,7 +1,5 @@
1
1
  import type { IconButtonProps } from '@mui/material'
2
2
  import type { ReactNode } from 'react'
3
- import type { BaseWidgetState } from '../../stores/types'
4
-
5
3
  export interface StackToggleProps {
6
4
  /** Widget ID to update stack configuration in the widget store */
7
5
  id: string
@@ -21,9 +19,3 @@ export interface StackToggleProps {
21
19
  /** Custom icon to display */
22
20
  Icon?: ReactNode
23
21
  }
24
-
25
- export type StackToggleState<T = unknown> = BaseWidgetState<
26
- T & {
27
- isStacked?: boolean
28
- }
29
- >
@@ -5,11 +5,10 @@ import {
5
5
  } from '@mui/icons-material'
6
6
  import { useEffect, useCallback } from 'react'
7
7
  import { useWidgetStore } from '../../stores/widget-store'
8
- import type { ZoomToggleProps, ZoomState } from './types'
8
+ import type { ZoomToggleProps } from './types'
9
9
  import { styles } from './style'
10
10
  import { Tooltip } from '../../../components'
11
11
  import { getEChartZoomConfig } from '../../echart/utils'
12
- import type { EchartWidgetState } from '../../echart/types'
13
12
  import type { EchartOptionsProps } from '../../echart/types'
14
13
  import { useShallow } from 'zustand/shallow'
15
14
 
@@ -45,143 +44,162 @@ export function ZoomToggle({
45
44
  IconButtonProps,
46
45
  }: ZoomToggleProps) {
47
46
  const theme = useTheme()
48
- const setWidget = useWidgetStore((state) => state.setWidget)
49
47
  const getWidget = useWidgetStore((state) => state.getWidget)
50
48
  const registerTool = useWidgetStore((state) => state.registerTool)
51
49
  const unregisterTool = useWidgetStore((state) => state.unregisterTool)
52
50
  const setToolEnabled = useWidgetStore((state) => state.setToolEnabled)
53
51
  const updateToolConfig = useWidgetStore((state) => state.updateToolConfig)
54
52
 
55
- const zoom = useWidgetStore(
53
+ const zoomTool = useWidgetStore(
56
54
  useShallow((state) => {
57
- const widget = state.getWidget<ZoomState>(id)
58
- return widget?.zoom ?? false
55
+ const tools = state.getWidget(id)?.registeredTools ?? []
56
+ return tools.find((tool) => tool.id === ZOOM_TOGGLE_TOOL_ID)
59
57
  }),
60
58
  )
61
59
 
62
- const zoomStart = useWidgetStore(
63
- useShallow((state) => {
64
- const widget = state.getWidget<ZoomState>(id)
65
- return widget?.zoomStart ?? defaultZoomStart
66
- }),
67
- )
60
+ const zoom = zoomTool?.enabled ?? defaultZoom
61
+ const zoomStart =
62
+ (zoomTool?.config?.start as number | undefined) ?? defaultZoomStart
63
+ const zoomEnd =
64
+ (zoomTool?.config?.end as number | undefined) ?? defaultZoomEnd
68
65
 
69
- const zoomEnd = useWidgetStore(
70
- useShallow((state) => {
71
- const widget = state.getWidget<ZoomState>(id)
72
- return widget?.zoomEnd ?? defaultZoomEnd
73
- }),
66
+ // Handle dataZoom event to update zoom range in tool config
67
+ const handleDataZoom = useCallback(
68
+ (event: unknown) => {
69
+ const zoomEvent = event as {
70
+ start?: number
71
+ end?: number
72
+ batch?: {
73
+ start?: number
74
+ end?: number
75
+ }[]
76
+ }
77
+
78
+ const start = zoomEvent.start ?? zoomEvent.batch?.[0]?.start
79
+ const end = zoomEvent.end ?? zoomEvent.batch?.[0]?.end
80
+
81
+ if (start !== undefined && end !== undefined) {
82
+ setToolEnabled(id, ZOOM_TOGGLE_TOOL_ID, true)
83
+ updateToolConfig(id, ZOOM_TOGGLE_TOOL_ID, {
84
+ enabled: true,
85
+ start,
86
+ end,
87
+ })
88
+ }
89
+ },
90
+ [id, setToolEnabled, updateToolConfig],
74
91
  )
75
92
 
76
93
  // Register config tool on mount
77
94
  useEffect(() => {
95
+ const existingTool = getWidget(id)?.registeredTools?.find(
96
+ (tool) => tool.id === ZOOM_TOGGLE_TOOL_ID,
97
+ )
98
+
99
+ const initialEnabled = existingTool?.enabled ?? defaultZoom
100
+ const initialStart =
101
+ (existingTool?.config?.start as number | undefined) ?? defaultZoomStart
102
+ const initialEnd =
103
+ (existingTool?.config?.end as number | undefined) ?? defaultZoomEnd
104
+
78
105
  registerTool(id, {
79
106
  id: ZOOM_TOGGLE_TOOL_ID,
80
107
  type: 'config',
81
108
  order: 20,
82
- enabled: zoom,
109
+ enabled: initialEnabled,
83
110
  fn: (currentConfig, toolConfig) => {
84
111
  const config = currentConfig as Record<string, unknown>
85
112
  const option = config.option as EchartOptionsProps | undefined
113
+ const currentOnEvents =
114
+ (config.onEvents as Record<string, unknown> | undefined) ?? {}
115
+
86
116
  const enabled = (toolConfig?.enabled as boolean) ?? false
87
117
  const start = (toolConfig?.start as number) ?? 0
88
118
  const end = (toolConfig?.end as number) ?? 100
89
119
 
120
+ const legend = option?.legend as { show?: boolean } | undefined
121
+ const hasLegend = legend?.show !== false && legend !== undefined
122
+
123
+ const sliderHeight = parseInt(theme?.spacing?.(4) ?? '32')
124
+ const sliderGap = 8
125
+ const legendBottomOffset = hasLegend ? 28 : 0
126
+
90
127
  const zoomConfig = getEChartZoomConfig(
91
128
  enabled,
92
129
  { start, end },
93
- { inside: true, xSlider: true, ySlider: false },
130
+ {
131
+ inside: true,
132
+ xSlider: true,
133
+ ySlider: false,
134
+ bottomOffset: legendBottomOffset,
135
+ },
94
136
  theme,
95
137
  )
96
- return { ...config, option: { ...option, ...zoomConfig } }
138
+
139
+ const grid = option?.grid as { bottom?: number | string } | undefined
140
+ const currentGridBottom =
141
+ typeof grid?.bottom === 'number'
142
+ ? grid.bottom
143
+ : parseInt(grid?.bottom ?? '24')
144
+
145
+ const gridBottom = enabled
146
+ ? currentGridBottom + sliderHeight + sliderGap
147
+ : currentGridBottom
148
+
149
+ const onEventsWithoutDataZoom = { ...currentOnEvents }
150
+ delete onEventsWithoutDataZoom.dataZoom
151
+ const onEvents = enabled
152
+ ? { ...currentOnEvents, dataZoom: handleDataZoom }
153
+ : onEventsWithoutDataZoom
154
+
155
+ return {
156
+ ...config,
157
+ option: {
158
+ ...option,
159
+ ...zoomConfig,
160
+ grid: { ...grid, bottom: gridBottom },
161
+ },
162
+ onEvents,
163
+ }
164
+ },
165
+ config: {
166
+ enabled: initialEnabled,
167
+ start: initialStart,
168
+ end: initialEnd,
97
169
  },
98
- config: { enabled: zoom, start: zoomStart, end: zoomEnd },
99
170
  })
100
171
  return () => unregisterTool(id, ZOOM_TOGGLE_TOOL_ID)
101
- }, [id, registerTool, unregisterTool, zoom, theme, zoomStart, zoomEnd])
102
-
103
- // Sync tool enabled/config when state changes
104
- useEffect(() => {
105
- setToolEnabled(id, ZOOM_TOGGLE_TOOL_ID, zoom)
106
- updateToolConfig(id, ZOOM_TOGGLE_TOOL_ID, {
107
- enabled: zoom,
108
- start: zoomStart,
109
- end: zoomEnd,
110
- })
111
- }, [id, zoom, zoomStart, zoomEnd, setToolEnabled, updateToolConfig])
112
-
113
- // Initialize zoom state from defaults only if not already set
114
- useEffect(() => {
115
- const existingState = getWidget<ZoomState>(id)
116
-
117
- if (existingState?.zoom) return
118
-
119
- setWidget<ZoomState>(id, {
120
- zoom: existingState?.zoom ?? defaultZoom,
121
- zoomStart: existingState?.zoomStart ?? defaultZoomStart,
122
- zoomEnd: existingState?.zoomEnd ?? defaultZoomEnd,
123
- })
124
- }, [defaultZoom, defaultZoomEnd, defaultZoomStart, getWidget, id, setWidget])
125
-
126
- // Cleanup: disable zoom when component unmounts
127
- useEffect(() => {
128
- return () => {
129
- setWidget<ZoomState>(id, { zoom: false })
130
- }
131
- }, [id, setWidget])
172
+ }, [
173
+ defaultZoom,
174
+ defaultZoomEnd,
175
+ defaultZoomStart,
176
+ getWidget,
177
+ handleDataZoom,
178
+ id,
179
+ registerTool,
180
+ theme,
181
+ unregisterTool,
182
+ ])
132
183
 
133
184
  const handleToggle = () => {
134
185
  const newZoom = !zoom
135
- const existingState = getWidget<ZoomState>(id)
136
-
137
- setWidget<ZoomState>(id, {
138
- zoom: newZoom,
139
- zoomStart: newZoom ? (existingState?.zoomStart ?? defaultZoomStart) : 0,
140
- zoomEnd: newZoom ? (existingState?.zoomEnd ?? defaultZoomEnd) : 100,
186
+ setToolEnabled(id, ZOOM_TOGGLE_TOOL_ID, newZoom)
187
+ updateToolConfig(id, ZOOM_TOGGLE_TOOL_ID, {
188
+ enabled: newZoom,
189
+ start: newZoom ? zoomStart : 0,
190
+ end: newZoom ? zoomEnd : 100,
141
191
  })
142
192
  }
143
193
 
144
194
  const handleReset = () => {
145
- setWidget<ZoomState>(id, {
146
- zoom: true,
147
- zoomStart: defaultZoomStart,
148
- zoomEnd: defaultZoomEnd,
195
+ setToolEnabled(id, ZOOM_TOGGLE_TOOL_ID, true)
196
+ updateToolConfig(id, ZOOM_TOGGLE_TOOL_ID, {
197
+ enabled: true,
198
+ start: defaultZoomStart,
199
+ end: defaultZoomEnd,
149
200
  })
150
201
  }
151
202
 
152
- // Handle dataZoom event to update zoom range in store
153
- const handleDataZoom = useCallback(
154
- (event: unknown) => {
155
- const zoomEvent = event as {
156
- start: number
157
- end: number
158
- }
159
-
160
- const start = zoomEvent.start
161
- const end = zoomEvent.end
162
-
163
- if (start !== undefined && end !== undefined) {
164
- setWidget<ZoomState>(id, {
165
- zoom: true,
166
- zoomStart: start,
167
- zoomEnd: end,
168
- })
169
- }
170
- },
171
- [id, setWidget],
172
- )
173
-
174
- // Register dataZoom event handler when zoom is enabled
175
- useEffect(() => {
176
- if (zoom) {
177
- setWidget<EchartWidgetState>(id, {
178
- onEvents: {
179
- dataZoom: handleDataZoom,
180
- },
181
- })
182
- }
183
- }, [id, zoom, handleDataZoom, setWidget])
184
-
185
203
  const enableLabel = labels?.enable ?? 'Enable zoom'
186
204
  const disableLabel = labels?.disable ?? 'Disable zoom'
187
205
  const resetLabel = labels?.reset ?? 'Reset zoom'
@@ -10,7 +10,7 @@ import {
10
10
  buildGridConfig,
11
11
  createTooltipPositioner,
12
12
  createTooltipFormatter,
13
- applyYAxisFormatter,
13
+ niceNum,
14
14
  } from '../_shared/chart-config'
15
15
  import { downloadToCSV, downloadToPNG, type DownloadItem } from '../actions'
16
16
  import type { ConfigProps } from '../loader/types'
@@ -47,32 +47,7 @@ function getOption({
47
47
  }: BarConfig): EchartOptionsProps {
48
48
  const hasLegend = (data?.length ?? 0) > 1
49
49
 
50
- const yAxis = {
51
- type: 'value' as const,
52
- splitNumber: 1,
53
- axisLabel: {
54
- fontSize: theme.typography.overlineDelicate.fontSize,
55
- fontFamily: theme.typography.overlineDelicate.fontFamily,
56
- margin: parseInt(theme.spacing(1)),
57
- show: true,
58
- showMaxLabel: true,
59
- showMinLabel: false,
60
- verticalAlign: 'bottom' as const,
61
- inside: true,
62
- },
63
- axisLine: {
64
- show: false,
65
- },
66
- axisTick: {
67
- show: false,
68
- },
69
- splitLine: {
70
- show: true,
71
- lineStyle: {
72
- color: theme.palette.black[4],
73
- },
74
- },
75
- }
50
+ let niceMax = 1
76
51
 
77
52
  return {
78
53
  legend: buildLegendConfig(hasLegend),
@@ -90,7 +65,41 @@ function getOption({
90
65
  margin: 0,
91
66
  },
92
67
  },
93
- yAxis: applyYAxisFormatter(yAxis, formatter),
68
+ yAxis: {
69
+ type: 'value' as const,
70
+ min: 0,
71
+ max: (extent: { min: number; max: number }) => {
72
+ niceMax = extent.max <= 0 ? 1 : niceNum(extent.max)
73
+ return niceMax
74
+ },
75
+ splitNumber: 1,
76
+ axisLabel: {
77
+ fontSize: theme.typography.overlineDelicate.fontSize,
78
+ fontFamily: theme.typography.overlineDelicate.fontFamily,
79
+ margin: parseInt(theme.spacing(1)),
80
+ show: true,
81
+ showMaxLabel: true,
82
+ showMinLabel: false,
83
+ verticalAlign: 'bottom' as const,
84
+ inside: true,
85
+ formatter: (value: number) => {
86
+ if (value !== niceMax) return ''
87
+ return formatter ? formatter(value) : String(value)
88
+ },
89
+ },
90
+ axisLine: {
91
+ show: false,
92
+ },
93
+ axisTick: {
94
+ show: false,
95
+ },
96
+ splitLine: {
97
+ show: true,
98
+ lineStyle: {
99
+ color: theme.palette.black[4],
100
+ },
101
+ },
102
+ },
94
103
  tooltip: {
95
104
  position: createTooltipPositioner(theme),
96
105
  formatter: createTooltipFormatter((item) => {
@@ -15,30 +15,33 @@ const defaultFormatter = (value: number) => value.toString()
15
15
 
16
16
  export function CategoryUI({ id }: CategoryUIProps) {
17
17
  const theme = useTheme()
18
- const widget = useWidgetStore(
19
- useShallow((state) => {
20
- const widget = state.getWidget<CategoryWidgetState>(id)
21
- return {
22
- formatter: widget?.formatter,
23
- series: widget?.series,
24
- data: widget?.data,
25
- maxItems: widget?.maxItems,
26
- labels: widget?.labels,
27
- onRowClick: widget?.onRowClick,
28
- selected: widget?.selected,
29
- max: widget?.max,
30
- }
31
- }),
18
+ const _formatter = useWidgetStore(
19
+ useShallow((state) => state.getWidget<CategoryWidgetState>(id)?.formatter),
20
+ )
21
+ const _series = useWidgetStore(
22
+ useShallow((state) => state.getWidget<CategoryWidgetState>(id)?.series),
23
+ )
24
+ const data = useWidgetStore(
25
+ useShallow((state) => state.getWidget<CategoryWidgetState>(id)?.data),
26
+ )
27
+ const maxItems = useWidgetStore(
28
+ useShallow((state) => state.getWidget<CategoryWidgetState>(id)?.maxItems),
29
+ )
30
+ const labels = useWidgetStore(
31
+ useShallow((state) => state.getWidget<CategoryWidgetState>(id)?.labels),
32
+ )
33
+ const onRowClick = useWidgetStore(
34
+ useShallow((state) => state.getWidget<CategoryWidgetState>(id)?.onRowClick),
35
+ )
36
+ const selected = useWidgetStore(
37
+ useShallow((state) => state.getWidget<CategoryWidgetState>(id)?.selected),
38
+ )
39
+ const max = useWidgetStore(
40
+ useShallow((state) => state.getWidget<CategoryWidgetState>(id)?.max),
32
41
  )
33
42
 
34
- const formatter = widget?.formatter ?? defaultFormatter
35
- const series = widget?.series ?? []
36
- const data = widget?.data
37
- const maxItems = widget?.maxItems
38
- const labels = widget?.labels
39
- const onRowClick = widget?.onRowClick
40
- const selected = widget?.selected
41
- const max = widget?.max
43
+ const formatter = _formatter ?? defaultFormatter
44
+ const series = _series ?? []
42
45
 
43
46
  const [maxHeight] = useState<string | number | undefined>(
44
47
  maxItems ? 40 * (series.length || 1) * maxItems : undefined,
@@ -175,7 +175,7 @@ describe('EchartUI', () => {
175
175
 
176
176
  expect(mockChart.setOption).toHaveBeenCalledWith(basicOption, {
177
177
  lazyUpdate: true,
178
- replaceMerge: ['dataset', 'series'],
178
+ notMerge: true,
179
179
  })
180
180
  })
181
181
 
@@ -200,22 +200,7 @@ describe('EchartUI', () => {
200
200
 
201
201
  expect(mockChart.setOption).toHaveBeenCalledWith(newOption, {
202
202
  lazyUpdate: true,
203
- replaceMerge: ['dataset', 'series'],
204
- })
205
- })
206
-
207
- test('uses provided replaceMerge values', () => {
208
- render(
209
- <EchartUI
210
- {...defaultProps}
211
- option={basicOption}
212
- replaceMerge={['series']}
213
- />,
214
- )
215
-
216
- expect(mockChart.setOption).toHaveBeenCalledWith(basicOption, {
217
- lazyUpdate: true,
218
- replaceMerge: ['series'],
203
+ notMerge: true,
219
204
  })
220
205
  })
221
206
 
@@ -496,7 +481,7 @@ describe('EchartUI', () => {
496
481
 
497
482
  expect(mockChart.setOption).toHaveBeenCalledWith(complexOption, {
498
483
  lazyUpdate: true,
499
- replaceMerge: ['dataset', 'series'],
484
+ notMerge: true,
500
485
  })
501
486
  })
502
487