@carto/ps-react-ui 4.3.7 → 4.3.9
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/types/widgets/actions/change-column/change-column.d.ts +4 -0
- package/dist/types/widgets/actions/fullscreen/fullscreen.d.ts +1 -1
- package/dist/types/widgets/actions/fullscreen/types.d.ts +2 -1
- package/dist/types/widgets/actions/index.d.ts +3 -3
- package/dist/types/widgets/actions/lock-selection/types.d.ts +13 -0
- package/dist/types/widgets/actions/relative-data/types.d.ts +4 -0
- package/dist/types/widgets/actions/searcher/types.d.ts +2 -0
- package/dist/types/widgets/actions/stack-toggle/types.d.ts +4 -0
- package/dist/types/widgets/table/components/cell.d.ts +4 -2
- package/dist/types/widgets/table/types.d.ts +1 -1
- package/dist/widgets/actions.js +714 -671
- package/dist/widgets/actions.js.map +1 -1
- package/dist/widgets/loader.js +58 -49
- package/dist/widgets/loader.js.map +1 -1
- package/dist/widgets/table.js +241 -240
- package/dist/widgets/table.js.map +1 -1
- package/package.json +3 -3
- package/src/widgets/actions/change-column/change-column.test.tsx +129 -2
- package/src/widgets/actions/change-column/change-column.tsx +79 -2
- package/src/widgets/actions/fullscreen/fullscreen.tsx +3 -0
- package/src/widgets/actions/fullscreen/types.ts +6 -1
- package/src/widgets/actions/index.ts +9 -3
- package/src/widgets/actions/lock-selection/lock-selection.tsx +26 -25
- package/src/widgets/actions/lock-selection/types.ts +17 -0
- package/src/widgets/actions/relative-data/relative-data.tsx +21 -18
- package/src/widgets/actions/relative-data/types.ts +7 -0
- package/src/widgets/actions/searcher/searcher.tsx +22 -40
- package/src/widgets/actions/searcher/types.ts +2 -0
- package/src/widgets/actions/stack-toggle/stack-toggle.tsx +22 -32
- package/src/widgets/actions/stack-toggle/types.ts +8 -0
- package/src/widgets/loader/loader.tsx +25 -24
- package/src/widgets/table/components/cell.tsx +5 -3
- package/src/widgets/table/components/row.tsx +6 -1
- package/src/widgets/table/types.ts +1 -1
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
DialogContentProps,
|
|
3
|
+
IconButtonProps,
|
|
4
|
+
DialogProps,
|
|
5
|
+
} from '@mui/material'
|
|
2
6
|
import type { ReactNode } from 'react'
|
|
3
7
|
import type { BaseWidgetState } from '../../../widgets/stores/types'
|
|
4
8
|
|
|
@@ -22,6 +26,7 @@ export interface FullScreenProps {
|
|
|
22
26
|
}
|
|
23
27
|
children: ReactNode
|
|
24
28
|
DialogContentProps?: DialogContentProps
|
|
29
|
+
DialogProps?: DialogProps
|
|
25
30
|
IconButtonProps?: IconButtonProps
|
|
26
31
|
Icon?: ReactNode
|
|
27
32
|
}
|
|
@@ -24,7 +24,7 @@ export type {
|
|
|
24
24
|
|
|
25
25
|
/* Stack Toggle Widget */
|
|
26
26
|
export { StackToggle, STACK_TOGGLE_TOOL_ID } from './stack-toggle/stack-toggle'
|
|
27
|
-
export type { StackToggleProps } from './stack-toggle/types'
|
|
27
|
+
export type { StackToggleProps, StackToggleState } from './stack-toggle/types'
|
|
28
28
|
|
|
29
29
|
/* Searcher Toggle Widget */
|
|
30
30
|
export { Searcher, SEARCHER_TOOL_ID } from './searcher/searcher'
|
|
@@ -37,7 +37,10 @@ export type {
|
|
|
37
37
|
} from './searcher/types'
|
|
38
38
|
|
|
39
39
|
/* Change Column Widget */
|
|
40
|
-
export {
|
|
40
|
+
export {
|
|
41
|
+
ChangeColumn,
|
|
42
|
+
CHANGE_COLUMN_TOOL_ID,
|
|
43
|
+
} from './change-column/change-column'
|
|
41
44
|
export type { ChangeColumnProps } from './change-column/types'
|
|
42
45
|
|
|
43
46
|
/* Lock Selection Widget */
|
|
@@ -45,4 +48,7 @@ export {
|
|
|
45
48
|
LockSelection,
|
|
46
49
|
LOCK_SELECTION_TOOL_ID,
|
|
47
50
|
} from './lock-selection/lock-selection'
|
|
48
|
-
export type {
|
|
51
|
+
export type {
|
|
52
|
+
LockSelectionProps,
|
|
53
|
+
LockSelectionState,
|
|
54
|
+
} from './lock-selection/types'
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { IconButton } from '@mui/material'
|
|
2
2
|
import { CheckBoxOutlined } from '@mui/icons-material'
|
|
3
|
-
import { useCallback, useEffect } from 'react'
|
|
4
|
-
import type { LockSelectionProps } from './types'
|
|
3
|
+
import { useCallback, useEffect, useMemo } from 'react'
|
|
4
|
+
import type { LockSelectionProps, LockSelectionState } from './types'
|
|
5
5
|
import { actionButtonStyles } from '../shared/styles'
|
|
6
6
|
import { Tooltip } from '../../../components'
|
|
7
7
|
import { useWidgetStore } from '../../stores/widget-store'
|
|
@@ -34,60 +34,61 @@ export function LockSelection({
|
|
|
34
34
|
Icon,
|
|
35
35
|
IconButtonProps,
|
|
36
36
|
}: LockSelectionProps) {
|
|
37
|
-
const
|
|
37
|
+
const setWidget = useWidgetStore((state) => state.setWidget)
|
|
38
38
|
const registerTool = useWidgetStore((state) => state.registerTool)
|
|
39
39
|
const unregisterTool = useWidgetStore((state) => state.unregisterTool)
|
|
40
40
|
const setToolEnabled = useWidgetStore((state) => state.setToolEnabled)
|
|
41
41
|
const updateToolConfig = useWidgetStore((state) => state.updateToolConfig)
|
|
42
42
|
|
|
43
|
-
const
|
|
44
|
-
useShallow((state) =>
|
|
45
|
-
const tools = state.getWidget(id)?.registeredTools ?? []
|
|
46
|
-
return tools.find((tool) => tool.id === LOCK_SELECTION_TOOL_ID)
|
|
47
|
-
}),
|
|
43
|
+
const storeIsLocked = useWidgetStore(
|
|
44
|
+
useShallow((state) => state.getWidget<LockSelectionState>(id)?.isLocked),
|
|
48
45
|
)
|
|
49
46
|
|
|
50
|
-
const isLocked =
|
|
47
|
+
const isLocked = storeIsLocked ?? false
|
|
48
|
+
const lockedItems = useMemo(
|
|
49
|
+
() => (isLocked ? selectedItems : []),
|
|
50
|
+
[isLocked, selectedItems],
|
|
51
|
+
)
|
|
51
52
|
|
|
52
53
|
// Register tool on mount
|
|
53
54
|
useEffect(() => {
|
|
54
|
-
const existingTool = getWidget(id)?.registeredTools?.find(
|
|
55
|
-
(tool) => tool.id === LOCK_SELECTION_TOOL_ID,
|
|
56
|
-
)
|
|
57
|
-
|
|
58
|
-
const initialEnabled = existingTool?.enabled ?? false
|
|
59
|
-
const initialLockedItems =
|
|
60
|
-
(existingTool?.config?.lockedItems as string[]) ?? []
|
|
61
|
-
|
|
62
55
|
registerTool(id, {
|
|
63
56
|
id: LOCK_SELECTION_TOOL_ID,
|
|
64
57
|
order,
|
|
65
|
-
enabled:
|
|
58
|
+
enabled: isLocked,
|
|
66
59
|
fn: (data, config) => {
|
|
67
60
|
const items = (config?.lockedItems as string[]) || []
|
|
68
61
|
if (items.length === 0) return data
|
|
69
62
|
|
|
70
63
|
return filterDataByLockedItems(data as EchartWidgetData, items)
|
|
71
64
|
},
|
|
72
|
-
config: { lockedItems
|
|
65
|
+
config: { lockedItems },
|
|
73
66
|
})
|
|
74
67
|
|
|
75
68
|
return () => unregisterTool(id, LOCK_SELECTION_TOOL_ID)
|
|
76
|
-
}, [id, order, registerTool, unregisterTool,
|
|
69
|
+
}, [id, order, registerTool, unregisterTool, isLocked, lockedItems])
|
|
70
|
+
|
|
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])
|
|
77
76
|
|
|
78
77
|
const handleToggle = useCallback(() => {
|
|
79
78
|
if (isLocked) {
|
|
80
79
|
// Unlock: clear locked items and disable tool
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
setWidget(id, {
|
|
81
|
+
isLocked: false,
|
|
82
|
+
lockedItems: [],
|
|
83
|
+
})
|
|
83
84
|
} else {
|
|
84
85
|
// Lock: save selected items and enable tool
|
|
85
|
-
|
|
86
|
-
|
|
86
|
+
setWidget(id, {
|
|
87
|
+
isLocked: true,
|
|
87
88
|
lockedItems: selectedItems,
|
|
88
89
|
})
|
|
89
90
|
}
|
|
90
|
-
}, [id, isLocked, selectedItems,
|
|
91
|
+
}, [id, isLocked, selectedItems, setWidget])
|
|
91
92
|
|
|
92
93
|
// Don't render if no selections
|
|
93
94
|
if (selectedItems.length === 0) {
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import type { IconButtonProps } from '@mui/material'
|
|
2
2
|
import type { ReactNode } from 'react'
|
|
3
|
+
import type { BaseWidgetState } from '../../stores/types'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Widget state extension for lock selection functionality.
|
|
7
|
+
* Extends the base widget state with lock-specific properties.
|
|
8
|
+
*/
|
|
9
|
+
export type LockSelectionState<T = object> = BaseWidgetState<
|
|
10
|
+
T & LockSelectionStateProps
|
|
11
|
+
>
|
|
3
12
|
|
|
4
13
|
export interface LockSelectionProps {
|
|
5
14
|
/** Widget ID to store lock selection state */
|
|
@@ -22,3 +31,11 @@ export interface LockSelectionProps {
|
|
|
22
31
|
/** Custom icon to display */
|
|
23
32
|
Icon?: ReactNode
|
|
24
33
|
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Lock selection specific state properties.
|
|
37
|
+
*/
|
|
38
|
+
export interface LockSelectionStateProps {
|
|
39
|
+
/** Whether the selection is currently locked */
|
|
40
|
+
isLocked?: boolean
|
|
41
|
+
}
|
|
@@ -2,12 +2,11 @@ import { IconButton } from '@mui/material'
|
|
|
2
2
|
import { PercentOutlined } from '@mui/icons-material'
|
|
3
3
|
import { useCallback, useEffect, useRef } from 'react'
|
|
4
4
|
import { useWidgetStore } from '../../stores/widget-store'
|
|
5
|
-
import type { RelativeDataProps } from './types'
|
|
5
|
+
import type { RelativeDataProps, RelativeDataState } from './types'
|
|
6
6
|
import { actionButtonStyles } from '../shared/styles'
|
|
7
7
|
import { Tooltip } from '../../../components'
|
|
8
8
|
import { calculateTotal, toRelativeData } from './utils'
|
|
9
9
|
import type { EchartWidgetData } from '../../../widgets/echart'
|
|
10
|
-
import { useShallow } from 'zustand/shallow'
|
|
11
10
|
|
|
12
11
|
export const RELATIVE_DATA_TOOL_ID = 'relative-data'
|
|
13
12
|
|
|
@@ -44,27 +43,26 @@ export function RelativeData({
|
|
|
44
43
|
const unregisterTool = useWidgetStore((state) => state.unregisterTool)
|
|
45
44
|
const setToolEnabled = useWidgetStore((state) => state.setToolEnabled)
|
|
46
45
|
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
const tools = state.getWidget(id)?.registeredTools ?? []
|
|
50
|
-
return tools.find((tool) => tool.id === RELATIVE_DATA_TOOL_ID)
|
|
51
|
-
}),
|
|
46
|
+
const storeIsRelative = useWidgetStore(
|
|
47
|
+
(state) => state.getWidget<RelativeDataState>(id)?.isRelative,
|
|
52
48
|
)
|
|
53
49
|
|
|
54
|
-
const isRelative =
|
|
50
|
+
const isRelative = storeIsRelative ?? defaultIsRelative
|
|
55
51
|
|
|
56
|
-
//
|
|
52
|
+
// Initialize store with default value on mount
|
|
57
53
|
useEffect(() => {
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
54
|
+
const currentValue = getWidget<RelativeDataState>(id)?.isRelative
|
|
55
|
+
if (currentValue === undefined) {
|
|
56
|
+
setWidget(id, { isRelative: defaultIsRelative })
|
|
57
|
+
}
|
|
58
|
+
}, [defaultIsRelative, getWidget, id, setWidget])
|
|
63
59
|
|
|
60
|
+
// Register tool on mount
|
|
61
|
+
useEffect(() => {
|
|
64
62
|
registerTool(id, {
|
|
65
63
|
id: RELATIVE_DATA_TOOL_ID,
|
|
66
64
|
order,
|
|
67
|
-
enabled:
|
|
65
|
+
enabled: isRelative,
|
|
68
66
|
fn: (data) => {
|
|
69
67
|
const echartData = data as EchartWidgetData
|
|
70
68
|
const total = calculateTotal(echartData)
|
|
@@ -73,7 +71,12 @@ export function RelativeData({
|
|
|
73
71
|
})
|
|
74
72
|
|
|
75
73
|
return () => unregisterTool(id, RELATIVE_DATA_TOOL_ID)
|
|
76
|
-
}, [id, order, registerTool, unregisterTool,
|
|
74
|
+
}, [id, order, registerTool, unregisterTool, isRelative])
|
|
75
|
+
|
|
76
|
+
// Update enabled flag when toggle changes
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
setToolEnabled(id, RELATIVE_DATA_TOOL_ID, isRelative)
|
|
79
|
+
}, [id, isRelative, setToolEnabled])
|
|
77
80
|
|
|
78
81
|
const handleToggle = useCallback(() => {
|
|
79
82
|
const newIsRelative = !isRelative
|
|
@@ -93,8 +96,8 @@ export function RelativeData({
|
|
|
93
96
|
max = 100
|
|
94
97
|
}
|
|
95
98
|
|
|
96
|
-
setToolEnabled(id, RELATIVE_DATA_TOOL_ID, newIsRelative)
|
|
97
99
|
setWidget(id, {
|
|
100
|
+
isRelative: newIsRelative,
|
|
98
101
|
max,
|
|
99
102
|
formatter: newIsRelative
|
|
100
103
|
? (value: number) => {
|
|
@@ -107,7 +110,7 @@ export function RelativeData({
|
|
|
107
110
|
}
|
|
108
111
|
: originalFormatter.current,
|
|
109
112
|
})
|
|
110
|
-
}, [isRelative, setWidget,
|
|
113
|
+
}, [isRelative, setWidget, id, getWidget])
|
|
111
114
|
|
|
112
115
|
const tooltipLabel = isRelative
|
|
113
116
|
? (labels?.absolute ?? 'Show absolute values')
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { IconButtonProps } from '@mui/material'
|
|
2
2
|
import type { ReactNode } from 'react'
|
|
3
|
+
import type { BaseWidgetState } from '../../../widgets/stores'
|
|
3
4
|
|
|
4
5
|
export interface RelativeDataProps {
|
|
5
6
|
/** Widget ID to update data in the widget store */
|
|
@@ -22,3 +23,9 @@ export interface RelativeDataProps {
|
|
|
22
23
|
/** Custom icon to display */
|
|
23
24
|
Icon?: ReactNode
|
|
24
25
|
}
|
|
26
|
+
|
|
27
|
+
export type RelativeDataState<T = unknown> = BaseWidgetState<
|
|
28
|
+
T & {
|
|
29
|
+
isRelative?: boolean
|
|
30
|
+
}
|
|
31
|
+
>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { TextField, InputAdornment, IconButton } from '@mui/material'
|
|
2
2
|
import { ClearOutlined, SearchOutlined } from '@mui/icons-material'
|
|
3
|
-
import { useEffect, useRef, useCallback
|
|
3
|
+
import { useEffect, useRef, useCallback } from 'react'
|
|
4
4
|
import { useWidgetStore } from '../../stores/widget-store'
|
|
5
5
|
import type { SearcherProps, SearcherFilterFn, SearcherState } from './types'
|
|
6
6
|
import type { EchartWidgetData } from '../../echart/types'
|
|
@@ -40,33 +40,29 @@ export function Searcher({
|
|
|
40
40
|
const inputRef = useRef<HTMLInputElement>(null)
|
|
41
41
|
const debounceTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
42
42
|
|
|
43
|
-
// Read enabled state from widget store
|
|
43
|
+
// Read enabled state and search text from widget store
|
|
44
44
|
const widgetState = useWidgetStore((state) =>
|
|
45
45
|
state.getWidget<SearcherState>(id),
|
|
46
46
|
)
|
|
47
47
|
const enabled = widgetState?.isSearchEnabled ?? false
|
|
48
|
+
const searchText = widgetState?.searchText ?? ''
|
|
48
49
|
const prevEnabledRef = useRef(enabled)
|
|
49
50
|
|
|
50
|
-
const getWidget = useWidgetStore((state) => state.getWidget)
|
|
51
|
-
|
|
52
|
-
// Local state for immediate input value — restore from tool config on remount
|
|
53
|
-
const [searchTextLocal, setSearchTextLocal] = useState(() => {
|
|
54
|
-
const existingTool = getWidget(id)?.registeredTools?.find(
|
|
55
|
-
(tool) => tool.id === SEARCHER_TOOL_ID,
|
|
56
|
-
)
|
|
57
|
-
return (existingTool?.config?.searchText as string) ?? ''
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
// When disabled, display empty; when enabled, use local state
|
|
61
|
-
const searchText = enabled ? searchTextLocal : ''
|
|
62
|
-
|
|
63
51
|
const filter = filterFn ?? defaultFilterFn
|
|
64
52
|
|
|
53
|
+
const setWidget = useWidgetStore((state) => state.setWidget)
|
|
65
54
|
const registerTool = useWidgetStore((state) => state.registerTool)
|
|
66
55
|
const unregisterTool = useWidgetStore((state) => state.unregisterTool)
|
|
67
56
|
const setToolEnabled = useWidgetStore((state) => state.setToolEnabled)
|
|
68
57
|
const updateToolConfig = useWidgetStore((state) => state.updateToolConfig)
|
|
69
58
|
|
|
59
|
+
const setSearchText = useCallback(
|
|
60
|
+
(text: string) => {
|
|
61
|
+
setWidget(id, { searchText: text })
|
|
62
|
+
},
|
|
63
|
+
[id, setWidget],
|
|
64
|
+
)
|
|
65
|
+
|
|
70
66
|
// Register tool on mount
|
|
71
67
|
useEffect(() => {
|
|
72
68
|
registerTool(id, {
|
|
@@ -82,17 +78,12 @@ export function Searcher({
|
|
|
82
78
|
// Return result directly (pipeline will handle Promise)
|
|
83
79
|
return result
|
|
84
80
|
},
|
|
85
|
-
config: {
|
|
86
|
-
searchText:
|
|
87
|
-
(getWidget(id)?.registeredTools?.find(
|
|
88
|
-
(tool) => tool.id === SEARCHER_TOOL_ID,
|
|
89
|
-
)?.config?.searchText as string) ?? '',
|
|
90
|
-
},
|
|
81
|
+
config: { searchText },
|
|
91
82
|
disables: [LOCK_SELECTION_TOOL_ID],
|
|
92
83
|
})
|
|
93
84
|
|
|
94
85
|
return () => unregisterTool(id, SEARCHER_TOOL_ID)
|
|
95
|
-
}, [id, order, filter, registerTool, unregisterTool, enabled,
|
|
86
|
+
}, [id, order, filter, registerTool, unregisterTool, enabled, searchText])
|
|
96
87
|
|
|
97
88
|
// Update enabled flag when it changes
|
|
98
89
|
useEffect(() => {
|
|
@@ -112,24 +103,15 @@ export function Searcher({
|
|
|
112
103
|
[id, updateToolConfig, debounceDelay],
|
|
113
104
|
)
|
|
114
105
|
|
|
115
|
-
// Handle enabled state transitions during render (React setState-during-render pattern)
|
|
116
|
-
if (enabled !== prevEnabledRef.current) {
|
|
117
|
-
if (enabled) {
|
|
118
|
-
// Transition from disabled to enabled - reset local state
|
|
119
|
-
setSearchTextLocal('')
|
|
120
|
-
}
|
|
121
|
-
prevEnabledRef.current = enabled
|
|
122
|
-
}
|
|
123
|
-
|
|
124
106
|
// Auto-focus when enabled becomes true
|
|
125
107
|
useEffect(() => {
|
|
126
|
-
|
|
108
|
+
// Transition from disabled to enabled - focus input
|
|
109
|
+
if (enabled && !prevEnabledRef.current && inputRef.current) {
|
|
127
110
|
inputRef.current.focus()
|
|
128
111
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}, [enabled, id, updateToolConfig])
|
|
112
|
+
|
|
113
|
+
prevEnabledRef.current = enabled
|
|
114
|
+
}, [enabled])
|
|
133
115
|
|
|
134
116
|
// Cleanup debounce timeout on unmount
|
|
135
117
|
useEffect(() => {
|
|
@@ -143,19 +125,19 @@ export function Searcher({
|
|
|
143
125
|
const handleChange = useCallback(
|
|
144
126
|
(event: React.ChangeEvent<HTMLInputElement>) => {
|
|
145
127
|
const newValue = event.target.value
|
|
146
|
-
|
|
128
|
+
setSearchText(newValue)
|
|
147
129
|
debouncedUpdateConfig(newValue)
|
|
148
130
|
},
|
|
149
|
-
[debouncedUpdateConfig],
|
|
131
|
+
[debouncedUpdateConfig, setSearchText],
|
|
150
132
|
)
|
|
151
133
|
|
|
152
134
|
const handleClear = useCallback(() => {
|
|
153
|
-
|
|
135
|
+
setSearchText('')
|
|
154
136
|
updateToolConfig(id, SEARCHER_TOOL_ID, { searchText: '' })
|
|
155
137
|
if (inputRef.current) {
|
|
156
138
|
inputRef.current.focus()
|
|
157
139
|
}
|
|
158
|
-
}, [id, updateToolConfig])
|
|
140
|
+
}, [id, setSearchText, updateToolConfig])
|
|
159
141
|
|
|
160
142
|
if (!enabled) {
|
|
161
143
|
return null
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { IconButton } from '@mui/material'
|
|
2
2
|
import { useCallback, useEffect, useMemo } from 'react'
|
|
3
3
|
import { useWidgetStore } from '../../stores/widget-store'
|
|
4
|
-
import type { StackToggleProps } from './types'
|
|
4
|
+
import type { StackToggleProps, StackToggleState } from './types'
|
|
5
5
|
import { actionButtonStyles } from '../shared/styles'
|
|
6
6
|
import { Tooltip } from '../../../components'
|
|
7
7
|
import { GroupedBarChartIcon } from './grouped-bar-chart-icon'
|
|
@@ -34,17 +34,14 @@ export function StackToggle({
|
|
|
34
34
|
Icon,
|
|
35
35
|
IconButtonProps,
|
|
36
36
|
}: StackToggleProps) {
|
|
37
|
-
const
|
|
37
|
+
const setWidget = useWidgetStore((state) => state.setWidget)
|
|
38
38
|
const registerTool = useWidgetStore((state) => state.registerTool)
|
|
39
39
|
const unregisterTool = useWidgetStore((state) => state.unregisterTool)
|
|
40
40
|
const setToolEnabled = useWidgetStore((state) => state.setToolEnabled)
|
|
41
41
|
const updateToolConfig = useWidgetStore((state) => state.updateToolConfig)
|
|
42
42
|
|
|
43
|
-
const
|
|
44
|
-
useShallow((state) =>
|
|
45
|
-
const tools = state.getWidget(id)?.registeredTools ?? []
|
|
46
|
-
return tools.find((tool) => tool.id === STACK_TOGGLE_TOOL_ID)
|
|
47
|
-
}),
|
|
43
|
+
const storeIsStacked = useWidgetStore(
|
|
44
|
+
useShallow((state) => state.getWidget<StackToggleState>(id)?.isStacked),
|
|
48
45
|
)
|
|
49
46
|
|
|
50
47
|
const option = useWidgetStore(
|
|
@@ -64,25 +61,15 @@ export function StackToggle({
|
|
|
64
61
|
|
|
65
62
|
// If series already has stack defined, default to stacked=true
|
|
66
63
|
const effectiveDefaultIsStacked = hasStackInSeries || defaultIsStacked
|
|
67
|
-
const isStacked =
|
|
68
|
-
(stackTool?.config?.stacked as boolean | undefined) ??
|
|
69
|
-
effectiveDefaultIsStacked
|
|
64
|
+
const isStacked = storeIsStacked ?? effectiveDefaultIsStacked
|
|
70
65
|
|
|
71
66
|
// Register config tool on mount
|
|
72
67
|
useEffect(() => {
|
|
73
|
-
const existingTool = getWidget(id)?.registeredTools?.find(
|
|
74
|
-
(tool) => tool.id === STACK_TOGGLE_TOOL_ID,
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
const initialStacked =
|
|
78
|
-
(existingTool?.config?.stacked as boolean | undefined) ??
|
|
79
|
-
effectiveDefaultIsStacked
|
|
80
|
-
|
|
81
68
|
registerTool(id, {
|
|
82
69
|
id: STACK_TOGGLE_TOOL_ID,
|
|
83
70
|
type: 'config',
|
|
84
71
|
order: 10,
|
|
85
|
-
enabled:
|
|
72
|
+
enabled: isStacked && hasMultiSeries,
|
|
86
73
|
fn: (currentConfig, toolConfig) => {
|
|
87
74
|
const config = currentConfig as Record<string, unknown>
|
|
88
75
|
const option = config.option as EchartOptionsProps | undefined
|
|
@@ -103,23 +90,26 @@ export function StackToggle({
|
|
|
103
90
|
|
|
104
91
|
return { ...config, option: { ...option, series: updatedSeries } }
|
|
105
92
|
},
|
|
106
|
-
config: { stacked:
|
|
93
|
+
config: { stacked: isStacked },
|
|
107
94
|
})
|
|
108
95
|
return () => unregisterTool(id, STACK_TOGGLE_TOOL_ID)
|
|
109
|
-
}, [
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
96
|
+
}, [id, registerTool, unregisterTool, isStacked, hasMultiSeries])
|
|
97
|
+
|
|
98
|
+
// Sync tool enabled/config when state changes
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
setToolEnabled(id, STACK_TOGGLE_TOOL_ID, isStacked && hasMultiSeries)
|
|
101
|
+
updateToolConfig(id, STACK_TOGGLE_TOOL_ID, { stacked: isStacked })
|
|
102
|
+
}, [id, isStacked, hasMultiSeries, setToolEnabled, updateToolConfig])
|
|
103
|
+
|
|
104
|
+
// Initialize store with default value only if not already configured
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
if (storeIsStacked !== undefined) return
|
|
107
|
+
setWidget(id, { isStacked: effectiveDefaultIsStacked })
|
|
108
|
+
}, [effectiveDefaultIsStacked, id, setWidget, storeIsStacked])
|
|
117
109
|
|
|
118
110
|
const handleToggle = useCallback(() => {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
updateToolConfig(id, STACK_TOGGLE_TOOL_ID, { stacked: newStacked })
|
|
122
|
-
}, [isStacked, hasMultiSeries, id, setToolEnabled, updateToolConfig])
|
|
111
|
+
setWidget(id, { isStacked: !isStacked })
|
|
112
|
+
}, [isStacked, id, setWidget])
|
|
123
113
|
|
|
124
114
|
const tooltipLabel = isStacked
|
|
125
115
|
? (labels?.unstacked ?? 'Disable stacking')
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { IconButtonProps } from '@mui/material'
|
|
2
2
|
import type { ReactNode } from 'react'
|
|
3
|
+
import type { BaseWidgetState } from '../../stores/types'
|
|
4
|
+
|
|
3
5
|
export interface StackToggleProps {
|
|
4
6
|
/** Widget ID to update stack configuration in the widget store */
|
|
5
7
|
id: string
|
|
@@ -19,3 +21,9 @@ export interface StackToggleProps {
|
|
|
19
21
|
/** Custom icon to display */
|
|
20
22
|
Icon?: ReactNode
|
|
21
23
|
}
|
|
24
|
+
|
|
25
|
+
export type StackToggleState<T = unknown> = BaseWidgetState<
|
|
26
|
+
T & {
|
|
27
|
+
isStacked?: boolean
|
|
28
|
+
}
|
|
29
|
+
>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect } from 'react'
|
|
1
|
+
import { useEffect, useRef, useSyncExternalStore } from 'react'
|
|
2
2
|
import type { WidgetLoaderProps } from './types'
|
|
3
3
|
import { useWidgetStore } from '../stores/widget-store'
|
|
4
4
|
import type { WrapperState } from '../wrapper'
|
|
@@ -14,6 +14,20 @@ export function WidgetLoader<T extends object = Record<string, unknown>>(
|
|
|
14
14
|
(state) => state.executeConfigPipeline,
|
|
15
15
|
)
|
|
16
16
|
|
|
17
|
+
const registeredTools = useSyncExternalStore(
|
|
18
|
+
useWidgetStore.subscribe,
|
|
19
|
+
() => useWidgetStore.getState().widgets[props.id]?.registeredTools,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
const dataRef = useRef(props.data)
|
|
23
|
+
const configRef = useRef(props.config)
|
|
24
|
+
const isMountedRef = useRef(false)
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
dataRef.current = props.data
|
|
28
|
+
configRef.current = props.config
|
|
29
|
+
})
|
|
30
|
+
|
|
17
31
|
// Split into 3 effects for metadata and 1 for data pipeline:
|
|
18
32
|
// Each property that can be modified independently gets its own effect to avoid
|
|
19
33
|
// accidentally resetting other properties.
|
|
@@ -52,31 +66,18 @@ export function WidgetLoader<T extends object = Record<string, unknown>>(
|
|
|
52
66
|
void executeToolPipeline(props.id, props.data)
|
|
53
67
|
}, [props.id, props.data, executeToolPipeline])
|
|
54
68
|
|
|
55
|
-
// Effect 5: Re-execute pipelines when
|
|
69
|
+
// Effect 5: Re-execute pipelines when registered tools change
|
|
56
70
|
useEffect(() => {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
// Only re-execute if tools array changed
|
|
63
|
-
if (currentTools !== prevTools) {
|
|
64
|
-
prevTools = currentTools
|
|
65
|
-
void executeToolPipeline(props.id, props.data)
|
|
66
|
-
if (props.config) {
|
|
67
|
-
void executeConfigPipeline(props.id, props.config)
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
})
|
|
71
|
+
if (!isMountedRef.current) {
|
|
72
|
+
isMountedRef.current = true
|
|
73
|
+
return
|
|
74
|
+
}
|
|
71
75
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
executeToolPipeline,
|
|
78
|
-
executeConfigPipeline,
|
|
79
|
-
])
|
|
76
|
+
void executeToolPipeline(props.id, dataRef.current)
|
|
77
|
+
if (configRef.current) {
|
|
78
|
+
void executeConfigPipeline(props.id, configRef.current)
|
|
79
|
+
}
|
|
80
|
+
}, [registeredTools, props.id, executeToolPipeline, executeConfigPipeline])
|
|
80
81
|
|
|
81
82
|
return props.children
|
|
82
83
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { TableCell as MuiTableCell, Link, Typography } from '@mui/material'
|
|
2
2
|
import ReactMarkdown, { type Components } from 'react-markdown'
|
|
3
|
-
import type { TableColumn } from '../types'
|
|
3
|
+
import type { TableColumn, TableRow } from '../types'
|
|
4
4
|
import { getCellValue } from '../helpers'
|
|
5
5
|
import type { ReactNode } from 'react'
|
|
6
6
|
|
|
@@ -10,6 +10,8 @@ import type { ReactNode } from 'react'
|
|
|
10
10
|
export interface CellProps {
|
|
11
11
|
/** Column definition */
|
|
12
12
|
column: TableColumn
|
|
13
|
+
/** Full table row for context */
|
|
14
|
+
row: TableRow
|
|
13
15
|
/** Cell value */
|
|
14
16
|
value: unknown
|
|
15
17
|
}
|
|
@@ -54,12 +56,12 @@ const CELL_MARKDOWN_COMPONENTS: Components = {
|
|
|
54
56
|
* Markdown is rendered when the raw value is a string and no formatter is defined.
|
|
55
57
|
* If a formatter is used, its output is rendered directly without markdown parsing.
|
|
56
58
|
*/
|
|
57
|
-
export function Cell({ column, value }: CellProps) {
|
|
59
|
+
export function Cell({ column, row, value }: CellProps) {
|
|
58
60
|
// If formatter is defined, use it directly without markdown
|
|
59
61
|
if (column.formatter) {
|
|
60
62
|
return (
|
|
61
63
|
<MuiTableCell align={column.align}>
|
|
62
|
-
{column.formatter(value)}
|
|
64
|
+
{column.formatter(value, row)}
|
|
63
65
|
</MuiTableCell>
|
|
64
66
|
)
|
|
65
67
|
}
|
|
@@ -53,7 +53,12 @@ export function Row({
|
|
|
53
53
|
</TableCell>
|
|
54
54
|
)}
|
|
55
55
|
{columns.map((column) => (
|
|
56
|
-
<Cell
|
|
56
|
+
<Cell
|
|
57
|
+
key={column.id}
|
|
58
|
+
row={row}
|
|
59
|
+
column={column}
|
|
60
|
+
value={row[column.id]}
|
|
61
|
+
/>
|
|
57
62
|
))}
|
|
58
63
|
</MuiTableRow>
|
|
59
64
|
)
|