@carto/ps-react-ui 4.3.5 → 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.
- package/dist/components.js +123 -123
- package/dist/components.js.map +1 -1
- package/dist/error-CEkRPccv.js +39 -0
- package/dist/error-CEkRPccv.js.map +1 -0
- package/dist/{lasso-tool-wFqOD6wk.js → lasso-tool-jl4YK02H.js} +184 -159
- package/dist/lasso-tool-jl4YK02H.js.map +1 -0
- package/dist/no-data-hR3KcJ-_.js +60 -0
- package/dist/no-data-hR3KcJ-_.js.map +1 -0
- package/dist/{row-DrHwXNvF.js → row-BKmVAUN5.js} +2 -2
- package/dist/{row-DrHwXNvF.js.map → row-BKmVAUN5.js.map} +1 -1
- package/dist/{series-D3Pc-kYX.js → series-D1pynfeh.js} +3 -3
- package/dist/{series-D3Pc-kYX.js.map → series-D1pynfeh.js.map} +1 -1
- package/dist/{styles-CCZnY17y.js → styles-DrPyd0y5.js} +28 -22
- package/dist/styles-DrPyd0y5.js.map +1 -0
- package/dist/types/components/lasso-tool/types.d.ts +1 -1
- package/dist/types/widgets/_shared/chart-config/index.d.ts +1 -1
- package/dist/types/widgets/_shared/chart-config/option-builders.d.ts +7 -0
- package/dist/types/widgets/_shared/chart-config/option-builders.test.d.ts +1 -0
- package/dist/types/widgets/actions/index.d.ts +4 -4
- package/dist/types/widgets/actions/lock-selection/types.d.ts +0 -13
- package/dist/types/widgets/actions/relative-data/types.d.ts +0 -4
- package/dist/types/widgets/actions/searcher/types.d.ts +0 -2
- package/dist/types/widgets/actions/stack-toggle/stack-toggle.d.ts +3 -2
- package/dist/types/widgets/actions/stack-toggle/types.d.ts +0 -4
- package/dist/types/widgets/actions/zoom-toggle/zoom-toggle.d.ts +4 -0
- package/dist/types/widgets/echart/types.d.ts +0 -4
- package/dist/types/widgets/echart/utils.d.ts +2 -1
- package/dist/types/widgets/error/error.d.ts +1 -1
- package/dist/types/widgets/error/types.d.ts +8 -0
- package/dist/types/widgets/loader/loader.d.ts +1 -1
- package/dist/types/widgets/loader/types.d.ts +1 -1
- package/dist/types/widgets/stores/index.d.ts +1 -1
- package/dist/types/widgets/stores/types.d.ts +15 -0
- package/dist/{use-widget-ref-B0aNCANx.js → use-widget-ref-P-2i0MJG.js} +2 -2
- package/dist/{use-widget-ref-B0aNCANx.js.map → use-widget-ref-P-2i0MJG.js.map} +1 -1
- package/dist/{utils-D3-eQyDR.js → utils-idmvq0Oa.js} +17 -16
- package/dist/utils-idmvq0Oa.js.map +1 -0
- package/dist/widget-store-CzDt8oSK.js +163 -0
- package/dist/widget-store-CzDt8oSK.js.map +1 -0
- package/dist/widgets/actions.js +714 -659
- package/dist/widgets/actions.js.map +1 -1
- package/dist/widgets/bar.js +67 -63
- package/dist/widgets/bar.js.map +1 -1
- package/dist/widgets/category.js +250 -241
- package/dist/widgets/category.js.map +1 -1
- package/dist/widgets/echart.js +93 -100
- package/dist/widgets/echart.js.map +1 -1
- package/dist/widgets/error.js +1 -1
- package/dist/widgets/formula.js +64 -72
- package/dist/widgets/formula.js.map +1 -1
- package/dist/widgets/histogram.js +75 -73
- package/dist/widgets/histogram.js.map +1 -1
- package/dist/widgets/loader.js +41 -40
- package/dist/widgets/loader.js.map +1 -1
- package/dist/widgets/markdown.js +2 -2
- package/dist/widgets/no-data.js +1 -1
- package/dist/widgets/pie.js +4 -4
- package/dist/widgets/range.js +97 -105
- package/dist/widgets/range.js.map +1 -1
- package/dist/widgets/scatterplot.js +8 -8
- package/dist/widgets/skeleton-loader.js +1 -1
- package/dist/widgets/spread.js +84 -100
- package/dist/widgets/spread.js.map +1 -1
- package/dist/widgets/stores.js +1 -1
- package/dist/widgets/table.js +493 -485
- package/dist/widgets/table.js.map +1 -1
- package/dist/widgets/timeseries.js +4 -4
- package/dist/widgets/wrapper.js +156 -156
- package/dist/widgets/wrapper.js.map +1 -1
- package/dist/widgets.js +4 -4
- package/package.json +3 -3
- package/src/components/lasso-tool/lasso-tool-inline.tsx +19 -17
- package/src/components/lasso-tool/lasso-tool.tsx +27 -22
- package/src/components/lasso-tool/types.ts +4 -3
- package/src/widgets/_shared/chart-config/index.ts +1 -0
- package/src/widgets/_shared/chart-config/option-builders.test.ts +40 -0
- package/src/widgets/_shared/chart-config/option-builders.ts +12 -0
- package/src/widgets/actions/fullscreen/fullscreen.tsx +5 -8
- package/src/widgets/actions/index.ts +4 -7
- package/src/widgets/actions/lock-selection/lock-selection.test.tsx +28 -30
- package/src/widgets/actions/lock-selection/lock-selection.tsx +25 -26
- package/src/widgets/actions/lock-selection/types.ts +0 -17
- package/src/widgets/actions/relative-data/relative-data.test.tsx +13 -13
- package/src/widgets/actions/relative-data/relative-data.tsx +18 -21
- package/src/widgets/actions/relative-data/types.ts +0 -7
- package/src/widgets/actions/searcher/searcher.tsx +40 -22
- package/src/widgets/actions/searcher/types.ts +0 -2
- package/src/widgets/actions/stack-toggle/stack-toggle.test.tsx +160 -16
- package/src/widgets/actions/stack-toggle/stack-toggle.tsx +79 -78
- package/src/widgets/actions/stack-toggle/types.ts +0 -8
- package/src/widgets/actions/zoom-toggle/zoom-toggle.tsx +137 -87
- package/src/widgets/bar/config.ts +37 -28
- package/src/widgets/category/category-ui.tsx +25 -22
- package/src/widgets/echart/echart-ui.test.tsx +3 -18
- package/src/widgets/echart/echart-ui.tsx +4 -22
- package/src/widgets/echart/echart.test.tsx +9 -25
- package/src/widgets/echart/echart.tsx +36 -29
- package/src/widgets/echart/types.ts +0 -4
- package/src/widgets/echart/utils.ts +3 -1
- package/src/widgets/error/error.tsx +17 -14
- package/src/widgets/error/types.ts +10 -0
- package/src/widgets/formula/components/value.tsx +13 -13
- package/src/widgets/histogram/config.ts +36 -29
- package/src/widgets/loader/loader.tsx +20 -8
- package/src/widgets/loader/types.ts +3 -1
- package/src/widgets/no-data/no-data.tsx +8 -11
- package/src/widgets/range/components/range-item.tsx +9 -13
- package/src/widgets/spread/components/max-value.tsx +13 -13
- package/src/widgets/spread/components/min-value.tsx +13 -13
- package/src/widgets/stores/index.ts +1 -0
- package/src/widgets/stores/types.ts +17 -0
- package/src/widgets/stores/widget-store.test.ts +141 -0
- package/src/widgets/stores/widget-store.ts +73 -2
- package/src/widgets/table/hooks/use-pagination.ts +44 -35
- package/src/widgets/table/hooks/use-sort.ts +25 -23
- package/src/widgets/wrapper/wrapper-ui.tsx +16 -17
- package/dist/error-B2IJ9d2h.js +0 -38
- package/dist/error-B2IJ9d2h.js.map +0 -1
- package/dist/lasso-tool-wFqOD6wk.js.map +0 -1
- package/dist/no-data-C54XJt13.js +0 -61
- package/dist/no-data-C54XJt13.js.map +0 -1
- package/dist/styles-CCZnY17y.js.map +0 -1
- package/dist/utils-D3-eQyDR.js.map +0 -1
- package/dist/widget-store-CB6Trp_0.js +0 -131
- package/dist/widget-store-CB6Trp_0.js.map +0 -1
|
@@ -5,14 +5,12 @@ import type { Ref } from 'react'
|
|
|
5
5
|
import { theme as CartoTheme } from '@carto/meridian-ds/theme'
|
|
6
6
|
|
|
7
7
|
export type EchartOptionsProps = EChartsOption
|
|
8
|
-
export type EchartReplaceMerge = string[]
|
|
9
8
|
|
|
10
9
|
export interface EchartUIProps {
|
|
11
10
|
id: string
|
|
12
11
|
option: EchartOptionsProps
|
|
13
12
|
className?: string
|
|
14
13
|
init?: echarts.EChartsInitOpts
|
|
15
|
-
replaceMerge?: EchartReplaceMerge
|
|
16
14
|
style?: React.CSSProperties
|
|
17
15
|
ref?: Ref<echarts.ECharts>
|
|
18
16
|
onEvents?: Record<string, Parameters<echarts.ECharts['on']>[2]>
|
|
@@ -28,7 +26,6 @@ export type EchartWidgetState = BaseWidgetState<{
|
|
|
28
26
|
option: EchartUIProps['option']
|
|
29
27
|
onEvents?: EchartUIProps['onEvents']
|
|
30
28
|
init?: EchartUIProps['init']
|
|
31
|
-
replaceMerge?: EchartReplaceMerge
|
|
32
29
|
}>
|
|
33
30
|
|
|
34
31
|
export interface EchartWidgetOptionProps<D> {
|
|
@@ -41,5 +38,4 @@ export interface EchartWidgetProps {
|
|
|
41
38
|
type: string
|
|
42
39
|
option: EchartUIProps['option']
|
|
43
40
|
onEvents?: EchartUIProps['onEvents']
|
|
44
|
-
replaceMerge?: EchartReplaceMerge
|
|
45
41
|
}
|
|
@@ -35,12 +35,14 @@ export function getEChartZoomConfig(
|
|
|
35
35
|
ySlider = false,
|
|
36
36
|
showSliders = true,
|
|
37
37
|
xAxisLabelFormatter,
|
|
38
|
+
bottomOffset = 0,
|
|
38
39
|
} = {} as {
|
|
39
40
|
inside?: boolean
|
|
40
41
|
xSlider?: boolean
|
|
41
42
|
ySlider?: boolean
|
|
42
43
|
showSliders?: boolean
|
|
43
44
|
xAxisLabelFormatter?: (value: number) => string
|
|
45
|
+
bottomOffset?: number
|
|
44
46
|
},
|
|
45
47
|
theme?: Theme,
|
|
46
48
|
) {
|
|
@@ -72,7 +74,7 @@ export function getEChartZoomConfig(
|
|
|
72
74
|
throttle: 0,
|
|
73
75
|
type: 'slider',
|
|
74
76
|
xAxisIndex: [0],
|
|
75
|
-
bottom:
|
|
77
|
+
bottom: bottomOffset,
|
|
76
78
|
height: parseInt(theme?.spacing?.(4) ?? '32'),
|
|
77
79
|
show: zoom && showSliders,
|
|
78
80
|
zoomLock: !zoom,
|
|
@@ -3,28 +3,31 @@ import { useWidgetStore } from '../stores/widget-store'
|
|
|
3
3
|
import { useShallow } from 'zustand/shallow'
|
|
4
4
|
import type { WidgetErrorProps } from './types'
|
|
5
5
|
|
|
6
|
-
export function WidgetError({
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
}),
|
|
6
|
+
export function WidgetError({
|
|
7
|
+
id,
|
|
8
|
+
children,
|
|
9
|
+
title: titleProp,
|
|
10
|
+
description,
|
|
11
|
+
}: WidgetErrorProps) {
|
|
12
|
+
const isLoading = useWidgetStore(
|
|
13
|
+
useShallow((state) => state.widgets[id]?.isLoading),
|
|
16
14
|
)
|
|
15
|
+
const isFetching = useWidgetStore(
|
|
16
|
+
useShallow((state) => state.widgets[id]?.isFetching),
|
|
17
|
+
)
|
|
18
|
+
const error = useWidgetStore(useShallow((state) => state.widgets[id]?.error))
|
|
17
19
|
|
|
18
20
|
// Don't show error during loading/fetching states
|
|
19
|
-
if (
|
|
21
|
+
if (isLoading || isFetching) {
|
|
20
22
|
return children
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
// Show error UI if error exists
|
|
24
|
-
if (
|
|
25
|
-
const errorTitle =
|
|
26
|
+
if (error) {
|
|
27
|
+
const errorTitle = titleProp ?? error.title ?? 'Error'
|
|
26
28
|
const errorMessage =
|
|
27
|
-
|
|
29
|
+
description ??
|
|
30
|
+
error.message ??
|
|
28
31
|
'An error occurred while loading the widget. Please try again.'
|
|
29
32
|
|
|
30
33
|
return (
|
|
@@ -11,4 +11,14 @@ export interface WidgetErrorProps {
|
|
|
11
11
|
* Children to render when no error exists
|
|
12
12
|
*/
|
|
13
13
|
children: ReactNode
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Override error title
|
|
17
|
+
*/
|
|
18
|
+
title?: string
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Override error description/message
|
|
22
|
+
*/
|
|
23
|
+
description?: string
|
|
14
24
|
}
|
|
@@ -6,20 +6,20 @@ import { useShallow } from 'zustand/shallow'
|
|
|
6
6
|
const defaultFormatter = (value: number) => value.toString()
|
|
7
7
|
|
|
8
8
|
export function Value({ id, index = 0, ...props }: ValueProps) {
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
} = useWidgetStore(
|
|
14
|
-
useShallow((state) => {
|
|
15
|
-
const widget = state.getWidget<FormulaWidgetState>(id)
|
|
16
|
-
return {
|
|
17
|
-
value: widget?.data?.[index]?.value,
|
|
18
|
-
color: widget?.data?.[index]?.color,
|
|
19
|
-
formatter: widget?.formatter,
|
|
20
|
-
}
|
|
21
|
-
}),
|
|
9
|
+
const value = useWidgetStore(
|
|
10
|
+
useShallow(
|
|
11
|
+
(state) => state.getWidget<FormulaWidgetState>(id)?.data?.[index]?.value,
|
|
12
|
+
),
|
|
22
13
|
)
|
|
14
|
+
const color = useWidgetStore(
|
|
15
|
+
useShallow(
|
|
16
|
+
(state) => state.getWidget<FormulaWidgetState>(id)?.data?.[index]?.color,
|
|
17
|
+
),
|
|
18
|
+
)
|
|
19
|
+
const formatter =
|
|
20
|
+
useWidgetStore(
|
|
21
|
+
useShallow((state) => state.getWidget<FormulaWidgetState>(id)?.formatter),
|
|
22
|
+
) ?? defaultFormatter
|
|
23
23
|
|
|
24
24
|
return (
|
|
25
25
|
<Item TypographyProps={{ color }} {...props}>
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
buildGridConfig,
|
|
15
15
|
createTooltipPositioner,
|
|
16
16
|
createTooltipFormatter,
|
|
17
|
-
|
|
17
|
+
niceNum,
|
|
18
18
|
} from '../_shared/chart-config'
|
|
19
19
|
import { downloadToCSV, downloadToPNG, type DownloadItem } from '../actions'
|
|
20
20
|
import type { ConfigProps } from '../loader/types'
|
|
@@ -49,33 +49,7 @@ function getOption({
|
|
|
49
49
|
}: HistogramConfig): EchartOptionsProps {
|
|
50
50
|
const hasLegend = (data?.length ?? 0) > 1
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
type: 'value' as const,
|
|
54
|
-
showMaxLabel: true,
|
|
55
|
-
showMinLabel: true,
|
|
56
|
-
splitNumber: 1,
|
|
57
|
-
axisLabel: {
|
|
58
|
-
fontSize: theme.typography.overlineDelicate.fontSize,
|
|
59
|
-
fontFamily: theme.typography.overlineDelicate.fontFamily,
|
|
60
|
-
margin: parseInt(theme.spacing(1)),
|
|
61
|
-
show: true,
|
|
62
|
-
showMaxLabel: true,
|
|
63
|
-
showMinLabel: true,
|
|
64
|
-
verticalAlign: 'bottom' as const,
|
|
65
|
-
},
|
|
66
|
-
axisLine: {
|
|
67
|
-
show: false,
|
|
68
|
-
},
|
|
69
|
-
axisTick: {
|
|
70
|
-
show: false,
|
|
71
|
-
},
|
|
72
|
-
splitLine: {
|
|
73
|
-
show: true,
|
|
74
|
-
lineStyle: {
|
|
75
|
-
color: theme.palette.black[4],
|
|
76
|
-
},
|
|
77
|
-
},
|
|
78
|
-
}
|
|
52
|
+
let niceMax = 1
|
|
79
53
|
|
|
80
54
|
return {
|
|
81
55
|
legend: buildLegendConfig(hasLegend),
|
|
@@ -110,7 +84,40 @@ function getOption({
|
|
|
110
84
|
},
|
|
111
85
|
},
|
|
112
86
|
},
|
|
113
|
-
yAxis:
|
|
87
|
+
yAxis: {
|
|
88
|
+
type: 'value' as const,
|
|
89
|
+
min: 0,
|
|
90
|
+
max: (extent: { min: number; max: number }) => {
|
|
91
|
+
niceMax = extent.max <= 0 ? 1 : niceNum(extent.max)
|
|
92
|
+
return niceMax
|
|
93
|
+
},
|
|
94
|
+
splitNumber: 1,
|
|
95
|
+
axisLabel: {
|
|
96
|
+
fontSize: theme.typography.overlineDelicate.fontSize,
|
|
97
|
+
fontFamily: theme.typography.overlineDelicate.fontFamily,
|
|
98
|
+
margin: parseInt(theme.spacing(1)),
|
|
99
|
+
show: true,
|
|
100
|
+
showMaxLabel: true,
|
|
101
|
+
showMinLabel: true,
|
|
102
|
+
verticalAlign: 'bottom' as const,
|
|
103
|
+
formatter: (value: number) => {
|
|
104
|
+
if (value !== niceMax) return ''
|
|
105
|
+
return formatter ? formatter(value) : String(value)
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
axisLine: {
|
|
109
|
+
show: false,
|
|
110
|
+
},
|
|
111
|
+
axisTick: {
|
|
112
|
+
show: false,
|
|
113
|
+
},
|
|
114
|
+
splitLine: {
|
|
115
|
+
show: true,
|
|
116
|
+
lineStyle: {
|
|
117
|
+
color: theme.palette.black[4],
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
},
|
|
114
121
|
tooltip: {
|
|
115
122
|
position: createTooltipPositioner(theme),
|
|
116
123
|
formatter: createTooltipFormatter((item) => {
|
|
@@ -3,11 +3,16 @@ import type { WidgetLoaderProps } from './types'
|
|
|
3
3
|
import { useWidgetStore } from '../stores/widget-store'
|
|
4
4
|
import type { WrapperState } from '../wrapper'
|
|
5
5
|
|
|
6
|
-
export function WidgetLoader<T
|
|
6
|
+
export function WidgetLoader<T extends object = Record<string, unknown>>(
|
|
7
|
+
props: WidgetLoaderProps<T>,
|
|
8
|
+
) {
|
|
7
9
|
const setWidget = useWidgetStore((state) => state.setWidget)
|
|
8
10
|
const executeToolPipeline = useWidgetStore(
|
|
9
11
|
(state) => state.executeToolPipeline,
|
|
10
12
|
)
|
|
13
|
+
const executeConfigPipeline = useWidgetStore(
|
|
14
|
+
(state) => state.executeConfigPipeline,
|
|
15
|
+
)
|
|
11
16
|
|
|
12
17
|
// Split into 3 effects for metadata and 1 for data pipeline:
|
|
13
18
|
// Each property that can be modified independently gets its own effect to avoid
|
|
@@ -35,21 +40,19 @@ export function WidgetLoader<T>(props: WidgetLoaderProps<T>) {
|
|
|
35
40
|
})
|
|
36
41
|
}, [props.id, props.isLoading, props.isFetching, props.error, setWidget])
|
|
37
42
|
|
|
38
|
-
// Effect 3: Config updates
|
|
43
|
+
// Effect 3: Config updates — run through config pipeline
|
|
39
44
|
useEffect(() => {
|
|
40
45
|
if (props.config) {
|
|
41
|
-
|
|
42
|
-
...props.config,
|
|
43
|
-
})
|
|
46
|
+
void executeConfigPipeline(props.id, props.config)
|
|
44
47
|
}
|
|
45
|
-
}, [props.id, props.config,
|
|
48
|
+
}, [props.id, props.config, executeConfigPipeline])
|
|
46
49
|
|
|
47
50
|
// Effect 4: Execute tool pipeline when props.data changes
|
|
48
51
|
useEffect(() => {
|
|
49
52
|
void executeToolPipeline(props.id, props.data)
|
|
50
53
|
}, [props.id, props.data, executeToolPipeline])
|
|
51
54
|
|
|
52
|
-
// Effect 5: Re-execute
|
|
55
|
+
// Effect 5: Re-execute pipelines when tool state changes (enabled/config)
|
|
53
56
|
useEffect(() => {
|
|
54
57
|
let prevTools = useWidgetStore.getState().widgets[props.id]?.registeredTools
|
|
55
58
|
|
|
@@ -60,11 +63,20 @@ export function WidgetLoader<T>(props: WidgetLoaderProps<T>) {
|
|
|
60
63
|
if (currentTools !== prevTools) {
|
|
61
64
|
prevTools = currentTools
|
|
62
65
|
void executeToolPipeline(props.id, props.data)
|
|
66
|
+
if (props.config) {
|
|
67
|
+
void executeConfigPipeline(props.id, props.config)
|
|
68
|
+
}
|
|
63
69
|
}
|
|
64
70
|
})
|
|
65
71
|
|
|
66
72
|
return unsubscribe
|
|
67
|
-
}, [
|
|
73
|
+
}, [
|
|
74
|
+
props.id,
|
|
75
|
+
props.data,
|
|
76
|
+
props.config,
|
|
77
|
+
executeToolPipeline,
|
|
78
|
+
executeConfigPipeline,
|
|
79
|
+
])
|
|
68
80
|
|
|
69
81
|
return props.children
|
|
70
82
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { ReactNode } from 'react'
|
|
2
2
|
import type { WidgetsStoreProps, WidgetState } from '../stores/types'
|
|
3
3
|
|
|
4
|
-
export interface WidgetLoaderProps<
|
|
4
|
+
export interface WidgetLoaderProps<
|
|
5
|
+
T extends object = Record<string, unknown>,
|
|
6
|
+
> extends WidgetsStoreProps {
|
|
5
7
|
children: ReactNode
|
|
6
8
|
config?: T
|
|
7
9
|
}
|
|
@@ -45,25 +45,22 @@ export function WidgetNoData({
|
|
|
45
45
|
isEmpty = defaultIsEmpty,
|
|
46
46
|
}: WidgetNoDataProps) {
|
|
47
47
|
// Subscribe to widget store with selective subscription for optimal performance
|
|
48
|
-
const
|
|
49
|
-
useShallow((state) =>
|
|
50
|
-
const w = state.widgets[id]
|
|
51
|
-
return {
|
|
52
|
-
isLoading: w?.isLoading,
|
|
53
|
-
isFetching: w?.isFetching,
|
|
54
|
-
data: w?.data,
|
|
55
|
-
}
|
|
56
|
-
}),
|
|
48
|
+
const isLoading = useWidgetStore(
|
|
49
|
+
useShallow((state) => state.widgets[id]?.isLoading),
|
|
57
50
|
)
|
|
51
|
+
const isFetching = useWidgetStore(
|
|
52
|
+
useShallow((state) => state.widgets[id]?.isFetching),
|
|
53
|
+
)
|
|
54
|
+
const data = useWidgetStore(useShallow((state) => state.widgets[id]?.data))
|
|
58
55
|
|
|
59
56
|
// If loading or fetching, show children
|
|
60
57
|
// SkeletonLoader handles loading state, this allows proper composition
|
|
61
|
-
if (
|
|
58
|
+
if (isLoading || isFetching) {
|
|
62
59
|
return children
|
|
63
60
|
}
|
|
64
61
|
|
|
65
62
|
// Check if data is empty
|
|
66
|
-
if (isEmpty(
|
|
63
|
+
if (isEmpty(data)) {
|
|
67
64
|
return (
|
|
68
65
|
<Box sx={styles.root}>
|
|
69
66
|
<Typography variant='body2' color='text.primary'>
|
|
@@ -10,20 +10,16 @@ type EditingState = '' | 'min' | 'max'
|
|
|
10
10
|
const defaultFormatter = (value: number) => value.toString()
|
|
11
11
|
|
|
12
12
|
export function RangeItem({ id, index }: RangeItemProps) {
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
useShallow((state) => {
|
|
19
|
-
const widget = state.getWidget<RangeWidgetState>(id)
|
|
20
|
-
return {
|
|
21
|
-
item: widget?.data[index],
|
|
22
|
-
onChange: widget?.onChange,
|
|
23
|
-
formatter: widget?.formatter,
|
|
24
|
-
}
|
|
25
|
-
}),
|
|
13
|
+
const item = useWidgetStore(
|
|
14
|
+
useShallow((state) => state.getWidget<RangeWidgetState>(id)?.data[index]),
|
|
15
|
+
)
|
|
16
|
+
const onChange = useWidgetStore(
|
|
17
|
+
useShallow((state) => state.getWidget<RangeWidgetState>(id)?.onChange),
|
|
26
18
|
)
|
|
19
|
+
const formatter =
|
|
20
|
+
useWidgetStore(
|
|
21
|
+
useShallow((state) => state.getWidget<RangeWidgetState>(id)?.formatter),
|
|
22
|
+
) ?? defaultFormatter
|
|
27
23
|
const getWidget = useWidgetStore((store) => store.getWidget)
|
|
28
24
|
const setWidget = useWidgetStore((store) => store.setWidget)
|
|
29
25
|
|
|
@@ -6,20 +6,20 @@ import { useShallow } from 'zustand/shallow'
|
|
|
6
6
|
const defaultFormatter = (value: number) => value.toString()
|
|
7
7
|
|
|
8
8
|
export function MaxValue({ id, index = 0, ...props }: ValueProps) {
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
} = useWidgetStore(
|
|
14
|
-
useShallow((state) => {
|
|
15
|
-
const widget = state.getWidget<SpreadWidgetState>(id)
|
|
16
|
-
return {
|
|
17
|
-
max: widget?.data[index]?.max,
|
|
18
|
-
color: widget?.data[index]?.color,
|
|
19
|
-
formatter: widget?.formatter,
|
|
20
|
-
}
|
|
21
|
-
}),
|
|
9
|
+
const max = useWidgetStore(
|
|
10
|
+
useShallow(
|
|
11
|
+
(state) => state.getWidget<SpreadWidgetState>(id)?.data[index]?.max,
|
|
12
|
+
),
|
|
22
13
|
)
|
|
14
|
+
const color = useWidgetStore(
|
|
15
|
+
useShallow(
|
|
16
|
+
(state) => state.getWidget<SpreadWidgetState>(id)?.data[index]?.color,
|
|
17
|
+
),
|
|
18
|
+
)
|
|
19
|
+
const formatter =
|
|
20
|
+
useWidgetStore(
|
|
21
|
+
useShallow((state) => state.getWidget<SpreadWidgetState>(id)?.formatter),
|
|
22
|
+
) ?? defaultFormatter
|
|
23
23
|
|
|
24
24
|
return (
|
|
25
25
|
<Item TypographyProps={{ color }} {...props}>
|
|
@@ -6,20 +6,20 @@ import { useShallow } from 'zustand/shallow'
|
|
|
6
6
|
const defaultFormatter = (value: number) => value.toString()
|
|
7
7
|
|
|
8
8
|
export function MinValue({ id, index = 0, ...props }: ValueProps) {
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
} = useWidgetStore(
|
|
14
|
-
useShallow((state) => {
|
|
15
|
-
const widget = state.getWidget<SpreadWidgetState>(id)
|
|
16
|
-
return {
|
|
17
|
-
min: widget?.data[index]?.min,
|
|
18
|
-
color: widget?.data[index]?.color,
|
|
19
|
-
formatter: widget?.formatter,
|
|
20
|
-
}
|
|
21
|
-
}),
|
|
9
|
+
const min = useWidgetStore(
|
|
10
|
+
useShallow(
|
|
11
|
+
(state) => state.getWidget<SpreadWidgetState>(id)?.data[index]?.min,
|
|
12
|
+
),
|
|
22
13
|
)
|
|
14
|
+
const color = useWidgetStore(
|
|
15
|
+
useShallow(
|
|
16
|
+
(state) => state.getWidget<SpreadWidgetState>(id)?.data[index]?.color,
|
|
17
|
+
),
|
|
18
|
+
)
|
|
19
|
+
const formatter =
|
|
20
|
+
useWidgetStore(
|
|
21
|
+
useShallow((state) => state.getWidget<SpreadWidgetState>(id)?.formatter),
|
|
22
|
+
) ?? defaultFormatter
|
|
23
23
|
|
|
24
24
|
return (
|
|
25
25
|
<Item TypographyProps={{ color }} {...props}>
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import type { RefObject } from 'react'
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Tool type determines which pipeline a tool participates in.
|
|
5
|
+
* - 'data': transforms widget data (default)
|
|
6
|
+
* - 'config': transforms widget config/option
|
|
7
|
+
*/
|
|
8
|
+
export type ToolType = 'data' | 'config'
|
|
9
|
+
|
|
3
10
|
export interface WidgetsStoreProps {
|
|
4
11
|
/** Unique identifier for the widget */
|
|
5
12
|
id: string
|
|
@@ -77,6 +84,8 @@ export interface ToolRegistration {
|
|
|
77
84
|
fn: ToolTransformFunction
|
|
78
85
|
/** Whether tool is currently enabled */
|
|
79
86
|
enabled: boolean
|
|
87
|
+
/** 'data' (default) transforms data, 'config' transforms widget config/option */
|
|
88
|
+
type?: ToolType
|
|
80
89
|
/** Tool-specific configuration */
|
|
81
90
|
config?: Record<string, unknown>
|
|
82
91
|
/**
|
|
@@ -184,6 +193,14 @@ export interface WidgetStoreActions {
|
|
|
184
193
|
* @param sourceData - Original data to transform
|
|
185
194
|
*/
|
|
186
195
|
executeToolPipeline: (widgetId: string, sourceData: unknown) => Promise<void>
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Execute the config transformation pipeline
|
|
199
|
+
* Applies config-type tools to the base config, then sets the result on the widget
|
|
200
|
+
* @param widgetId - Widget ID
|
|
201
|
+
* @param baseConfig - Base config to transform
|
|
202
|
+
*/
|
|
203
|
+
executeConfigPipeline: (widgetId: string, baseConfig: object) => Promise<void>
|
|
187
204
|
}
|
|
188
205
|
|
|
189
206
|
/**
|
|
@@ -465,6 +465,147 @@ describe('WidgetStore', () => {
|
|
|
465
465
|
})
|
|
466
466
|
})
|
|
467
467
|
|
|
468
|
+
describe('Config Tool Pipeline', () => {
|
|
469
|
+
const widgetId = 'test-widget-config'
|
|
470
|
+
|
|
471
|
+
beforeEach(() => {
|
|
472
|
+
useWidgetStore.getState().clearWidgets()
|
|
473
|
+
})
|
|
474
|
+
|
|
475
|
+
it('executes config tools and sets transformed config', async () => {
|
|
476
|
+
useWidgetStore.getState().setWidget(widgetId, {
|
|
477
|
+
type: 'bar',
|
|
478
|
+
isLoading: false,
|
|
479
|
+
})
|
|
480
|
+
|
|
481
|
+
const configTool: import('./types').ToolRegistration = {
|
|
482
|
+
id: 'stack-tool',
|
|
483
|
+
type: 'config',
|
|
484
|
+
order: 10,
|
|
485
|
+
enabled: true,
|
|
486
|
+
fn: (config) => {
|
|
487
|
+
const c = config as Record<string, unknown>
|
|
488
|
+
const option = c.option as { series?: { name: string }[] }
|
|
489
|
+
const series = option?.series ?? []
|
|
490
|
+
return {
|
|
491
|
+
...c,
|
|
492
|
+
option: {
|
|
493
|
+
...option,
|
|
494
|
+
series: series.map((s) => ({ ...s, stack: 'group' })),
|
|
495
|
+
},
|
|
496
|
+
}
|
|
497
|
+
},
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
useWidgetStore.getState().registerTool(widgetId, configTool)
|
|
501
|
+
|
|
502
|
+
await useWidgetStore.getState().executeConfigPipeline(widgetId, {
|
|
503
|
+
option: {
|
|
504
|
+
series: [{ name: 'Series 1' }, { name: 'Series 2' }],
|
|
505
|
+
},
|
|
506
|
+
})
|
|
507
|
+
|
|
508
|
+
const widget = useWidgetStore.getState().getWidget(widgetId)
|
|
509
|
+
const option = (widget as { option?: { series?: { stack?: string }[] } })
|
|
510
|
+
?.option
|
|
511
|
+
expect(option?.series?.[0]?.stack).toBe('group')
|
|
512
|
+
expect(option?.series?.[1]?.stack).toBe('group')
|
|
513
|
+
})
|
|
514
|
+
|
|
515
|
+
it('passes base config through when no config tools registered', async () => {
|
|
516
|
+
useWidgetStore.getState().setWidget(widgetId, {
|
|
517
|
+
type: 'bar',
|
|
518
|
+
isLoading: false,
|
|
519
|
+
})
|
|
520
|
+
|
|
521
|
+
await useWidgetStore.getState().executeConfigPipeline(widgetId, {
|
|
522
|
+
option: { series: [{ name: 'Series 1' }] },
|
|
523
|
+
})
|
|
524
|
+
|
|
525
|
+
const widget = useWidgetStore.getState().getWidget(widgetId)
|
|
526
|
+
const option = (widget as { option?: { series?: { name: string }[] } })
|
|
527
|
+
?.option
|
|
528
|
+
expect(option?.series?.[0]?.name).toBe('Series 1')
|
|
529
|
+
})
|
|
530
|
+
|
|
531
|
+
it('does not include config tools in data pipeline', async () => {
|
|
532
|
+
const executionOrder: string[] = []
|
|
533
|
+
|
|
534
|
+
useWidgetStore.getState().setWidget(widgetId, {
|
|
535
|
+
type: 'bar',
|
|
536
|
+
isLoading: false,
|
|
537
|
+
})
|
|
538
|
+
|
|
539
|
+
const dataTool: import('./types').ToolRegistration = {
|
|
540
|
+
id: 'data-tool',
|
|
541
|
+
type: 'data',
|
|
542
|
+
order: 10,
|
|
543
|
+
enabled: true,
|
|
544
|
+
fn: (data) => {
|
|
545
|
+
executionOrder.push('data-tool')
|
|
546
|
+
return data
|
|
547
|
+
},
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
const configTool: import('./types').ToolRegistration = {
|
|
551
|
+
id: 'config-tool',
|
|
552
|
+
type: 'config',
|
|
553
|
+
order: 10,
|
|
554
|
+
enabled: true,
|
|
555
|
+
fn: (config) => {
|
|
556
|
+
executionOrder.push('config-tool')
|
|
557
|
+
return config
|
|
558
|
+
},
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
useWidgetStore.getState().registerTool(widgetId, dataTool)
|
|
562
|
+
useWidgetStore.getState().registerTool(widgetId, configTool)
|
|
563
|
+
|
|
564
|
+
await useWidgetStore.getState().executeToolPipeline(widgetId, {})
|
|
565
|
+
|
|
566
|
+
expect(executionOrder).toEqual(['data-tool'])
|
|
567
|
+
expect(executionOrder).not.toContain('config-tool')
|
|
568
|
+
})
|
|
569
|
+
|
|
570
|
+
it('respects disables across tool types', async () => {
|
|
571
|
+
const executionOrder: string[] = []
|
|
572
|
+
|
|
573
|
+
useWidgetStore.getState().setWidget(widgetId, {
|
|
574
|
+
type: 'bar',
|
|
575
|
+
isLoading: false,
|
|
576
|
+
})
|
|
577
|
+
|
|
578
|
+
const configTool: import('./types').ToolRegistration = {
|
|
579
|
+
id: 'config-tool',
|
|
580
|
+
type: 'config',
|
|
581
|
+
order: 10,
|
|
582
|
+
enabled: true,
|
|
583
|
+
fn: (config) => {
|
|
584
|
+
executionOrder.push('config-tool')
|
|
585
|
+
return config
|
|
586
|
+
},
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
const disablerTool: import('./types').ToolRegistration = {
|
|
590
|
+
id: 'disabler',
|
|
591
|
+
type: 'data',
|
|
592
|
+
order: 10,
|
|
593
|
+
enabled: true,
|
|
594
|
+
fn: (data) => data,
|
|
595
|
+
disables: ['config-tool'],
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
useWidgetStore.getState().registerTool(widgetId, configTool)
|
|
599
|
+
useWidgetStore.getState().registerTool(widgetId, disablerTool)
|
|
600
|
+
|
|
601
|
+
await useWidgetStore.getState().executeConfigPipeline(widgetId, {
|
|
602
|
+
option: {},
|
|
603
|
+
})
|
|
604
|
+
|
|
605
|
+
expect(executionOrder).not.toContain('config-tool')
|
|
606
|
+
})
|
|
607
|
+
})
|
|
608
|
+
|
|
468
609
|
describe('Tool Dependency Management', () => {
|
|
469
610
|
const widgetId = 'test-widget-deps'
|
|
470
611
|
|