@carto/ps-react-ui 4.4.2 → 4.5.0
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.
- package/dist/download-config-DemuQ3Jm.js +56 -0
- package/dist/download-config-DemuQ3Jm.js.map +1 -0
- package/dist/error-Cj8eUMrl.js +40 -0
- package/dist/error-Cj8eUMrl.js.map +1 -0
- package/dist/formatter-B9Bxn1k7.js +6 -0
- package/dist/formatter-B9Bxn1k7.js.map +1 -0
- package/dist/no-data-DkIt7Qt1.js +61 -0
- package/dist/no-data-DkIt7Qt1.js.map +1 -0
- package/dist/row-D4VOhcNI.js +34 -0
- package/dist/row-D4VOhcNI.js.map +1 -0
- package/dist/series-Bola3CmD.js +90 -0
- package/dist/series-Bola3CmD.js.map +1 -0
- package/dist/styles-Y8q7Jff3.js +118 -0
- package/dist/styles-Y8q7Jff3.js.map +1 -0
- package/dist/types/widgets/actions/brush-toggle/types.d.ts +8 -2
- package/dist/types/widgets/category/components/category-row-multi.d.ts +2 -1
- package/dist/types/widgets/category/components/category-row-single.d.ts +2 -1
- package/dist/types/widgets/category/types.d.ts +1 -0
- package/dist/types/widgets/echart/shared-resize-observer.d.ts +12 -0
- package/dist/types/widgets/echart/types.d.ts +2 -0
- package/dist/types/widgets/histogram/config.d.ts +15 -3
- package/dist/types/widgets/histogram/index.d.ts +2 -1
- package/dist/types/widgets/histogram/types.d.ts +6 -3
- package/dist/types/widgets/stores/index.d.ts +2 -1
- package/dist/types/widgets/stores/types.d.ts +2 -0
- package/dist/types/widgets/stores/use-widget-selector.d.ts +35 -0
- package/dist/types/widgets/stores/widget-store-performance.test.d.ts +1 -0
- package/dist/types/widgets/stores/widget-store.d.ts +49 -27
- package/dist/types/widgets/table/types.d.ts +1 -1
- package/dist/types/widgets/utils/chart-config/index.d.ts +1 -1
- package/dist/types/widgets/utils/chart-config/option-builders.d.ts +13 -8
- package/dist/types/widgets/utils/formatter.d.ts +1 -0
- package/dist/types/widgets/utils/index.d.ts +1 -1
- package/dist/use-widget-ref-BFazQvJK.js +22 -0
- package/dist/use-widget-ref-BFazQvJK.js.map +1 -0
- package/dist/use-widget-selector-DqRmWQ1K.js +12 -0
- package/dist/use-widget-selector-DqRmWQ1K.js.map +1 -0
- package/dist/widget-store-CIrb9RKP.js +263 -0
- package/dist/widget-store-CIrb9RKP.js.map +1 -0
- package/dist/widgets/actions.js +799 -817
- package/dist/widgets/actions.js.map +1 -1
- package/dist/widgets/bar.js +53 -47
- package/dist/widgets/bar.js.map +1 -1
- package/dist/widgets/category.js +261 -255
- package/dist/widgets/category.js.map +1 -1
- package/dist/widgets/echart.js +109 -99
- package/dist/widgets/echart.js.map +1 -1
- package/dist/widgets/error.js +1 -1
- package/dist/widgets/formula.js +71 -63
- package/dist/widgets/formula.js.map +1 -1
- package/dist/widgets/histogram.js +119 -80
- package/dist/widgets/histogram.js.map +1 -1
- package/dist/widgets/loader.js +53 -60
- package/dist/widgets/loader.js.map +1 -1
- package/dist/widgets/markdown.js +51 -50
- package/dist/widgets/markdown.js.map +1 -1
- package/dist/widgets/no-data.js +1 -1
- package/dist/widgets/pie.js +111 -99
- package/dist/widgets/pie.js.map +1 -1
- package/dist/widgets/range.js +146 -144
- package/dist/widgets/range.js.map +1 -1
- package/dist/widgets/scatterplot.js +50 -44
- package/dist/widgets/scatterplot.js.map +1 -1
- package/dist/widgets/skeleton-loader.js +18 -17
- package/dist/widgets/skeleton-loader.js.map +1 -1
- package/dist/widgets/spread.js +110 -94
- package/dist/widgets/spread.js.map +1 -1
- package/dist/widgets/stores.js +5 -2
- package/dist/widgets/stores.js.map +1 -1
- package/dist/widgets/table.js +422 -436
- package/dist/widgets/table.js.map +1 -1
- package/dist/widgets/timeseries.js +52 -46
- package/dist/widgets/timeseries.js.map +1 -1
- package/dist/widgets/toolbar-actions.js +101 -6693
- package/dist/widgets/toolbar-actions.js.map +1 -1
- package/dist/widgets/utils.js +16 -14
- package/dist/widgets/utils.js.map +1 -1
- package/dist/widgets/wrapper.js +156 -158
- package/dist/widgets/wrapper.js.map +1 -1
- package/dist/widgets.js +4 -4
- package/package.json +5 -4
- package/src/hooks/use-widget-ref.ts +3 -4
- package/src/widgets/README.md +3 -3
- package/src/widgets/actions/brush-toggle/brush-toggle.tsx +60 -79
- package/src/widgets/actions/brush-toggle/types.ts +8 -2
- package/src/widgets/actions/change-column/change-column.tsx +15 -15
- package/src/widgets/actions/change-column/sortable-column-item.tsx +3 -1
- package/src/widgets/actions/download/download.tsx +4 -3
- package/src/widgets/actions/fullscreen/fullscreen.tsx +7 -11
- package/src/widgets/actions/lock-selection/lock-selection.tsx +12 -15
- package/src/widgets/actions/relative-data/relative-data.tsx +22 -26
- package/src/widgets/actions/searcher/searcher-toggle.tsx +11 -12
- package/src/widgets/actions/searcher/searcher.tsx +20 -21
- package/src/widgets/actions/stack-toggle/stack-toggle.tsx +15 -21
- package/src/widgets/actions/zoom-toggle/zoom-toggle.tsx +27 -43
- package/src/widgets/bar/config.ts +22 -14
- package/src/widgets/category/category-ui.tsx +31 -27
- package/src/widgets/category/components/category-row-multi.tsx +6 -2
- package/src/widgets/category/components/category-row-single.tsx +5 -1
- package/src/widgets/category/types.ts +1 -0
- package/src/widgets/echart/echart-ui.test.tsx +20 -16
- package/src/widgets/echart/echart-ui.tsx +6 -12
- package/src/widgets/echart/echart.tsx +13 -27
- package/src/widgets/echart/shared-resize-observer.ts +45 -0
- package/src/widgets/echart/types.ts +2 -0
- package/src/widgets/error/error.tsx +7 -9
- package/src/widgets/formula/components/prefix.tsx +4 -6
- package/src/widgets/formula/components/row.tsx +4 -4
- package/src/widgets/formula/components/series.tsx +4 -6
- package/src/widgets/formula/components/suffix.tsx +4 -6
- package/src/widgets/formula/components/value.tsx +9 -16
- package/src/widgets/histogram/config.ts +101 -20
- package/src/widgets/histogram/index.ts +6 -1
- package/src/widgets/histogram/types.ts +9 -3
- package/src/widgets/loader/loader.tsx +31 -44
- package/src/widgets/markdown/markdown.tsx +4 -7
- package/src/widgets/no-data/no-data.tsx +7 -10
- package/src/widgets/pie/config.ts +17 -5
- package/src/widgets/range/components/range-item.tsx +20 -18
- package/src/widgets/scatterplot/config.ts +8 -3
- package/src/widgets/skeleton-loader/skeleton-loader.tsx +2 -5
- package/src/widgets/spread/components/max-value.tsx +14 -16
- package/src/widgets/spread/components/min-value.tsx +14 -16
- package/src/widgets/stores/index.ts +2 -1
- package/src/widgets/stores/types.ts +2 -0
- package/src/widgets/stores/use-widget-selector.ts +47 -0
- package/src/widgets/stores/widget-store-performance.test.ts +750 -0
- package/src/widgets/stores/widget-store.test.ts +81 -0
- package/src/widgets/stores/widget-store.ts +225 -44
- package/src/widgets/table/config.ts +0 -1
- package/src/widgets/table/hooks/use-pagination.ts +28 -52
- package/src/widgets/table/hooks/use-selection.ts +20 -24
- package/src/widgets/table/hooks/use-sort.ts +22 -39
- package/src/widgets/table/types.ts +1 -1
- package/src/widgets/timeseries/config.ts +21 -13
- package/src/widgets/utils/chart-config/index.ts +1 -1
- package/src/widgets/utils/chart-config/option-builders.ts +22 -12
- package/src/widgets/utils/formatter.ts +2 -1
- package/src/widgets/utils/index.ts +1 -1
- package/src/widgets/wrapper/wrapper-ui.tsx +12 -13
- package/src/widgets/wrapper/wrapper.tsx +4 -6
- package/dist/error-CEkRPccv.js +0 -39
- package/dist/error-CEkRPccv.js.map +0 -1
- package/dist/formatter-B1Xh8XDH.js +0 -5
- package/dist/formatter-B1Xh8XDH.js.map +0 -1
- package/dist/no-data-hR3KcJ-_.js +0 -60
- package/dist/no-data-hR3KcJ-_.js.map +0 -1
- package/dist/row-DTCV0Ocm.js +0 -35
- package/dist/row-DTCV0Ocm.js.map +0 -1
- package/dist/series-CYNOu2Ju.js +0 -91
- package/dist/series-CYNOu2Ju.js.map +0 -1
- package/dist/styles-C_8vOEep.js +0 -167
- package/dist/styles-C_8vOEep.js.map +0 -1
- package/dist/use-widget-ref-wtFLDFCD.js +0 -25
- package/dist/use-widget-ref-wtFLDFCD.js.map +0 -1
- package/dist/widget-store-CzDt8oSK.js +0 -163
- package/dist/widget-store-CzDt8oSK.js.map +0 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { TextField, InputAdornment, IconButton } from '@mui/material'
|
|
2
2
|
import { ClearOutlined, SearchOutlined } from '@mui/icons-material'
|
|
3
3
|
import { useEffect, useRef, useCallback } from 'react'
|
|
4
|
-
import {
|
|
4
|
+
import { widgetStoreActions } from '../../stores/widget-store'
|
|
5
|
+
import { useWidgetSelector } from '../../stores/use-widget-selector'
|
|
5
6
|
import type { SearcherProps, SearcherFilterFn, SearcherState } from './types'
|
|
6
7
|
import type { EchartWidgetData } from '../../echart/types'
|
|
7
8
|
import { LOCK_SELECTION_TOOL_ID } from '../lock-selection/lock-selection'
|
|
@@ -41,30 +42,24 @@ export function Searcher({
|
|
|
41
42
|
const debounceTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
42
43
|
|
|
43
44
|
// Read enabled state and search text from widget store
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const searchText = widgetState?.searchText ?? ''
|
|
45
|
+
const { enabled, searchText } = useWidgetSelector(id, (w) => ({
|
|
46
|
+
enabled: (w as SearcherState | undefined)?.isSearchEnabled ?? false,
|
|
47
|
+
searchText: (w as SearcherState | undefined)?.searchText ?? '',
|
|
48
|
+
}))
|
|
49
49
|
const prevEnabledRef = useRef(enabled)
|
|
50
50
|
|
|
51
51
|
const filter = filterFn ?? defaultFilterFn
|
|
52
52
|
|
|
53
|
-
const setWidget = useWidgetStore((state) => state.setWidget)
|
|
54
|
-
const registerTool = useWidgetStore((state) => state.registerTool)
|
|
55
|
-
const unregisterTool = useWidgetStore((state) => state.unregisterTool)
|
|
56
|
-
const updateToolConfig = useWidgetStore((state) => state.updateToolConfig)
|
|
57
|
-
|
|
58
53
|
const setSearchText = useCallback(
|
|
59
54
|
(text: string) => {
|
|
60
|
-
setWidget(id, { searchText: text })
|
|
55
|
+
widgetStoreActions.setWidget(id, { searchText: text })
|
|
61
56
|
},
|
|
62
|
-
[id
|
|
57
|
+
[id],
|
|
63
58
|
)
|
|
64
59
|
|
|
65
|
-
// Register tool
|
|
60
|
+
// Register tool with all reactive deps — store's no-op detection handles performance
|
|
66
61
|
useEffect(() => {
|
|
67
|
-
registerTool(id, {
|
|
62
|
+
widgetStoreActions.registerTool(id, {
|
|
68
63
|
id: SEARCHER_TOOL_ID,
|
|
69
64
|
order,
|
|
70
65
|
enabled,
|
|
@@ -81,8 +76,8 @@ export function Searcher({
|
|
|
81
76
|
disables: [LOCK_SELECTION_TOOL_ID],
|
|
82
77
|
})
|
|
83
78
|
|
|
84
|
-
return () => unregisterTool(id, SEARCHER_TOOL_ID)
|
|
85
|
-
}, [id, order,
|
|
79
|
+
return () => widgetStoreActions.unregisterTool(id, SEARCHER_TOOL_ID)
|
|
80
|
+
}, [id, order, enabled, searchText, filter])
|
|
86
81
|
|
|
87
82
|
// Update config when search text changes (debounced)
|
|
88
83
|
const debouncedUpdateConfig = useCallback(
|
|
@@ -91,10 +86,12 @@ export function Searcher({
|
|
|
91
86
|
clearTimeout(debounceTimeoutRef.current)
|
|
92
87
|
}
|
|
93
88
|
debounceTimeoutRef.current = setTimeout(() => {
|
|
94
|
-
updateToolConfig(id, SEARCHER_TOOL_ID, {
|
|
89
|
+
widgetStoreActions.updateToolConfig(id, SEARCHER_TOOL_ID, {
|
|
90
|
+
searchText: text,
|
|
91
|
+
})
|
|
95
92
|
}, debounceDelay)
|
|
96
93
|
},
|
|
97
|
-
[id,
|
|
94
|
+
[id, debounceDelay],
|
|
98
95
|
)
|
|
99
96
|
|
|
100
97
|
// Auto-focus when enabled becomes true
|
|
@@ -127,11 +124,13 @@ export function Searcher({
|
|
|
127
124
|
|
|
128
125
|
const handleClear = useCallback(() => {
|
|
129
126
|
setSearchText('')
|
|
130
|
-
updateToolConfig(id, SEARCHER_TOOL_ID, {
|
|
127
|
+
widgetStoreActions.updateToolConfig(id, SEARCHER_TOOL_ID, {
|
|
128
|
+
searchText: '',
|
|
129
|
+
})
|
|
131
130
|
if (inputRef.current) {
|
|
132
131
|
inputRef.current.focus()
|
|
133
132
|
}
|
|
134
|
-
}, [id, setSearchText
|
|
133
|
+
}, [id, setSearchText])
|
|
135
134
|
|
|
136
135
|
if (!enabled) {
|
|
137
136
|
return null
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { IconButton } from '@mui/material'
|
|
2
2
|
import { useCallback, useEffect, useMemo } from 'react'
|
|
3
|
-
import {
|
|
3
|
+
import { widgetStoreActions } from '../../stores/widget-store'
|
|
4
4
|
import type { StackToggleProps, StackToggleState } from './types'
|
|
5
5
|
import { actionButtonStyles } from '../shared/styles'
|
|
6
6
|
import { Tooltip } from '../../../components'
|
|
@@ -9,7 +9,7 @@ import { getEChartStackConfig } from '../../echart/utils'
|
|
|
9
9
|
import { DEFAULT_STACK_GROUP } from '../../echart/const'
|
|
10
10
|
import type { EchartWidgetState } from '../../echart/types'
|
|
11
11
|
import type { EchartOptionsProps } from '../../echart/types'
|
|
12
|
-
import {
|
|
12
|
+
import { useWidgetSelector } from '../../stores/use-widget-selector'
|
|
13
13
|
|
|
14
14
|
export const STACK_TOGGLE_TOOL_ID = 'stack-toggle'
|
|
15
15
|
|
|
@@ -34,16 +34,10 @@ export function StackToggle({
|
|
|
34
34
|
Icon,
|
|
35
35
|
IconButtonProps,
|
|
36
36
|
}: StackToggleProps) {
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
useShallow((state) => state.getWidget<StackToggleState>(id)?.isStacked),
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
const option = useWidgetStore(
|
|
45
|
-
useShallow((state) => state.getWidget<EchartWidgetState>(id)?.option),
|
|
46
|
-
)
|
|
37
|
+
const { storeIsStacked, option } = useWidgetSelector(id, (w) => ({
|
|
38
|
+
storeIsStacked: (w as StackToggleState | undefined)?.isStacked,
|
|
39
|
+
option: (w as EchartWidgetState | undefined)?.option,
|
|
40
|
+
}))
|
|
47
41
|
|
|
48
42
|
const { hasMultiSeries, hasStackInSeries } = useMemo(() => {
|
|
49
43
|
if (!option) return { hasMultiSeries: false, hasStackInSeries: false }
|
|
@@ -60,14 +54,14 @@ export function StackToggle({
|
|
|
60
54
|
const effectiveDefaultIsStacked = hasStackInSeries || defaultIsStacked
|
|
61
55
|
const isStacked = storeIsStacked ?? effectiveDefaultIsStacked
|
|
62
56
|
|
|
63
|
-
// Register config tool
|
|
57
|
+
// Register config tool with all reactive deps — store's no-op detection handles performance
|
|
64
58
|
useEffect(() => {
|
|
65
|
-
registerTool(id, {
|
|
59
|
+
widgetStoreActions.registerTool(id, {
|
|
66
60
|
id: STACK_TOGGLE_TOOL_ID,
|
|
67
61
|
type: 'config',
|
|
68
62
|
order: 10,
|
|
69
63
|
enabled: isStacked && hasMultiSeries,
|
|
70
|
-
fn: (currentConfig) => {
|
|
64
|
+
fn: (currentConfig: unknown) => {
|
|
71
65
|
const config = currentConfig as Record<string, unknown>
|
|
72
66
|
const option = config.option as EchartOptionsProps | undefined
|
|
73
67
|
if (!option) return currentConfig
|
|
@@ -87,18 +81,18 @@ export function StackToggle({
|
|
|
87
81
|
return { ...config, option: { ...option, series: updatedSeries } }
|
|
88
82
|
},
|
|
89
83
|
})
|
|
90
|
-
return () => unregisterTool(id, STACK_TOGGLE_TOOL_ID)
|
|
91
|
-
}, [id,
|
|
84
|
+
return () => widgetStoreActions.unregisterTool(id, STACK_TOGGLE_TOOL_ID)
|
|
85
|
+
}, [id, isStacked, hasMultiSeries])
|
|
92
86
|
|
|
93
87
|
// Initialize store with default value only if not already configured
|
|
94
88
|
useEffect(() => {
|
|
95
89
|
if (storeIsStacked !== undefined) return
|
|
96
|
-
setWidget(id, { isStacked: effectiveDefaultIsStacked })
|
|
97
|
-
}, [effectiveDefaultIsStacked, id,
|
|
90
|
+
widgetStoreActions.setWidget(id, { isStacked: effectiveDefaultIsStacked })
|
|
91
|
+
}, [effectiveDefaultIsStacked, id, storeIsStacked])
|
|
98
92
|
|
|
99
93
|
const handleToggle = useCallback(() => {
|
|
100
|
-
setWidget(id, { isStacked: !isStacked })
|
|
101
|
-
}, [isStacked, id
|
|
94
|
+
widgetStoreActions.setWidget(id, { isStacked: !isStacked })
|
|
95
|
+
}, [isStacked, id])
|
|
102
96
|
|
|
103
97
|
const tooltipLabel = isStacked
|
|
104
98
|
? (labels?.unstacked ?? 'Disable stacking')
|
|
@@ -4,13 +4,13 @@ import {
|
|
|
4
4
|
ZoomInOutlined,
|
|
5
5
|
} from '@mui/icons-material'
|
|
6
6
|
import { useEffect, useCallback } from 'react'
|
|
7
|
-
import {
|
|
7
|
+
import { widgetStoreActions } from '../../stores/widget-store'
|
|
8
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
12
|
import type { EchartOptionsProps } from '../../echart/types'
|
|
13
|
-
import {
|
|
13
|
+
import { useWidgetSelector } from '../../stores/use-widget-selector'
|
|
14
14
|
|
|
15
15
|
export const ZOOM_TOGGLE_TOOL_ID = 'zoom-toggle'
|
|
16
16
|
|
|
@@ -44,24 +44,18 @@ export function ZoomToggle({
|
|
|
44
44
|
IconButtonProps,
|
|
45
45
|
}: ZoomToggleProps) {
|
|
46
46
|
const theme = useTheme()
|
|
47
|
-
const getWidget = useWidgetStore((state) => state.getWidget)
|
|
48
|
-
const registerTool = useWidgetStore((state) => state.registerTool)
|
|
49
|
-
const unregisterTool = useWidgetStore((state) => state.unregisterTool)
|
|
50
|
-
const setToolEnabled = useWidgetStore((state) => state.setToolEnabled)
|
|
51
|
-
const updateToolConfig = useWidgetStore((state) => state.updateToolConfig)
|
|
52
|
-
|
|
53
|
-
const zoomTool = useWidgetStore(
|
|
54
|
-
useShallow((state) => {
|
|
55
|
-
const tools = state.getWidget(id)?.registeredTools ?? []
|
|
56
|
-
return tools.find((tool) => tool.id === ZOOM_TOGGLE_TOOL_ID)
|
|
57
|
-
}),
|
|
58
|
-
)
|
|
59
47
|
|
|
60
|
-
const zoom =
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
48
|
+
const { zoom, zoomStart, zoomEnd } = useWidgetSelector(id, (w) => {
|
|
49
|
+
const zoomTool = w?.registeredTools?.find(
|
|
50
|
+
(tool) => tool.id === ZOOM_TOGGLE_TOOL_ID,
|
|
51
|
+
)
|
|
52
|
+
return {
|
|
53
|
+
zoom: zoomTool?.enabled ?? defaultZoom,
|
|
54
|
+
zoomStart:
|
|
55
|
+
(zoomTool?.config?.start as number | undefined) ?? defaultZoomStart,
|
|
56
|
+
zoomEnd: (zoomTool?.config?.end as number | undefined) ?? defaultZoomEnd,
|
|
57
|
+
}
|
|
58
|
+
})
|
|
65
59
|
|
|
66
60
|
// Handle dataZoom event to update zoom range in tool config
|
|
67
61
|
const handleDataZoom = useCallback(
|
|
@@ -79,21 +73,21 @@ export function ZoomToggle({
|
|
|
79
73
|
const end = zoomEvent.end ?? zoomEvent.batch?.[0]?.end
|
|
80
74
|
|
|
81
75
|
if (start !== undefined && end !== undefined) {
|
|
82
|
-
setToolEnabled(id, ZOOM_TOGGLE_TOOL_ID, true)
|
|
83
|
-
updateToolConfig(id, ZOOM_TOGGLE_TOOL_ID, {
|
|
76
|
+
widgetStoreActions.setToolEnabled(id, ZOOM_TOGGLE_TOOL_ID, true)
|
|
77
|
+
widgetStoreActions.updateToolConfig(id, ZOOM_TOGGLE_TOOL_ID, {
|
|
84
78
|
start,
|
|
85
79
|
end,
|
|
86
80
|
})
|
|
87
81
|
}
|
|
88
82
|
},
|
|
89
|
-
[id
|
|
83
|
+
[id],
|
|
90
84
|
)
|
|
91
85
|
|
|
92
|
-
// Register config tool
|
|
86
|
+
// Register config tool with all reactive deps — store's no-op detection handles performance
|
|
93
87
|
useEffect(() => {
|
|
94
|
-
const existingTool =
|
|
95
|
-
(
|
|
96
|
-
|
|
88
|
+
const existingTool = widgetStoreActions
|
|
89
|
+
.getWidget(id)
|
|
90
|
+
?.registeredTools?.find((tool) => tool.id === ZOOM_TOGGLE_TOOL_ID)
|
|
97
91
|
|
|
98
92
|
const initialEnabled = existingTool?.enabled ?? defaultZoom
|
|
99
93
|
const initialStart =
|
|
@@ -101,7 +95,7 @@ export function ZoomToggle({
|
|
|
101
95
|
const initialEnd =
|
|
102
96
|
(existingTool?.config?.end as number | undefined) ?? defaultZoomEnd
|
|
103
97
|
|
|
104
|
-
registerTool(id, {
|
|
98
|
+
widgetStoreActions.registerTool(id, {
|
|
105
99
|
id: ZOOM_TOGGLE_TOOL_ID,
|
|
106
100
|
type: 'config',
|
|
107
101
|
order: 20,
|
|
@@ -160,31 +154,21 @@ export function ZoomToggle({
|
|
|
160
154
|
end: initialEnd,
|
|
161
155
|
},
|
|
162
156
|
})
|
|
163
|
-
return () => unregisterTool(id, ZOOM_TOGGLE_TOOL_ID)
|
|
164
|
-
}, [
|
|
165
|
-
defaultZoom,
|
|
166
|
-
defaultZoomEnd,
|
|
167
|
-
defaultZoomStart,
|
|
168
|
-
getWidget,
|
|
169
|
-
handleDataZoom,
|
|
170
|
-
id,
|
|
171
|
-
registerTool,
|
|
172
|
-
theme,
|
|
173
|
-
unregisterTool,
|
|
174
|
-
])
|
|
157
|
+
return () => widgetStoreActions.unregisterTool(id, ZOOM_TOGGLE_TOOL_ID)
|
|
158
|
+
}, [id, theme, handleDataZoom, defaultZoom, defaultZoomStart, defaultZoomEnd])
|
|
175
159
|
|
|
176
160
|
const handleToggle = () => {
|
|
177
161
|
const newZoom = !zoom
|
|
178
|
-
setToolEnabled(id, ZOOM_TOGGLE_TOOL_ID, newZoom)
|
|
179
|
-
updateToolConfig(id, ZOOM_TOGGLE_TOOL_ID, {
|
|
162
|
+
widgetStoreActions.setToolEnabled(id, ZOOM_TOGGLE_TOOL_ID, newZoom)
|
|
163
|
+
widgetStoreActions.updateToolConfig(id, ZOOM_TOGGLE_TOOL_ID, {
|
|
180
164
|
start: newZoom ? zoomStart : 0,
|
|
181
165
|
end: newZoom ? zoomEnd : 100,
|
|
182
166
|
})
|
|
183
167
|
}
|
|
184
168
|
|
|
185
169
|
const handleReset = () => {
|
|
186
|
-
setToolEnabled(id, ZOOM_TOGGLE_TOOL_ID, true)
|
|
187
|
-
updateToolConfig(id, ZOOM_TOGGLE_TOOL_ID, {
|
|
170
|
+
widgetStoreActions.setToolEnabled(id, ZOOM_TOGGLE_TOOL_ID, true)
|
|
171
|
+
widgetStoreActions.updateToolConfig(id, ZOOM_TOGGLE_TOOL_ID, {
|
|
188
172
|
start: defaultZoomStart,
|
|
189
173
|
end: defaultZoomEnd,
|
|
190
174
|
})
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
createTooltipPositioner,
|
|
12
12
|
createTooltipFormatter,
|
|
13
13
|
createChartDownloadConfig,
|
|
14
|
+
applyXAxisFormatter,
|
|
14
15
|
niceNum,
|
|
15
16
|
} from '../utils/chart-config'
|
|
16
17
|
|
|
@@ -29,6 +30,7 @@ export function barConfig(props: BarConfig): BarWidgetConfig {
|
|
|
29
30
|
type: 'bar',
|
|
30
31
|
option: mergeEchartWidgetConfig(getCommonOptions(props), getOption(props)),
|
|
31
32
|
formatter: props.formatter,
|
|
33
|
+
labelFormatter: props.labelFormatter,
|
|
32
34
|
}
|
|
33
35
|
}
|
|
34
36
|
|
|
@@ -36,6 +38,7 @@ function getOption({
|
|
|
36
38
|
data = [],
|
|
37
39
|
theme,
|
|
38
40
|
formatter,
|
|
41
|
+
labelFormatter,
|
|
39
42
|
}: BarConfig): EchartOptionsProps {
|
|
40
43
|
const hasLegend = (data?.length ?? 0) > 1
|
|
41
44
|
|
|
@@ -43,21 +46,24 @@ function getOption({
|
|
|
43
46
|
let niceMax = 1
|
|
44
47
|
|
|
45
48
|
return {
|
|
46
|
-
legend: buildLegendConfig(hasLegend),
|
|
49
|
+
legend: buildLegendConfig({ hasLegend, labelFormatter }),
|
|
47
50
|
grid: buildGridConfig(hasLegend, theme),
|
|
48
|
-
xAxis:
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
51
|
+
xAxis: applyXAxisFormatter(
|
|
52
|
+
{
|
|
53
|
+
type: 'category',
|
|
54
|
+
axisLine: {
|
|
55
|
+
show: false,
|
|
56
|
+
},
|
|
57
|
+
axisTick: {
|
|
58
|
+
show: false,
|
|
59
|
+
},
|
|
60
|
+
axisLabel: {
|
|
61
|
+
padding: [parseInt(theme.spacing(0.5)), 0, 0, 0],
|
|
62
|
+
margin: 0,
|
|
63
|
+
},
|
|
59
64
|
},
|
|
60
|
-
|
|
65
|
+
labelFormatter,
|
|
66
|
+
),
|
|
61
67
|
yAxis: {
|
|
62
68
|
type: 'value' as const,
|
|
63
69
|
min: (extent: { min: number }) => {
|
|
@@ -110,7 +116,9 @@ function getOption({
|
|
|
110
116
|
|
|
111
117
|
const marker = typeof item.marker === 'string' ? item.marker : ''
|
|
112
118
|
const seriesName = item.seriesName ? `${item.seriesName}: ` : ''
|
|
113
|
-
const name =
|
|
119
|
+
const name = labelFormatter
|
|
120
|
+
? String(labelFormatter(item.name ?? ''))
|
|
121
|
+
: (item.name ?? '')
|
|
114
122
|
|
|
115
123
|
return { name, seriesName, marker, value: formattedValue }
|
|
116
124
|
}),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Box, useTheme } from '@mui/material'
|
|
2
|
-
import {
|
|
2
|
+
import { useWidgetSelector } from '../stores/use-widget-selector'
|
|
3
3
|
import { styles } from './style'
|
|
4
4
|
import type { CategoryUIProps, CategoryWidgetState } from './types'
|
|
5
5
|
import {
|
|
@@ -8,41 +8,43 @@ import {
|
|
|
8
8
|
CategoryRowOther,
|
|
9
9
|
CategoryLegend,
|
|
10
10
|
} from './components'
|
|
11
|
-
import { useShallow } from 'zustand/shallow'
|
|
12
11
|
import { useState } from 'react'
|
|
13
|
-
import { defaultFormatter } from '../utils/formatter'
|
|
12
|
+
import { defaultFormatter, defaultLabelFormatter } from '../utils/formatter'
|
|
14
13
|
|
|
15
14
|
/**
|
|
16
15
|
* Renders a category widget displaying horizontal bars for categorical data with support for single and multi-series layouts, selection, and overflow grouping.
|
|
17
16
|
*/
|
|
18
17
|
export function CategoryUI({ id }: CategoryUIProps) {
|
|
19
18
|
const theme = useTheme()
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
19
|
+
|
|
20
|
+
// Single consolidated subscription instead of 9 separate ones.
|
|
21
|
+
const {
|
|
22
|
+
_formatter,
|
|
23
|
+
_labelFormatter,
|
|
24
|
+
_series,
|
|
25
|
+
data,
|
|
26
|
+
maxItems,
|
|
27
|
+
labels,
|
|
28
|
+
onRowClick,
|
|
29
|
+
selected,
|
|
30
|
+
max,
|
|
31
|
+
} = useWidgetSelector(id, (w) => {
|
|
32
|
+
const cw = w as CategoryWidgetState | undefined
|
|
33
|
+
return {
|
|
34
|
+
_formatter: cw?.formatter,
|
|
35
|
+
_labelFormatter: cw?.labelFormatter,
|
|
36
|
+
_series: cw?.series,
|
|
37
|
+
data: cw?.data,
|
|
38
|
+
maxItems: cw?.maxItems,
|
|
39
|
+
labels: cw?.labels,
|
|
40
|
+
onRowClick: cw?.onRowClick,
|
|
41
|
+
selected: cw?.selected,
|
|
42
|
+
max: cw?.max,
|
|
43
|
+
}
|
|
44
|
+
})
|
|
44
45
|
|
|
45
46
|
const formatter = _formatter ?? defaultFormatter
|
|
47
|
+
const labelFormatter = _labelFormatter ?? defaultLabelFormatter
|
|
46
48
|
const series = _series ?? []
|
|
47
49
|
|
|
48
50
|
const [maxHeight] = useState<string | number | undefined>(
|
|
@@ -100,6 +102,7 @@ export function CategoryUI({ id }: CategoryUIProps) {
|
|
|
100
102
|
maxValue={maxValue}
|
|
101
103
|
colors={colors}
|
|
102
104
|
formatter={formatter}
|
|
105
|
+
labelFormatter={labelFormatter}
|
|
103
106
|
onClick={onRowClick}
|
|
104
107
|
selected={selected?.(item.name) ?? true}
|
|
105
108
|
/>
|
|
@@ -113,6 +116,7 @@ export function CategoryUI({ id }: CategoryUIProps) {
|
|
|
113
116
|
maxValue={maxValue}
|
|
114
117
|
color={colors[0]!}
|
|
115
118
|
formatter={formatter}
|
|
119
|
+
labelFormatter={labelFormatter}
|
|
116
120
|
onClick={onRowClick}
|
|
117
121
|
/>
|
|
118
122
|
))}
|
|
@@ -9,6 +9,7 @@ export interface CategoryRowMultiProps {
|
|
|
9
9
|
maxValue: number
|
|
10
10
|
colors: string[]
|
|
11
11
|
formatter: NonNullable<CategoryWidgetConfig['formatter']>
|
|
12
|
+
labelFormatter?: CategoryWidgetConfig['labelFormatter']
|
|
12
13
|
onClick?: CategoryWidgetConfig['onRowClick']
|
|
13
14
|
selected?: boolean
|
|
14
15
|
}
|
|
@@ -22,6 +23,7 @@ export function CategoryRowMulti({
|
|
|
22
23
|
maxValue,
|
|
23
24
|
colors,
|
|
24
25
|
formatter,
|
|
26
|
+
labelFormatter,
|
|
25
27
|
onClick,
|
|
26
28
|
selected = true,
|
|
27
29
|
}: CategoryRowMultiProps) {
|
|
@@ -30,10 +32,12 @@ export function CategoryRowMulti({
|
|
|
30
32
|
|
|
31
33
|
return (
|
|
32
34
|
<Box sx={rowStyle} onClick={handleClick}>
|
|
33
|
-
<Typography sx={styles.rowLabel}>
|
|
35
|
+
<Typography sx={styles.rowLabel}>
|
|
36
|
+
{labelFormatter ? labelFormatter(name) : name}
|
|
37
|
+
</Typography>
|
|
34
38
|
<Box sx={styles.barContainer}>
|
|
35
39
|
{values.map((value, index) => (
|
|
36
|
-
<Box key={`${name}-${value}`} sx={styles.multiBarRow}>
|
|
40
|
+
<Box key={`${name}-${value}-${index}`} sx={styles.multiBarRow}>
|
|
37
41
|
<Box sx={styles.multiBarContainer}>
|
|
38
42
|
<CategoryBar
|
|
39
43
|
value={value}
|
|
@@ -9,6 +9,7 @@ export interface CategoryRowSingleProps {
|
|
|
9
9
|
maxValue: number
|
|
10
10
|
color: string
|
|
11
11
|
formatter: NonNullable<CategoryWidgetConfig['formatter']>
|
|
12
|
+
labelFormatter?: CategoryWidgetConfig['labelFormatter']
|
|
12
13
|
onClick?: CategoryWidgetConfig['onRowClick']
|
|
13
14
|
selected?: boolean
|
|
14
15
|
}
|
|
@@ -22,6 +23,7 @@ export function CategoryRowSingle({
|
|
|
22
23
|
maxValue,
|
|
23
24
|
color,
|
|
24
25
|
formatter,
|
|
26
|
+
labelFormatter,
|
|
25
27
|
onClick,
|
|
26
28
|
selected = true,
|
|
27
29
|
}: CategoryRowSingleProps) {
|
|
@@ -36,7 +38,9 @@ export function CategoryRowSingle({
|
|
|
36
38
|
return (
|
|
37
39
|
<Box sx={rowStyle} onClick={handleClick}>
|
|
38
40
|
<Box sx={styles.rowHeader}>
|
|
39
|
-
<Typography sx={styles.rowLabel}>
|
|
41
|
+
<Typography sx={styles.rowLabel}>
|
|
42
|
+
{labelFormatter ? labelFormatter(name) : name}
|
|
43
|
+
</Typography>
|
|
40
44
|
<Typography sx={styles.rowValue}>{formatter(value)}</Typography>
|
|
41
45
|
</Box>
|
|
42
46
|
<CategoryBar
|
|
@@ -29,6 +29,7 @@ export type CategoryWidgetState = BaseWidgetState<
|
|
|
29
29
|
|
|
30
30
|
export interface CategoryWidgetConfig {
|
|
31
31
|
formatter?: (value: number) => string
|
|
32
|
+
labelFormatter?: (value: string | number) => string | number
|
|
32
33
|
series?: CategorySeriesConfig[]
|
|
33
34
|
maxItems?: number
|
|
34
35
|
labels?: CategoryLabels
|
|
@@ -3,6 +3,7 @@ import { render } from '@testing-library/react'
|
|
|
3
3
|
import { EchartUI } from './echart-ui'
|
|
4
4
|
import type { EChartsOption } from 'echarts'
|
|
5
5
|
import * as echarts from 'echarts'
|
|
6
|
+
import { resetSharedResizeObserver } from './shared-resize-observer'
|
|
6
7
|
|
|
7
8
|
// Mock echarts module
|
|
8
9
|
vi.mock('echarts', () => {
|
|
@@ -36,6 +37,7 @@ describe('EchartUI', () => {
|
|
|
36
37
|
disconnect: ReturnType<typeof vi.fn>
|
|
37
38
|
unobserve: ReturnType<typeof vi.fn>
|
|
38
39
|
}
|
|
40
|
+
let resizeObserverCallback: ResizeObserverCallback | undefined
|
|
39
41
|
|
|
40
42
|
const defaultProps = {
|
|
41
43
|
id: 'test-echart',
|
|
@@ -73,6 +75,9 @@ describe('EchartUI', () => {
|
|
|
73
75
|
mockChart as unknown as echarts.ECharts,
|
|
74
76
|
)
|
|
75
77
|
|
|
78
|
+
// Reset the shared observer so each test gets a fresh one using the mock
|
|
79
|
+
resetSharedResizeObserver()
|
|
80
|
+
|
|
76
81
|
// Mock ResizeObserver
|
|
77
82
|
mockResizeObserver = {
|
|
78
83
|
observe: vi.fn(),
|
|
@@ -80,7 +85,12 @@ describe('EchartUI', () => {
|
|
|
80
85
|
unobserve: vi.fn(),
|
|
81
86
|
}
|
|
82
87
|
|
|
88
|
+
resizeObserverCallback = undefined
|
|
89
|
+
|
|
83
90
|
global.ResizeObserver = class {
|
|
91
|
+
constructor(callback: ResizeObserverCallback) {
|
|
92
|
+
resizeObserverCallback = callback
|
|
93
|
+
}
|
|
84
94
|
observe = mockResizeObserver.observe
|
|
85
95
|
disconnect = mockResizeObserver.disconnect
|
|
86
96
|
unobserve = mockResizeObserver.unobserve
|
|
@@ -245,32 +255,26 @@ describe('EchartUI', () => {
|
|
|
245
255
|
)
|
|
246
256
|
})
|
|
247
257
|
|
|
248
|
-
test('
|
|
258
|
+
test('unobserves element from shared ResizeObserver on unmount', () => {
|
|
249
259
|
const { unmount } = render(
|
|
250
260
|
<EchartUI {...defaultProps} option={basicOption} />,
|
|
251
261
|
)
|
|
252
262
|
|
|
253
263
|
unmount()
|
|
254
264
|
|
|
255
|
-
|
|
265
|
+
// The shared observer calls unobserve() for the specific element, not disconnect()
|
|
266
|
+
expect(mockResizeObserver.unobserve).toHaveBeenCalled()
|
|
256
267
|
})
|
|
257
268
|
|
|
258
269
|
test('calls resize on chart when ResizeObserver triggers', () => {
|
|
259
|
-
let resizeCallback: ResizeObserverCallback
|
|
260
|
-
|
|
261
|
-
global.ResizeObserver = class {
|
|
262
|
-
constructor(callback: ResizeObserverCallback) {
|
|
263
|
-
resizeCallback = callback
|
|
264
|
-
}
|
|
265
|
-
observe = mockResizeObserver.observe
|
|
266
|
-
disconnect = mockResizeObserver.disconnect
|
|
267
|
-
unobserve = mockResizeObserver.unobserve
|
|
268
|
-
} as unknown as typeof ResizeObserver
|
|
269
|
-
|
|
270
270
|
render(<EchartUI {...defaultProps} option={basicOption} />)
|
|
271
271
|
|
|
272
|
-
// Trigger
|
|
273
|
-
|
|
272
|
+
// Trigger the shared observer's callback with a mock entry targeting the chart element
|
|
273
|
+
const chartElement = document.getElementById(defaultProps.id)!
|
|
274
|
+
const entries: ResizeObserverEntry[] = [
|
|
275
|
+
{ target: chartElement } as unknown as ResizeObserverEntry,
|
|
276
|
+
]
|
|
277
|
+
resizeObserverCallback!(entries, {} as ResizeObserver)
|
|
274
278
|
|
|
275
279
|
expect(mockChart.resize).toHaveBeenCalled()
|
|
276
280
|
})
|
|
@@ -517,6 +521,6 @@ describe('EchartUI', () => {
|
|
|
517
521
|
unmount()
|
|
518
522
|
|
|
519
523
|
expect(mockChart.dispose).toHaveBeenCalled()
|
|
520
|
-
expect(mockResizeObserver.
|
|
524
|
+
expect(mockResizeObserver.unobserve).toHaveBeenCalled()
|
|
521
525
|
})
|
|
522
526
|
})
|
|
@@ -2,6 +2,7 @@ import { useEffect, useRef, useImperativeHandle } from 'react'
|
|
|
2
2
|
import * as echarts from 'echarts'
|
|
3
3
|
import type { EchartUIProps } from './types'
|
|
4
4
|
import { useWidgetRef } from '../../hooks'
|
|
5
|
+
import { observeResize } from './shared-resize-observer'
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Presentational component that initializes and manages an Apache ECharts instance.
|
|
@@ -16,7 +17,6 @@ export function EchartUI(props: EchartUIProps) {
|
|
|
16
17
|
const { ref: chartRef, instance: chartInstanceRef } =
|
|
17
18
|
useWidgetRef<HTMLDivElement>(id)
|
|
18
19
|
const chartInstance = useRef<echarts.ECharts>(null)
|
|
19
|
-
const resizeObserverRef = useRef<ResizeObserver | null>(null)
|
|
20
20
|
|
|
21
21
|
useImperativeHandle(ref, () => chartInstance.current!, [])
|
|
22
22
|
|
|
@@ -45,19 +45,13 @@ export function EchartUI(props: EchartUIProps) {
|
|
|
45
45
|
})
|
|
46
46
|
}, [option])
|
|
47
47
|
|
|
48
|
-
// Handle resize using ResizeObserver
|
|
48
|
+
// Handle resize using shared ResizeObserver (single instance for all charts)
|
|
49
49
|
useEffect(() => {
|
|
50
|
-
|
|
51
|
-
chartInstance.current?.resize()
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
resizeObserverRef.current = new ResizeObserver(handleResize)
|
|
55
|
-
resizeObserverRef.current.observe(chartRef.current!)
|
|
50
|
+
if (!chartRef.current) return
|
|
56
51
|
|
|
57
|
-
return () => {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
52
|
+
return observeResize(chartRef.current, () => {
|
|
53
|
+
chartInstance.current?.resize()
|
|
54
|
+
})
|
|
61
55
|
}, [chartRef])
|
|
62
56
|
|
|
63
57
|
useEffect(() => {
|