@carto/ps-react-ui 4.3.7 → 4.3.8
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/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/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
|
@@ -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
|
}
|