@carto/ps-react-ui 4.3.9 → 4.4.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/components.js +692 -700
- package/dist/components.js.map +1 -1
- package/dist/{lasso-tool-jl4YK02H.js → lasso-tool-BYbxrJ-7.js} +184 -190
- package/dist/lasso-tool-BYbxrJ-7.js.map +1 -0
- package/dist/{row-BKmVAUN5.js → row-DTCV0Ocm.js} +2 -2
- package/dist/row-DTCV0Ocm.js.map +1 -0
- package/dist/{series-D1pynfeh.js → series-CYNOu2Ju.js} +2 -2
- package/dist/{series-D1pynfeh.js.map → series-CYNOu2Ju.js.map} +1 -1
- package/dist/smart-tooltip-D4vwQpFf.js +37 -0
- package/dist/smart-tooltip-D4vwQpFf.js.map +1 -0
- package/dist/{styles-DrPyd0y5.js → styles-CAroD5Rc.js} +12 -12
- package/dist/styles-CAroD5Rc.js.map +1 -0
- package/dist/types/hooks/use-widget-ref.d.ts +5 -2
- package/dist/types/widgets/_shared/chart-config/option-builders.d.ts +1 -1
- package/dist/types/widgets/actions/brush-toggle/brush-toggle.d.ts +21 -0
- package/dist/types/widgets/actions/{relative-data → brush-toggle}/style.d.ts +5 -0
- package/dist/types/widgets/actions/brush-toggle/types.d.ts +33 -0
- package/dist/types/widgets/actions/index.d.ts +2 -0
- package/dist/types/widgets/category/config.d.ts +3 -10
- package/dist/types/widgets/echart/types.d.ts +2 -1
- package/dist/types/widgets/echart/utils.d.ts +12 -6
- package/dist/types/widgets/range/config.d.ts +0 -4
- package/dist/types/widgets/spread/config.d.ts +0 -5
- package/dist/types/widgets/table/config.d.ts +1 -2
- package/dist/types/widgets/table/table-ui.d.ts +1 -1
- package/dist/types/widgets/wrapper/components/options.d.ts +1 -1
- package/dist/use-widget-ref-wtFLDFCD.js +25 -0
- package/dist/use-widget-ref-wtFLDFCD.js.map +1 -0
- package/dist/{utils-idmvq0Oa.js → utils-Cx3gYUcL.js} +74 -68
- package/dist/utils-Cx3gYUcL.js.map +1 -0
- package/dist/widgets/actions.js +788 -700
- package/dist/widgets/actions.js.map +1 -1
- package/dist/widgets/bar.js +48 -49
- package/dist/widgets/bar.js.map +1 -1
- package/dist/widgets/category.js +28 -28
- package/dist/widgets/category.js.map +1 -1
- package/dist/widgets/echart.js +85 -82
- package/dist/widgets/echart.js.map +1 -1
- package/dist/widgets/formula.js +7 -5
- package/dist/widgets/formula.js.map +1 -1
- package/dist/widgets/histogram.js +50 -50
- package/dist/widgets/histogram.js.map +1 -1
- package/dist/widgets/markdown.js +1 -1
- package/dist/widgets/pie.js +10 -10
- package/dist/widgets/pie.js.map +1 -1
- package/dist/widgets/range.js +1 -1
- package/dist/widgets/range.js.map +1 -1
- package/dist/widgets/scatterplot.js +14 -14
- package/dist/widgets/scatterplot.js.map +1 -1
- package/dist/widgets/spread.js +7 -5
- package/dist/widgets/spread.js.map +1 -1
- package/dist/widgets/table.js +5 -3
- package/dist/widgets/table.js.map +1 -1
- package/dist/widgets/timeseries.js +4 -4
- package/dist/widgets/timeseries.js.map +1 -1
- package/dist/widgets/wrapper.js +152 -162
- package/dist/widgets/wrapper.js.map +1 -1
- package/dist/widgets.js +1 -1
- package/package.json +1 -1
- package/src/components/basemaps/basemaps.tsx +3 -1
- package/src/components/geolocation-controls/geolocation-controls.tsx +10 -6
- package/src/components/lasso-tool/lasso-tool-inline.tsx +6 -2
- package/src/components/lasso-tool/lasso-tool.tsx +9 -3
- package/src/components/list-data/list-data-skeleton.tsx +1 -1
- package/src/components/list-data/list-data.tsx +5 -3
- package/src/components/measurement-tools/measurement-tools.tsx +5 -1
- package/src/components/smart-tooltip/smart-tooltip.tsx +3 -1
- package/src/hooks/use-widget-ref.ts +4 -3
- package/src/widgets/_shared/chart-config/option-builders.test.ts +2 -2
- package/src/widgets/_shared/chart-config/option-builders.ts +6 -4
- package/src/widgets/actions/brush-toggle/brush-toggle.tsx +220 -0
- package/src/widgets/actions/{relative-data → brush-toggle}/style.ts +5 -0
- package/src/widgets/actions/brush-toggle/types.ts +37 -0
- package/src/widgets/actions/download/download.test.tsx +6 -2
- package/src/widgets/actions/download/download.tsx +3 -1
- package/src/widgets/actions/fullscreen/fullscreen.tsx +8 -1
- package/src/widgets/actions/index.ts +9 -0
- package/src/widgets/actions/lock-selection/lock-selection.tsx +0 -9
- package/src/widgets/actions/relative-data/relative-data.tsx +2 -6
- package/src/widgets/actions/searcher/searcher.tsx +0 -6
- package/src/widgets/actions/stack-toggle/stack-toggle.test.tsx +4 -4
- package/src/widgets/actions/stack-toggle/stack-toggle.tsx +2 -13
- package/src/widgets/actions/zoom-toggle/zoom-toggle.tsx +2 -12
- package/src/widgets/bar/config.ts +8 -4
- package/src/widgets/bar/skeleton.tsx +1 -1
- package/src/widgets/category/components/category-row-multi.tsx +1 -1
- package/src/widgets/category/config.ts +1 -11
- package/src/widgets/echart/echart-ui.tsx +5 -2
- package/src/widgets/echart/echart.tsx +1 -1
- package/src/widgets/echart/types.ts +2 -1
- package/src/widgets/echart/utils.ts +20 -15
- package/src/widgets/formula/components/row.tsx +1 -1
- package/src/widgets/formula/formula-ui.tsx +1 -1
- package/src/widgets/histogram/config.ts +7 -2
- package/src/widgets/histogram/skeleton.tsx +2 -2
- package/src/widgets/pie/skeleton.tsx +1 -1
- package/src/widgets/range/config.ts +0 -5
- package/src/widgets/scatterplot/skeleton.tsx +2 -2
- package/src/widgets/spread/config.ts +0 -6
- package/src/widgets/spread/spread-ui.tsx +1 -1
- package/src/widgets/table/config.ts +1 -1
- package/src/widgets/table/table-ui.tsx +2 -2
- package/src/widgets/timeseries/skeleton.tsx +1 -1
- package/src/widgets/wrapper/components/actions.test.tsx +6 -2
- package/src/widgets/wrapper/components/actions.tsx +3 -1
- package/src/widgets/wrapper/components/options.test.tsx +12 -4
- package/src/widgets/wrapper/components/options.tsx +8 -3
- package/src/widgets/wrapper/wrapper-ui.tsx +5 -2
- package/src/widgets/wrapper/wrapper.tsx +2 -4
- package/dist/lasso-tool-jl4YK02H.js.map +0 -1
- package/dist/row-BKmVAUN5.js.map +0 -1
- package/dist/smart-tooltip-BEtBaIdz.js +0 -39
- package/dist/smart-tooltip-BEtBaIdz.js.map +0 -1
- package/dist/styles-DrPyd0y5.js.map +0 -1
- package/dist/types/widgets/actions/zoom-toggle/index.d.ts +0 -2
- package/dist/types/widgets/table/components/index.d.ts +0 -4
- package/dist/use-widget-ref-P-2i0MJG.js +0 -19
- package/dist/use-widget-ref-P-2i0MJG.js.map +0 -1
- package/dist/utils-idmvq0Oa.js.map +0 -1
- package/src/widgets/actions/zoom-toggle/index.ts +0 -2
- package/src/widgets/table/components/index.ts +0 -4
|
@@ -12,8 +12,10 @@ const DEFAULT_LABELS = {
|
|
|
12
12
|
showLess: 'Show Less',
|
|
13
13
|
} as const
|
|
14
14
|
|
|
15
|
+
const EMPTY_DATA: ListDataProps['data'] = []
|
|
16
|
+
|
|
15
17
|
export function ListDataUI({
|
|
16
|
-
data =
|
|
18
|
+
data = EMPTY_DATA,
|
|
17
19
|
isLoading = false,
|
|
18
20
|
maxItems = 5,
|
|
19
21
|
labels = DEFAULT_LABELS,
|
|
@@ -47,11 +49,11 @@ export function ListDataUI({
|
|
|
47
49
|
return (
|
|
48
50
|
<>
|
|
49
51
|
<List id='expandable-list' role='list'>
|
|
50
|
-
{items.map((item
|
|
52
|
+
{items.map((item) => (
|
|
51
53
|
<SmartTooltip<HTMLLIElement>
|
|
52
54
|
// Default tooltip props
|
|
53
55
|
followCursor={false}
|
|
54
|
-
key={
|
|
56
|
+
key={item.id}
|
|
55
57
|
placement='top'
|
|
56
58
|
arrow
|
|
57
59
|
title={item.tooltipTitle}
|
|
@@ -49,6 +49,10 @@ import type {
|
|
|
49
49
|
} from '../types'
|
|
50
50
|
import { Tooltip } from '../tooltip/tooltip'
|
|
51
51
|
|
|
52
|
+
const EMPTY_PAPER_PROPS: NonNullable<
|
|
53
|
+
MeasurementToolsComponentProps['PaperProps']
|
|
54
|
+
> = {}
|
|
55
|
+
|
|
52
56
|
export function MeasurementToolsUI({
|
|
53
57
|
enabled,
|
|
54
58
|
actionProps,
|
|
@@ -57,7 +61,7 @@ export function MeasurementToolsUI({
|
|
|
57
61
|
modesMapping = DEFAULT_MEASUREMENT_TOOLS_MODES_MAPPING,
|
|
58
62
|
unitsMapping = DEFAULT_MEASUREMENT_TOOLS_UNITS_MAPPING,
|
|
59
63
|
modeSelected,
|
|
60
|
-
PaperProps: { sx, ...PaperProps } =
|
|
64
|
+
PaperProps: { sx, ...PaperProps } = EMPTY_PAPER_PROPS,
|
|
61
65
|
units,
|
|
62
66
|
unitSelected,
|
|
63
67
|
onActionToggle,
|
|
@@ -2,9 +2,11 @@ import type { TooltipProps } from '@mui/material'
|
|
|
2
2
|
import { useLayoutEffect, useRef, useState, type JSX, type Ref } from 'react'
|
|
3
3
|
import { Tooltip } from '../tooltip/tooltip'
|
|
4
4
|
|
|
5
|
+
const EMPTY_DEPENDENCIES: unknown[] = []
|
|
6
|
+
|
|
5
7
|
export function SmartTooltip<T extends HTMLElement>({
|
|
6
8
|
title,
|
|
7
|
-
dependencies =
|
|
9
|
+
dependencies = EMPTY_DEPENDENCIES,
|
|
8
10
|
timeout = 500,
|
|
9
11
|
TooltipProps,
|
|
10
12
|
children,
|
|
@@ -11,7 +11,7 @@ import { useWidgetStore } from '../widgets/stores/widget-store'
|
|
|
11
11
|
* @example
|
|
12
12
|
* ```tsx
|
|
13
13
|
* function MyWidget({ id }: { id: string }) {
|
|
14
|
-
* const ref = useWidgetRef<HTMLDivElement>(id)
|
|
14
|
+
* const { ref } = useWidgetRef<HTMLDivElement>(id)
|
|
15
15
|
*
|
|
16
16
|
* return <div ref={ref}>Widget content</div>
|
|
17
17
|
* }
|
|
@@ -21,13 +21,14 @@ export function useWidgetRef<T extends HTMLElement = HTMLElement>(
|
|
|
21
21
|
widgetId: string,
|
|
22
22
|
) {
|
|
23
23
|
const ref = useRef<T | null>(null)
|
|
24
|
+
const instance = useRef<echarts.ECharts | null>(null)
|
|
24
25
|
const setWidget = useWidgetStore((store) => store.setWidget)
|
|
25
26
|
|
|
26
27
|
useEffect(() => {
|
|
27
28
|
if (ref.current) {
|
|
28
|
-
setWidget(widgetId, { refUI: ref })
|
|
29
|
+
setWidget(widgetId, { refUI: ref, instance: instance })
|
|
29
30
|
}
|
|
30
31
|
}, [widgetId, setWidget])
|
|
31
32
|
|
|
32
|
-
return ref
|
|
33
|
+
return { ref, instance }
|
|
33
34
|
}
|
|
@@ -6,8 +6,8 @@ describe('niceNum', () => {
|
|
|
6
6
|
expect(niceNum(0)).toBe(0)
|
|
7
7
|
})
|
|
8
8
|
|
|
9
|
-
it('should return
|
|
10
|
-
expect(niceNum(-5)).toBe(
|
|
9
|
+
it('should return the same negative value for negative numbers', () => {
|
|
10
|
+
expect(niceNum(-5)).toBe(-5)
|
|
11
11
|
})
|
|
12
12
|
|
|
13
13
|
it('should round 547 to 600', () => {
|
|
@@ -13,12 +13,14 @@ import type {
|
|
|
13
13
|
* Rounds a value up to the nearest "nice" number.
|
|
14
14
|
* A nice number is a multiple of 10^floor(log10(value)).
|
|
15
15
|
*
|
|
16
|
-
* Examples: 547 → 600, 200 → 200, 1200 → 2000, 18 → 20, 5 → 5
|
|
16
|
+
* Examples: 547 → 600, 200 → 200, 1200 → 2000, 18 → 20, 5 → 5, -547 → -500
|
|
17
17
|
*/
|
|
18
18
|
export function niceNum(value: number): number {
|
|
19
|
-
if (value
|
|
20
|
-
const
|
|
21
|
-
|
|
19
|
+
if (value === 0) return 0
|
|
20
|
+
const absValue = Math.abs(value)
|
|
21
|
+
const base = Math.pow(10, Math.floor(Math.log10(absValue)))
|
|
22
|
+
const rounded = Math.ceil(absValue / base) * base
|
|
23
|
+
return value < 0 ? -rounded : rounded
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
/**
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { Box, IconButton } from '@mui/material'
|
|
2
|
+
import { HighlightAltOutlined } from '@mui/icons-material'
|
|
3
|
+
import { useEffect, useCallback, useRef } from 'react'
|
|
4
|
+
import { useWidgetStore } from '../../stores/widget-store'
|
|
5
|
+
import type { BrushToggleProps } from './types'
|
|
6
|
+
import { styles } from './style'
|
|
7
|
+
import { Tooltip } from '../../../components'
|
|
8
|
+
import { getEChartBrushConfig } from '../../echart/utils'
|
|
9
|
+
import type {
|
|
10
|
+
EchartOptionsProps,
|
|
11
|
+
EchartWidgetData,
|
|
12
|
+
EchartWidgetState,
|
|
13
|
+
} from '../../echart/types'
|
|
14
|
+
import { useShallow } from 'zustand/shallow'
|
|
15
|
+
|
|
16
|
+
export const BRUSH_TOGGLE_TOOL_ID = 'brush-toggle'
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Widget action to toggle EChart brush selection functionality.
|
|
20
|
+
*
|
|
21
|
+
* Registers as a config pipeline tool so that brush configuration is automatically
|
|
22
|
+
* re-applied when the base config is updated (e.g., by WidgetLoader).
|
|
23
|
+
*
|
|
24
|
+
* When brush is active, users can drag across bars to select a range.
|
|
25
|
+
* Selection clearing is handled via the chart's brush interactions/toolbox configuration.
|
|
26
|
+
* Only intended for use in fullscreen ToolbarActions for bar and histogram widgets.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```tsx
|
|
30
|
+
* <BrushToggle
|
|
31
|
+
* id="my-widget"
|
|
32
|
+
* onBrushSelected={(items) => console.log(items)}
|
|
33
|
+
* />
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export function BrushToggle({
|
|
37
|
+
id,
|
|
38
|
+
onBrushSelected,
|
|
39
|
+
labels,
|
|
40
|
+
Icon,
|
|
41
|
+
IconButtonProps,
|
|
42
|
+
}: BrushToggleProps) {
|
|
43
|
+
const getWidget = useWidgetStore((state) => state.getWidget)
|
|
44
|
+
const registerTool = useWidgetStore((state) => state.registerTool)
|
|
45
|
+
const unregisterTool = useWidgetStore((state) => state.unregisterTool)
|
|
46
|
+
const setToolEnabled = useWidgetStore((state) => state.setToolEnabled)
|
|
47
|
+
const selected = useRef<(string | number)[]>([])
|
|
48
|
+
|
|
49
|
+
const brushTool = useWidgetStore(
|
|
50
|
+
useShallow((state) => {
|
|
51
|
+
const tools = state.getWidget(id)?.registeredTools ?? []
|
|
52
|
+
return tools.find((tool) => tool.id === BRUSH_TOGGLE_TOOL_ID)
|
|
53
|
+
}),
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
const brush = brushTool?.enabled
|
|
57
|
+
|
|
58
|
+
const toggleTool = useCallback(
|
|
59
|
+
(value: boolean) => {
|
|
60
|
+
setToolEnabled(id, BRUSH_TOGGLE_TOOL_ID, value)
|
|
61
|
+
},
|
|
62
|
+
[id, setToolEnabled],
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
const handleToggle = useCallback(() => {
|
|
66
|
+
const newBrush = !brush
|
|
67
|
+
toggleTool(newBrush)
|
|
68
|
+
|
|
69
|
+
if (newBrush) {
|
|
70
|
+
onBrushSelected?.([])
|
|
71
|
+
}
|
|
72
|
+
}, [brush, onBrushSelected, toggleTool])
|
|
73
|
+
|
|
74
|
+
// Re-dispatch brush action after every ECharts render while brush is active
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
if (!brush) return
|
|
77
|
+
|
|
78
|
+
const instance = getWidget<EchartWidgetState>(id)?.instance?.current
|
|
79
|
+
if (!instance) return
|
|
80
|
+
|
|
81
|
+
const handleRendered = () => {
|
|
82
|
+
const config = getEChartBrushConfig()
|
|
83
|
+
instance.dispatchAction({
|
|
84
|
+
type: 'takeGlobalCursor',
|
|
85
|
+
key: 'brush',
|
|
86
|
+
brushOption: {
|
|
87
|
+
brushType: config.brush.brushType,
|
|
88
|
+
brushMode: config.brush.brushMode,
|
|
89
|
+
},
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Dispatch immediately for the initial activation
|
|
94
|
+
handleRendered()
|
|
95
|
+
|
|
96
|
+
// Re-dispatch after each render (covers setOption with notMerge:true)
|
|
97
|
+
instance.on('rendered', handleRendered)
|
|
98
|
+
|
|
99
|
+
return () => {
|
|
100
|
+
instance.off('rendered', handleRendered)
|
|
101
|
+
}
|
|
102
|
+
}, [brush, getWidget, id])
|
|
103
|
+
|
|
104
|
+
// Handle brushSelected event to capture selected bar indices
|
|
105
|
+
const handleBrushSelected = useCallback(
|
|
106
|
+
(event: unknown) => {
|
|
107
|
+
const brushEvent = event as {
|
|
108
|
+
batch?: {
|
|
109
|
+
selected?: {
|
|
110
|
+
dataIndex?: number[]
|
|
111
|
+
seriesIndex?: number
|
|
112
|
+
}[]
|
|
113
|
+
}[]
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Resolve names from the widget dataset
|
|
117
|
+
const widget = getWidget(id)
|
|
118
|
+
const data = (widget?.data ?? []) as EchartWidgetData
|
|
119
|
+
|
|
120
|
+
const items = [
|
|
121
|
+
...new Set(
|
|
122
|
+
brushEvent.batch?.flatMap(
|
|
123
|
+
(batch) =>
|
|
124
|
+
batch.selected?.flatMap(
|
|
125
|
+
(sel) =>
|
|
126
|
+
sel.dataIndex?.map((dataIndex) => {
|
|
127
|
+
const row = data[sel.seriesIndex ?? 0]?.[dataIndex]
|
|
128
|
+
const firstValue = row ? Object.values(row)[0] : undefined
|
|
129
|
+
const name =
|
|
130
|
+
typeof firstValue === 'string' ||
|
|
131
|
+
typeof firstValue === 'number'
|
|
132
|
+
? String(firstValue)
|
|
133
|
+
: String(dataIndex)
|
|
134
|
+
return name
|
|
135
|
+
}) ?? [],
|
|
136
|
+
) ?? [],
|
|
137
|
+
),
|
|
138
|
+
),
|
|
139
|
+
]
|
|
140
|
+
|
|
141
|
+
selected.current = items ?? []
|
|
142
|
+
},
|
|
143
|
+
[getWidget, id],
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
const handleBrushEnd = useCallback(() => {
|
|
147
|
+
onBrushSelected?.(selected.current)
|
|
148
|
+
toggleTool(false) // Disable brush after selection is made
|
|
149
|
+
}, [onBrushSelected, toggleTool])
|
|
150
|
+
|
|
151
|
+
// Register config tool on mount
|
|
152
|
+
useEffect(() => {
|
|
153
|
+
const existingTool = getWidget(id)?.registeredTools?.find(
|
|
154
|
+
(tool) => tool.id === BRUSH_TOGGLE_TOOL_ID,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
const initialEnabled = existingTool?.enabled ?? false
|
|
158
|
+
|
|
159
|
+
registerTool(id, {
|
|
160
|
+
id: BRUSH_TOGGLE_TOOL_ID,
|
|
161
|
+
type: 'config',
|
|
162
|
+
order: 25,
|
|
163
|
+
enabled: initialEnabled,
|
|
164
|
+
fn: (currentConfig) => {
|
|
165
|
+
const config = currentConfig as Record<string, unknown>
|
|
166
|
+
const option = config.option as EchartOptionsProps | undefined
|
|
167
|
+
const currentOnEvents =
|
|
168
|
+
(config.onEvents as Record<string, unknown> | undefined) ?? {}
|
|
169
|
+
|
|
170
|
+
const brushConfig = getEChartBrushConfig()
|
|
171
|
+
|
|
172
|
+
const onEventsWithoutBrush = { ...currentOnEvents }
|
|
173
|
+
delete onEventsWithoutBrush.brushSelected
|
|
174
|
+
const onEvents = {
|
|
175
|
+
...currentOnEvents,
|
|
176
|
+
brushSelected: handleBrushSelected,
|
|
177
|
+
brushEnd: handleBrushEnd,
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
...config,
|
|
182
|
+
option: {
|
|
183
|
+
...option,
|
|
184
|
+
...brushConfig,
|
|
185
|
+
},
|
|
186
|
+
onEvents,
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
})
|
|
190
|
+
return () => unregisterTool(id, BRUSH_TOGGLE_TOOL_ID)
|
|
191
|
+
}, [
|
|
192
|
+
getWidget,
|
|
193
|
+
handleBrushEnd,
|
|
194
|
+
handleBrushSelected,
|
|
195
|
+
id,
|
|
196
|
+
registerTool,
|
|
197
|
+
unregisterTool,
|
|
198
|
+
])
|
|
199
|
+
|
|
200
|
+
const enableLabel = labels?.enable ?? 'Enable brush selection'
|
|
201
|
+
const disableLabel = labels?.disable ?? 'Disable brush selection'
|
|
202
|
+
const tooltipLabel = brush ? disableLabel : enableLabel
|
|
203
|
+
|
|
204
|
+
return (
|
|
205
|
+
<Box sx={styles.container}>
|
|
206
|
+
<Tooltip title={tooltipLabel}>
|
|
207
|
+
<IconButton
|
|
208
|
+
size='small'
|
|
209
|
+
aria-label={labels?.ariaLabel ?? tooltipLabel}
|
|
210
|
+
onClick={handleToggle}
|
|
211
|
+
sx={styles.trigger}
|
|
212
|
+
data-active={brush}
|
|
213
|
+
{...IconButtonProps}
|
|
214
|
+
>
|
|
215
|
+
{Icon ?? <HighlightAltOutlined />}
|
|
216
|
+
</IconButton>
|
|
217
|
+
</Tooltip>
|
|
218
|
+
</Box>
|
|
219
|
+
)
|
|
220
|
+
}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { SxProps, Theme } from '@mui/material'
|
|
2
2
|
|
|
3
3
|
export const styles = {
|
|
4
|
+
container: {
|
|
5
|
+
display: 'flex',
|
|
6
|
+
alignItems: 'center',
|
|
7
|
+
gap: ({ spacing }) => spacing(0.25),
|
|
8
|
+
},
|
|
4
9
|
trigger: {
|
|
5
10
|
'&[data-active="true"]': {
|
|
6
11
|
background: (theme: Theme) => theme.palette.primary.relatedLight,
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { IconButtonProps } from '@mui/material'
|
|
2
|
+
import type { ReactNode } from 'react'
|
|
3
|
+
import type { BaseWidgetState } from '../../stores/types'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Represents a single item selected by the brush
|
|
7
|
+
*/
|
|
8
|
+
export type BrushSelectedItems = (string | number)[]
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* State stored in widget store for brush functionality
|
|
12
|
+
*/
|
|
13
|
+
export interface BrushConfig {
|
|
14
|
+
brush?: boolean
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type BrushState<T = unknown> = BaseWidgetState<T & BrushConfig>
|
|
18
|
+
|
|
19
|
+
export interface BrushToggleProps {
|
|
20
|
+
/** Widget ID to update brush state in the widget store */
|
|
21
|
+
id: string
|
|
22
|
+
/** Callback fired when items are selected via brush */
|
|
23
|
+
onBrushSelected?: (items: BrushSelectedItems) => void
|
|
24
|
+
/** Custom labels for the action */
|
|
25
|
+
labels?: {
|
|
26
|
+
/** Tooltip when brush is disabled (button will enable brush) */
|
|
27
|
+
enable?: string
|
|
28
|
+
/** Tooltip when brush is enabled (button will disable brush) */
|
|
29
|
+
disable?: string
|
|
30
|
+
/** Accessibility label */
|
|
31
|
+
ariaLabel?: string
|
|
32
|
+
}
|
|
33
|
+
/** Props passed to the IconButton component */
|
|
34
|
+
IconButtonProps?: IconButtonProps
|
|
35
|
+
/** Custom icon to display for brush toggle */
|
|
36
|
+
Icon?: ReactNode
|
|
37
|
+
}
|
|
@@ -271,8 +271,12 @@ describe('Download', () => {
|
|
|
271
271
|
const parentClickHandler = vi.fn()
|
|
272
272
|
|
|
273
273
|
render(
|
|
274
|
-
|
|
275
|
-
|
|
274
|
+
<div
|
|
275
|
+
role='button'
|
|
276
|
+
tabIndex={0}
|
|
277
|
+
onClick={parentClickHandler}
|
|
278
|
+
onKeyDown={parentClickHandler}
|
|
279
|
+
>
|
|
276
280
|
<Download id={widgetId} items={mockDownload} />
|
|
277
281
|
</div>,
|
|
278
282
|
)
|
|
@@ -12,10 +12,12 @@ import { useState, type MouseEvent } from 'react'
|
|
|
12
12
|
import { useWidgetStore } from '../../../widgets'
|
|
13
13
|
import { useShallow } from 'zustand/shallow'
|
|
14
14
|
|
|
15
|
+
const EMPTY_LABELS: NonNullable<DownloadProps['labels']> = {}
|
|
16
|
+
|
|
15
17
|
export function Download({
|
|
16
18
|
id,
|
|
17
19
|
items,
|
|
18
|
-
labels =
|
|
20
|
+
labels = EMPTY_LABELS,
|
|
19
21
|
Icon,
|
|
20
22
|
IconButtonProps,
|
|
21
23
|
}: DownloadProps) {
|
|
@@ -15,13 +15,20 @@ import type {
|
|
|
15
15
|
import { styles } from './styles'
|
|
16
16
|
import { useShallow } from 'zustand/shallow'
|
|
17
17
|
|
|
18
|
+
const EMPTY_DIALOG_CONTENT_PROPS: NonNullable<
|
|
19
|
+
FullScreenProps['DialogContentProps']
|
|
20
|
+
> = {}
|
|
21
|
+
|
|
18
22
|
export function FullScreen({
|
|
19
23
|
id,
|
|
20
24
|
labels,
|
|
21
25
|
children,
|
|
22
26
|
Icon,
|
|
23
27
|
IconButtonProps,
|
|
24
|
-
DialogContentProps: {
|
|
28
|
+
DialogContentProps: {
|
|
29
|
+
sx,
|
|
30
|
+
...DialogContentProps
|
|
31
|
+
} = EMPTY_DIALOG_CONTENT_PROPS,
|
|
25
32
|
DialogProps,
|
|
26
33
|
}: FullScreenProps) {
|
|
27
34
|
const isFullScreen = useWidgetStore(
|
|
@@ -52,3 +52,12 @@ export type {
|
|
|
52
52
|
LockSelectionProps,
|
|
53
53
|
LockSelectionState,
|
|
54
54
|
} from './lock-selection/types'
|
|
55
|
+
|
|
56
|
+
/* Brush Toggle Widget */
|
|
57
|
+
export { BrushToggle, BRUSH_TOGGLE_TOOL_ID } from './brush-toggle/brush-toggle'
|
|
58
|
+
export type {
|
|
59
|
+
BrushToggleProps,
|
|
60
|
+
BrushState,
|
|
61
|
+
BrushConfig,
|
|
62
|
+
BrushSelectedItems,
|
|
63
|
+
} from './brush-toggle/types'
|
|
@@ -37,9 +37,6 @@ export function LockSelection({
|
|
|
37
37
|
const setWidget = useWidgetStore((state) => state.setWidget)
|
|
38
38
|
const registerTool = useWidgetStore((state) => state.registerTool)
|
|
39
39
|
const unregisterTool = useWidgetStore((state) => state.unregisterTool)
|
|
40
|
-
const setToolEnabled = useWidgetStore((state) => state.setToolEnabled)
|
|
41
|
-
const updateToolConfig = useWidgetStore((state) => state.updateToolConfig)
|
|
42
|
-
|
|
43
40
|
const storeIsLocked = useWidgetStore(
|
|
44
41
|
useShallow((state) => state.getWidget<LockSelectionState>(id)?.isLocked),
|
|
45
42
|
)
|
|
@@ -68,12 +65,6 @@ export function LockSelection({
|
|
|
68
65
|
return () => unregisterTool(id, LOCK_SELECTION_TOOL_ID)
|
|
69
66
|
}, [id, order, registerTool, unregisterTool, isLocked, lockedItems])
|
|
70
67
|
|
|
71
|
-
// Update enabled flag and config when they change
|
|
72
|
-
useEffect(() => {
|
|
73
|
-
setToolEnabled(id, LOCK_SELECTION_TOOL_ID, isLocked)
|
|
74
|
-
updateToolConfig(id, LOCK_SELECTION_TOOL_ID, { lockedItems })
|
|
75
|
-
}, [id, isLocked, lockedItems, setToolEnabled, updateToolConfig])
|
|
76
|
-
|
|
77
68
|
const handleToggle = useCallback(() => {
|
|
78
69
|
if (isLocked) {
|
|
79
70
|
// Unlock: clear locked items and disable tool
|
|
@@ -73,13 +73,9 @@ export function RelativeData({
|
|
|
73
73
|
return () => unregisterTool(id, RELATIVE_DATA_TOOL_ID)
|
|
74
74
|
}, [id, order, registerTool, unregisterTool, isRelative])
|
|
75
75
|
|
|
76
|
-
// Update enabled flag when toggle changes
|
|
77
|
-
useEffect(() => {
|
|
78
|
-
setToolEnabled(id, RELATIVE_DATA_TOOL_ID, isRelative)
|
|
79
|
-
}, [id, isRelative, setToolEnabled])
|
|
80
|
-
|
|
81
76
|
const handleToggle = useCallback(() => {
|
|
82
77
|
const newIsRelative = !isRelative
|
|
78
|
+
setToolEnabled(id, RELATIVE_DATA_TOOL_ID, newIsRelative)
|
|
83
79
|
let max = previousMaxValue.current
|
|
84
80
|
|
|
85
81
|
if (newIsRelative) {
|
|
@@ -110,7 +106,7 @@ export function RelativeData({
|
|
|
110
106
|
}
|
|
111
107
|
: originalFormatter.current,
|
|
112
108
|
})
|
|
113
|
-
}, [isRelative, setWidget, id, getWidget])
|
|
109
|
+
}, [isRelative, setWidget, id, getWidget, setToolEnabled])
|
|
114
110
|
|
|
115
111
|
const tooltipLabel = isRelative
|
|
116
112
|
? (labels?.absolute ?? 'Show absolute values')
|
|
@@ -53,7 +53,6 @@ export function Searcher({
|
|
|
53
53
|
const setWidget = useWidgetStore((state) => state.setWidget)
|
|
54
54
|
const registerTool = useWidgetStore((state) => state.registerTool)
|
|
55
55
|
const unregisterTool = useWidgetStore((state) => state.unregisterTool)
|
|
56
|
-
const setToolEnabled = useWidgetStore((state) => state.setToolEnabled)
|
|
57
56
|
const updateToolConfig = useWidgetStore((state) => state.updateToolConfig)
|
|
58
57
|
|
|
59
58
|
const setSearchText = useCallback(
|
|
@@ -85,11 +84,6 @@ export function Searcher({
|
|
|
85
84
|
return () => unregisterTool(id, SEARCHER_TOOL_ID)
|
|
86
85
|
}, [id, order, filter, registerTool, unregisterTool, enabled, searchText])
|
|
87
86
|
|
|
88
|
-
// Update enabled flag when it changes
|
|
89
|
-
useEffect(() => {
|
|
90
|
-
setToolEnabled(id, SEARCHER_TOOL_ID, enabled)
|
|
91
|
-
}, [id, enabled, setToolEnabled])
|
|
92
|
-
|
|
93
87
|
// Update config when search text changes (debounced)
|
|
94
88
|
const debouncedUpdateConfig = useCallback(
|
|
95
89
|
(text: string) => {
|
|
@@ -49,7 +49,7 @@ describe('StackToggle', () => {
|
|
|
49
49
|
const tool = widget?.registeredTools?.find(
|
|
50
50
|
(t) => t.id === STACK_TOGGLE_TOOL_ID,
|
|
51
51
|
)
|
|
52
|
-
expect(tool?.
|
|
52
|
+
expect(tool?.enabled).toBe(true)
|
|
53
53
|
})
|
|
54
54
|
|
|
55
55
|
test('toggles back to unstacked mode', () => {
|
|
@@ -67,7 +67,7 @@ describe('StackToggle', () => {
|
|
|
67
67
|
const tool = widget?.registeredTools?.find(
|
|
68
68
|
(t) => t.id === STACK_TOGGLE_TOOL_ID,
|
|
69
69
|
)
|
|
70
|
-
expect(tool?.
|
|
70
|
+
expect(tool?.enabled).toBe(false)
|
|
71
71
|
})
|
|
72
72
|
|
|
73
73
|
test('has active state when in stacked mode', () => {
|
|
@@ -112,7 +112,7 @@ describe('StackToggle', () => {
|
|
|
112
112
|
const tool = widget?.registeredTools?.find(
|
|
113
113
|
(t) => t.id === STACK_TOGGLE_TOOL_ID,
|
|
114
114
|
)
|
|
115
|
-
expect(tool?.
|
|
115
|
+
expect(tool?.enabled).toBe(false)
|
|
116
116
|
})
|
|
117
117
|
|
|
118
118
|
test('initializes tool config with stacked values when defaultIsStacked is true', () => {
|
|
@@ -122,7 +122,7 @@ describe('StackToggle', () => {
|
|
|
122
122
|
const tool = widget?.registeredTools?.find(
|
|
123
123
|
(t) => t.id === STACK_TOGGLE_TOOL_ID,
|
|
124
124
|
)
|
|
125
|
-
expect(tool?.
|
|
125
|
+
expect(tool?.enabled).toBe(true)
|
|
126
126
|
})
|
|
127
127
|
|
|
128
128
|
test('applies stack to EChart series via config pipeline when toggling to stacked', async () => {
|
|
@@ -37,9 +37,6 @@ export function StackToggle({
|
|
|
37
37
|
const setWidget = useWidgetStore((state) => state.setWidget)
|
|
38
38
|
const registerTool = useWidgetStore((state) => state.registerTool)
|
|
39
39
|
const unregisterTool = useWidgetStore((state) => state.unregisterTool)
|
|
40
|
-
const setToolEnabled = useWidgetStore((state) => state.setToolEnabled)
|
|
41
|
-
const updateToolConfig = useWidgetStore((state) => state.updateToolConfig)
|
|
42
|
-
|
|
43
40
|
const storeIsStacked = useWidgetStore(
|
|
44
41
|
useShallow((state) => state.getWidget<StackToggleState>(id)?.isStacked),
|
|
45
42
|
)
|
|
@@ -70,12 +67,11 @@ export function StackToggle({
|
|
|
70
67
|
type: 'config',
|
|
71
68
|
order: 10,
|
|
72
69
|
enabled: isStacked && hasMultiSeries,
|
|
73
|
-
fn: (currentConfig
|
|
70
|
+
fn: (currentConfig) => {
|
|
74
71
|
const config = currentConfig as Record<string, unknown>
|
|
75
72
|
const option = config.option as EchartOptionsProps | undefined
|
|
76
73
|
if (!option) return currentConfig
|
|
77
74
|
|
|
78
|
-
const stacked = (toolConfig?.stacked as boolean) ?? false
|
|
79
75
|
const series = Array.isArray(option.series)
|
|
80
76
|
? option.series
|
|
81
77
|
: [option.series]
|
|
@@ -85,22 +81,15 @@ export function StackToggle({
|
|
|
85
81
|
typeof existingStack === 'string'
|
|
86
82
|
? existingStack
|
|
87
83
|
: DEFAULT_STACK_GROUP
|
|
88
|
-
return { ...s, ...getEChartStackConfig(
|
|
84
|
+
return { ...s, ...getEChartStackConfig(stackGroup) }
|
|
89
85
|
})
|
|
90
86
|
|
|
91
87
|
return { ...config, option: { ...option, series: updatedSeries } }
|
|
92
88
|
},
|
|
93
|
-
config: { stacked: isStacked },
|
|
94
89
|
})
|
|
95
90
|
return () => unregisterTool(id, STACK_TOGGLE_TOOL_ID)
|
|
96
91
|
}, [id, registerTool, unregisterTool, isStacked, hasMultiSeries])
|
|
97
92
|
|
|
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
93
|
// Initialize store with default value only if not already configured
|
|
105
94
|
useEffect(() => {
|
|
106
95
|
if (storeIsStacked !== undefined) return
|
|
@@ -81,7 +81,6 @@ export function ZoomToggle({
|
|
|
81
81
|
if (start !== undefined && end !== undefined) {
|
|
82
82
|
setToolEnabled(id, ZOOM_TOGGLE_TOOL_ID, true)
|
|
83
83
|
updateToolConfig(id, ZOOM_TOGGLE_TOOL_ID, {
|
|
84
|
-
enabled: true,
|
|
85
84
|
start,
|
|
86
85
|
end,
|
|
87
86
|
})
|
|
@@ -113,7 +112,6 @@ export function ZoomToggle({
|
|
|
113
112
|
const currentOnEvents =
|
|
114
113
|
(config.onEvents as Record<string, unknown> | undefined) ?? {}
|
|
115
114
|
|
|
116
|
-
const enabled = (toolConfig?.enabled as boolean) ?? false
|
|
117
115
|
const start = (toolConfig?.start as number) ?? 0
|
|
118
116
|
const end = (toolConfig?.end as number) ?? 100
|
|
119
117
|
|
|
@@ -125,7 +123,6 @@ export function ZoomToggle({
|
|
|
125
123
|
const legendBottomOffset = hasLegend ? 28 : 0
|
|
126
124
|
|
|
127
125
|
const zoomConfig = getEChartZoomConfig(
|
|
128
|
-
enabled,
|
|
129
126
|
{ start, end },
|
|
130
127
|
{
|
|
131
128
|
inside: true,
|
|
@@ -142,15 +139,11 @@ export function ZoomToggle({
|
|
|
142
139
|
? grid.bottom
|
|
143
140
|
: parseInt(grid?.bottom ?? '24')
|
|
144
141
|
|
|
145
|
-
const gridBottom =
|
|
146
|
-
? currentGridBottom + sliderHeight + sliderGap
|
|
147
|
-
: currentGridBottom
|
|
142
|
+
const gridBottom = currentGridBottom + sliderHeight + sliderGap
|
|
148
143
|
|
|
149
144
|
const onEventsWithoutDataZoom = { ...currentOnEvents }
|
|
150
145
|
delete onEventsWithoutDataZoom.dataZoom
|
|
151
|
-
const onEvents =
|
|
152
|
-
? { ...currentOnEvents, dataZoom: handleDataZoom }
|
|
153
|
-
: onEventsWithoutDataZoom
|
|
146
|
+
const onEvents = { ...currentOnEvents, dataZoom: handleDataZoom }
|
|
154
147
|
|
|
155
148
|
return {
|
|
156
149
|
...config,
|
|
@@ -163,7 +156,6 @@ export function ZoomToggle({
|
|
|
163
156
|
}
|
|
164
157
|
},
|
|
165
158
|
config: {
|
|
166
|
-
enabled: initialEnabled,
|
|
167
159
|
start: initialStart,
|
|
168
160
|
end: initialEnd,
|
|
169
161
|
},
|
|
@@ -185,7 +177,6 @@ export function ZoomToggle({
|
|
|
185
177
|
const newZoom = !zoom
|
|
186
178
|
setToolEnabled(id, ZOOM_TOGGLE_TOOL_ID, newZoom)
|
|
187
179
|
updateToolConfig(id, ZOOM_TOGGLE_TOOL_ID, {
|
|
188
|
-
enabled: newZoom,
|
|
189
180
|
start: newZoom ? zoomStart : 0,
|
|
190
181
|
end: newZoom ? zoomEnd : 100,
|
|
191
182
|
})
|
|
@@ -194,7 +185,6 @@ export function ZoomToggle({
|
|
|
194
185
|
const handleReset = () => {
|
|
195
186
|
setToolEnabled(id, ZOOM_TOGGLE_TOOL_ID, true)
|
|
196
187
|
updateToolConfig(id, ZOOM_TOGGLE_TOOL_ID, {
|
|
197
|
-
enabled: true,
|
|
198
188
|
start: defaultZoomStart,
|
|
199
189
|
end: defaultZoomEnd,
|
|
200
190
|
})
|