@carto/ps-react-ui 4.4.3 → 4.5.1

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.
Files changed (116) hide show
  1. package/dist/{download-config-Dqu78h2a.js → download-config-DemuQ3Jm.js} +9 -10
  2. package/dist/{download-config-Dqu78h2a.js.map → download-config-DemuQ3Jm.js.map} +1 -1
  3. package/dist/error-Cj8eUMrl.js +40 -0
  4. package/dist/error-Cj8eUMrl.js.map +1 -0
  5. package/dist/no-data-DkIt7Qt1.js +61 -0
  6. package/dist/no-data-DkIt7Qt1.js.map +1 -0
  7. package/dist/row-D4VOhcNI.js +34 -0
  8. package/dist/row-D4VOhcNI.js.map +1 -0
  9. package/dist/series-Bola3CmD.js +90 -0
  10. package/dist/series-Bola3CmD.js.map +1 -0
  11. package/dist/types/widgets/category/style.d.ts +1 -0
  12. package/dist/types/widgets/echart/shared-resize-observer.d.ts +12 -0
  13. package/dist/types/widgets/stores/index.d.ts +2 -1
  14. package/dist/types/widgets/stores/use-widget-selector.d.ts +35 -0
  15. package/dist/types/widgets/stores/widget-store-performance.test.d.ts +1 -0
  16. package/dist/types/widgets/stores/widget-store.d.ts +49 -27
  17. package/dist/types/widgets/table/types.d.ts +1 -1
  18. package/dist/use-widget-ref-BFazQvJK.js +22 -0
  19. package/dist/use-widget-ref-BFazQvJK.js.map +1 -0
  20. package/dist/use-widget-selector-DqRmWQ1K.js +12 -0
  21. package/dist/use-widget-selector-DqRmWQ1K.js.map +1 -0
  22. package/dist/widget-store-CIrb9RKP.js +263 -0
  23. package/dist/widget-store-CIrb9RKP.js.map +1 -0
  24. package/dist/widgets/actions.js +783 -817
  25. package/dist/widgets/actions.js.map +1 -1
  26. package/dist/widgets/bar.js +2 -2
  27. package/dist/widgets/category.js +259 -258
  28. package/dist/widgets/category.js.map +1 -1
  29. package/dist/widgets/echart.js +109 -99
  30. package/dist/widgets/echart.js.map +1 -1
  31. package/dist/widgets/error.js +1 -1
  32. package/dist/widgets/formula.js +71 -63
  33. package/dist/widgets/formula.js.map +1 -1
  34. package/dist/widgets/histogram.js +7 -8
  35. package/dist/widgets/histogram.js.map +1 -1
  36. package/dist/widgets/loader.js +53 -60
  37. package/dist/widgets/loader.js.map +1 -1
  38. package/dist/widgets/markdown.js +51 -50
  39. package/dist/widgets/markdown.js.map +1 -1
  40. package/dist/widgets/no-data.js +1 -1
  41. package/dist/widgets/pie.js +2 -2
  42. package/dist/widgets/range.js +146 -144
  43. package/dist/widgets/range.js.map +1 -1
  44. package/dist/widgets/scatterplot.js +2 -2
  45. package/dist/widgets/skeleton-loader.js +18 -17
  46. package/dist/widgets/skeleton-loader.js.map +1 -1
  47. package/dist/widgets/spread.js +110 -94
  48. package/dist/widgets/spread.js.map +1 -1
  49. package/dist/widgets/stores.js +5 -2
  50. package/dist/widgets/stores.js.map +1 -1
  51. package/dist/widgets/subheader.js +29 -29
  52. package/dist/widgets/subheader.js.map +1 -1
  53. package/dist/widgets/table.js +422 -436
  54. package/dist/widgets/table.js.map +1 -1
  55. package/dist/widgets/timeseries.js +2 -2
  56. package/dist/widgets/utils.js +1 -1
  57. package/dist/widgets/wrapper.js +156 -158
  58. package/dist/widgets/wrapper.js.map +1 -1
  59. package/dist/widgets.js +4 -4
  60. package/package.json +1 -1
  61. package/src/hooks/use-widget-ref.ts +3 -4
  62. package/src/widgets/actions/brush-toggle/brush-toggle.tsx +18 -32
  63. package/src/widgets/actions/change-column/change-column.tsx +15 -15
  64. package/src/widgets/actions/change-column/sortable-column-item.tsx +3 -1
  65. package/src/widgets/actions/download/download.tsx +4 -3
  66. package/src/widgets/actions/fullscreen/fullscreen.tsx +7 -11
  67. package/src/widgets/actions/lock-selection/lock-selection.tsx +12 -15
  68. package/src/widgets/actions/relative-data/relative-data.tsx +22 -26
  69. package/src/widgets/actions/searcher/searcher-toggle.tsx +11 -12
  70. package/src/widgets/actions/searcher/searcher.tsx +20 -21
  71. package/src/widgets/actions/stack-toggle/stack-toggle.tsx +15 -21
  72. package/src/widgets/actions/zoom-toggle/zoom-toggle.tsx +27 -43
  73. package/src/widgets/category/category-ui.tsx +30 -31
  74. package/src/widgets/category/style.ts +1 -0
  75. package/src/widgets/echart/echart-ui.test.tsx +20 -16
  76. package/src/widgets/echart/echart-ui.tsx +6 -12
  77. package/src/widgets/echart/echart.tsx +13 -27
  78. package/src/widgets/echart/shared-resize-observer.ts +45 -0
  79. package/src/widgets/error/error.tsx +7 -9
  80. package/src/widgets/formula/components/prefix.tsx +4 -6
  81. package/src/widgets/formula/components/row.tsx +4 -4
  82. package/src/widgets/formula/components/series.tsx +4 -6
  83. package/src/widgets/formula/components/suffix.tsx +4 -6
  84. package/src/widgets/formula/components/value.tsx +9 -16
  85. package/src/widgets/loader/loader.tsx +31 -44
  86. package/src/widgets/markdown/markdown.tsx +4 -7
  87. package/src/widgets/no-data/no-data.tsx +7 -10
  88. package/src/widgets/range/components/range-item.tsx +20 -18
  89. package/src/widgets/skeleton-loader/skeleton-loader.tsx +2 -5
  90. package/src/widgets/spread/components/max-value.tsx +14 -16
  91. package/src/widgets/spread/components/min-value.tsx +14 -16
  92. package/src/widgets/stores/index.ts +2 -1
  93. package/src/widgets/stores/use-widget-selector.ts +47 -0
  94. package/src/widgets/stores/widget-store-performance.test.ts +750 -0
  95. package/src/widgets/stores/widget-store.test.ts +81 -0
  96. package/src/widgets/stores/widget-store.ts +225 -44
  97. package/src/widgets/subheader/subheader.tsx +11 -3
  98. package/src/widgets/table/config.ts +0 -1
  99. package/src/widgets/table/hooks/use-pagination.ts +28 -52
  100. package/src/widgets/table/hooks/use-selection.ts +20 -24
  101. package/src/widgets/table/hooks/use-sort.ts +22 -39
  102. package/src/widgets/table/types.ts +1 -1
  103. package/src/widgets/wrapper/wrapper-ui.tsx +12 -13
  104. package/src/widgets/wrapper/wrapper.tsx +4 -6
  105. package/dist/error-CEkRPccv.js +0 -39
  106. package/dist/error-CEkRPccv.js.map +0 -1
  107. package/dist/no-data-hR3KcJ-_.js +0 -60
  108. package/dist/no-data-hR3KcJ-_.js.map +0 -1
  109. package/dist/row-DTCV0Ocm.js +0 -35
  110. package/dist/row-DTCV0Ocm.js.map +0 -1
  111. package/dist/series-CYNOu2Ju.js +0 -91
  112. package/dist/series-CYNOu2Ju.js.map +0 -1
  113. package/dist/use-widget-ref-wtFLDFCD.js +0 -25
  114. package/dist/use-widget-ref-wtFLDFCD.js.map +0 -1
  115. package/dist/widget-store-CzDt8oSK.js +0 -163
  116. package/dist/widget-store-CzDt8oSK.js.map +0 -1
@@ -21,14 +21,14 @@ import {
21
21
  useState,
22
22
  type MouseEvent,
23
23
  } from 'react'
24
- import { useWidgetStore } from '../../stores/widget-store'
24
+ import { widgetStoreActions } from '../../stores/widget-store'
25
25
  import type { ChangeColumnProps } from './types'
26
26
  import { actionButtonStyles } from '../shared/styles'
27
27
  import { Tooltip } from '../../../components'
28
28
  import type { TableColumn, TableWidgetState } from '../../table/types'
29
29
  import { ChangeColumnIcon } from './change-column-icon'
30
30
  import { SortableColumnItem } from './sortable-column-item'
31
- import { useShallow } from 'zustand/shallow'
31
+ import { useWidgetSelector } from '../../stores/use-widget-selector'
32
32
 
33
33
  export const CHANGE_COLUMN_TOOL_ID = 'change-column'
34
34
 
@@ -57,12 +57,9 @@ export function ChangeColumn({
57
57
  MenuProps,
58
58
  }: ChangeColumnProps) {
59
59
  const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null)
60
- const setWidget = useWidgetStore((state) => state.setWidget)
61
- const registerTool = useWidgetStore((state) => state.registerTool)
62
- const unregisterTool = useWidgetStore((state) => state.unregisterTool)
63
- const columns = useWidgetStore(
64
- useShallow((state) => state.getWidget<TableWidgetState>(id)?.columns),
65
- )
60
+ const { columns } = useWidgetSelector(id, (w) => ({
61
+ columns: (w as TableWidgetState | undefined)?.columns,
62
+ }))
66
63
 
67
64
  /**
68
65
  * Config tool function that reorders columns to match the current widget state.
@@ -72,9 +69,7 @@ export function ChangeColumn({
72
69
  */
73
70
  const reorderFn = useCallback(
74
71
  (currentConfig: unknown): unknown => {
75
- const widgetState = useWidgetStore
76
- .getState()
77
- .getWidget<TableWidgetState>(id)
72
+ const widgetState = widgetStoreActions.getWidget<TableWidgetState>(id)
78
73
  const currentColumns = widgetState?.columns
79
74
  if (!currentColumns || currentColumns.length === 0) return currentConfig
80
75
 
@@ -130,15 +125,15 @@ export function ChangeColumn({
130
125
 
131
126
  // Register config tool on mount
132
127
  useEffect(() => {
133
- registerTool(id, {
128
+ widgetStoreActions.registerTool(id, {
134
129
  id: CHANGE_COLUMN_TOOL_ID,
135
130
  type: 'config',
136
131
  order: 100,
137
132
  enabled: true,
138
133
  fn: reorderFn,
139
134
  })
140
- return () => unregisterTool(id, CHANGE_COLUMN_TOOL_ID)
141
- }, [id, registerTool, unregisterTool, reorderFn])
135
+ return () => widgetStoreActions.unregisterTool(id, CHANGE_COLUMN_TOOL_ID)
136
+ }, [id, reorderFn])
142
137
 
143
138
  const handleToggle = useCallback((event: MouseEvent<HTMLElement>) => {
144
139
  event.stopPropagation()
@@ -156,7 +151,7 @@ export function ChangeColumn({
156
151
  const newIndex = columns.findIndex((col) => col.id === over.id)
157
152
  if (oldIndex !== -1 && newIndex !== -1) {
158
153
  const newColumns = arrayMove(columns, oldIndex, newIndex)
159
- setWidget(id, { columns: newColumns })
154
+ widgetStoreActions.setWidget(id, { columns: newColumns })
160
155
  }
161
156
  }
162
157
 
@@ -201,6 +196,11 @@ export function ChangeColumn({
201
196
  onClose={handleClose}
202
197
  anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
203
198
  transformOrigin={{ vertical: 'top', horizontal: 'right' }}
199
+ slotProps={{
200
+ paper: {
201
+ sx: { overflow: 'hidden' },
202
+ },
203
+ }}
204
204
  {...MenuProps}
205
205
  >
206
206
  <SortableContext
@@ -22,7 +22,9 @@ export function SortableColumnItem({ column }: SortableColumnItemProps) {
22
22
  } = useSortable({ id: column.id })
23
23
 
24
24
  const style = {
25
- transform: CSS.Transform.toString(transform),
25
+ transform: CSS.Transform.toString(
26
+ transform ? { ...transform, x: 0 } : null,
27
+ ),
26
28
  transition,
27
29
  opacity: isDragging ? 0.5 : 1,
28
30
  cursor: isDragging ? 'grabbing' : 'grab',
@@ -9,8 +9,7 @@ import {
9
9
  import type { DownloadItem, DownloadProps } from './types'
10
10
  import { FileDownloadOutlined } from '@mui/icons-material'
11
11
  import { useState, type MouseEvent } from 'react'
12
- import { useWidgetStore } from '../../../widgets'
13
- import { useShallow } from 'zustand/shallow'
12
+ import { useWidgetSelector } from '../../stores/use-widget-selector'
14
13
 
15
14
  const EMPTY_LABELS: NonNullable<DownloadProps['labels']> = {}
16
15
 
@@ -32,7 +31,9 @@ export function Download({
32
31
  Icon,
33
32
  IconButtonProps,
34
33
  }: DownloadProps) {
35
- const data = useWidgetStore(useShallow((state) => state.getWidget(id)?.data))
34
+ const { data } = useWidgetSelector(id, (w) => ({
35
+ data: w?.data,
36
+ }))
36
37
 
37
38
  const [isDownloading, setIsDownloading] = useState(false)
38
39
  const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null)
@@ -5,7 +5,8 @@ import {
5
5
  IconButton,
6
6
  Typography,
7
7
  } from '@mui/material'
8
- import { useWidgetStore } from '../../stores/widget-store'
8
+ import { widgetStoreActions } from '../../stores/widget-store'
9
+ import { useWidgetSelector } from '../../stores/use-widget-selector'
9
10
  import { Close, FullscreenOutlined } from '@mui/icons-material'
10
11
  import type {
11
12
  FullScreenConfig,
@@ -13,7 +14,6 @@ import type {
13
14
  FullScreenState,
14
15
  } from './types'
15
16
  import { styles } from './styles'
16
- import { useShallow } from 'zustand/shallow'
17
17
 
18
18
  const EMPTY_DIALOG_CONTENT_PROPS: NonNullable<
19
19
  FullScreenProps['DialogContentProps']
@@ -44,16 +44,12 @@ export function FullScreen({
44
44
  } = EMPTY_DIALOG_CONTENT_PROPS,
45
45
  DialogProps,
46
46
  }: FullScreenProps) {
47
- const isFullScreen = useWidgetStore(
48
- useShallow((state) => state.getWidget<FullScreenState>(id)?.isFullScreen),
49
- )
50
- const title = useWidgetStore(
51
- useShallow((state) => state.getWidget<FullScreenState>(id)?.title),
52
- )
53
- const setWidget = useWidgetStore((state) => state.setWidget)
54
-
47
+ const { isFullScreen, title } = useWidgetSelector(id, (w) => ({
48
+ isFullScreen: (w as FullScreenState | undefined)?.isFullScreen,
49
+ title: (w as FullScreenState | undefined)?.title,
50
+ }))
55
51
  const updateFullScreenConfig = (updates: Partial<FullScreenConfig>) => {
56
- setWidget<FullScreenState>(id, {
52
+ widgetStoreActions.setWidget<FullScreenState>(id, {
57
53
  isFullScreen: updates.isFullScreen,
58
54
  })
59
55
  }
@@ -4,8 +4,8 @@ import { useCallback, useEffect, useMemo } from 'react'
4
4
  import type { LockSelectionProps, LockSelectionState } from './types'
5
5
  import { actionButtonStyles } from '../shared/styles'
6
6
  import { Tooltip } from '../../../components'
7
- import { useWidgetStore } from '../../stores/widget-store'
8
- import { useShallow } from 'zustand/shallow'
7
+ import { widgetStoreActions } from '../../stores/widget-store'
8
+ import { useWidgetSelector } from '../../stores/use-widget-selector'
9
9
  import type { EchartWidgetData } from '../../echart/types'
10
10
 
11
11
  export const LOCK_SELECTION_TOOL_ID = 'lock-selection'
@@ -34,12 +34,9 @@ export function LockSelection({
34
34
  Icon,
35
35
  IconButtonProps,
36
36
  }: LockSelectionProps) {
37
- const setWidget = useWidgetStore((state) => state.setWidget)
38
- const registerTool = useWidgetStore((state) => state.registerTool)
39
- const unregisterTool = useWidgetStore((state) => state.unregisterTool)
40
- const storeIsLocked = useWidgetStore(
41
- useShallow((state) => state.getWidget<LockSelectionState>(id)?.isLocked),
42
- )
37
+ const { storeIsLocked } = useWidgetSelector(id, (w) => ({
38
+ storeIsLocked: (w as LockSelectionState | undefined)?.isLocked,
39
+ }))
43
40
 
44
41
  const isLocked = storeIsLocked ?? false
45
42
  const lockedItems = useMemo(
@@ -47,9 +44,9 @@ export function LockSelection({
47
44
  [isLocked, selectedItems],
48
45
  )
49
46
 
50
- // Register tool on mount
47
+ // Register tool with all reactive deps — store's no-op detection handles performance
51
48
  useEffect(() => {
52
- registerTool(id, {
49
+ widgetStoreActions.registerTool(id, {
53
50
  id: LOCK_SELECTION_TOOL_ID,
54
51
  order,
55
52
  enabled: isLocked,
@@ -62,24 +59,24 @@ export function LockSelection({
62
59
  config: { lockedItems },
63
60
  })
64
61
 
65
- return () => unregisterTool(id, LOCK_SELECTION_TOOL_ID)
66
- }, [id, order, registerTool, unregisterTool, isLocked, lockedItems])
62
+ return () => widgetStoreActions.unregisterTool(id, LOCK_SELECTION_TOOL_ID)
63
+ }, [id, order, isLocked, lockedItems])
67
64
 
68
65
  const handleToggle = useCallback(() => {
69
66
  if (isLocked) {
70
67
  // Unlock: clear locked items and disable tool
71
- setWidget(id, {
68
+ widgetStoreActions.setWidget(id, {
72
69
  isLocked: false,
73
70
  lockedItems: [],
74
71
  })
75
72
  } else {
76
73
  // Lock: save selected items and enable tool
77
- setWidget(id, {
74
+ widgetStoreActions.setWidget(id, {
78
75
  isLocked: true,
79
76
  lockedItems: selectedItems,
80
77
  })
81
78
  }
82
- }, [id, isLocked, selectedItems, setWidget])
79
+ }, [id, isLocked, selectedItems])
83
80
 
84
81
  // Don't render if no selections
85
82
  if (selectedItems.length === 0) {
@@ -1,7 +1,8 @@
1
1
  import { IconButton } from '@mui/material'
2
2
  import { PercentOutlined } from '@mui/icons-material'
3
3
  import { useCallback, useEffect, useRef } from 'react'
4
- import { useWidgetStore } from '../../stores/widget-store'
4
+ import { widgetStoreActions } from '../../stores/widget-store'
5
+ import { useWidgetSelector } from '../../stores/use-widget-selector'
5
6
  import type { RelativeDataProps } from './types'
6
7
  import { actionButtonStyles } from '../shared/styles'
7
8
  import { Tooltip } from '../../../components'
@@ -37,24 +38,18 @@ export function RelativeData({
37
38
  const percentFormatterRef = useRef<((value: number) => string) | undefined>(
38
39
  undefined,
39
40
  )
40
- const getWidget = useWidgetStore((state) => state.getWidget)
41
- const registerTool = useWidgetStore((state) => state.registerTool)
42
- const unregisterTool = useWidgetStore((state) => state.unregisterTool)
43
- const setToolEnabled = useWidgetStore((state) => state.setToolEnabled)
44
- const updateToolConfig = useWidgetStore((state) => state.updateToolConfig)
45
41
 
46
- const storeIsRelative = useWidgetStore(
47
- (state) =>
48
- state.widgets[id]?.registeredTools?.find(
49
- (t) => t.id === RELATIVE_DATA_CONFIG_TOOL_ID,
50
- )?.config?.isRelative as boolean | undefined,
51
- )
42
+ const { storeIsRelative } = useWidgetSelector(id, (w) => ({
43
+ storeIsRelative: w?.registeredTools?.find(
44
+ (t) => t.id === RELATIVE_DATA_CONFIG_TOOL_ID,
45
+ )?.config?.isRelative as boolean | undefined,
46
+ }))
52
47
 
53
48
  const isRelative = storeIsRelative ?? defaultIsRelative
54
49
 
55
- // Register tool on mount
50
+ // Register data tool with all reactive deps — store's no-op detection handles performance
56
51
  useEffect(() => {
57
- registerTool(id, {
52
+ widgetStoreActions.registerTool(id, {
58
53
  id: RELATIVE_DATA_TOOL_ID,
59
54
  order,
60
55
  enabled: defaultIsRelative,
@@ -65,17 +60,17 @@ export function RelativeData({
65
60
  },
66
61
  })
67
62
 
68
- return () => unregisterTool(id, RELATIVE_DATA_TOOL_ID)
69
- }, [id, order, registerTool, unregisterTool, defaultIsRelative])
63
+ return () => widgetStoreActions.unregisterTool(id, RELATIVE_DATA_TOOL_ID)
64
+ }, [id, order, defaultIsRelative])
70
65
 
71
- // Register config tool for formatter management
66
+ // Register config tool with all reactive deps — store's no-op detection handles performance
72
67
  useEffect(() => {
73
- registerTool(id, {
68
+ widgetStoreActions.registerTool(id, {
74
69
  id: RELATIVE_DATA_CONFIG_TOOL_ID,
75
70
  type: 'config',
76
71
  order,
77
72
  enabled: true,
78
- fn: (currentConfig, toolConfig) => {
73
+ fn: (currentConfig: unknown, toolConfig?: Record<string, unknown>) => {
79
74
  const config = currentConfig as Record<string, unknown>
80
75
  if (toolConfig?.isRelative) {
81
76
  if (!percentFormatterRef.current) {
@@ -104,20 +99,21 @@ export function RelativeData({
104
99
  isRelative: defaultIsRelative,
105
100
  },
106
101
  })
107
- return () => unregisterTool(id, RELATIVE_DATA_CONFIG_TOOL_ID)
108
- }, [id, order, registerTool, unregisterTool, defaultIsRelative])
102
+ return () =>
103
+ widgetStoreActions.unregisterTool(id, RELATIVE_DATA_CONFIG_TOOL_ID)
104
+ }, [id, order, defaultIsRelative])
109
105
 
110
106
  const handleToggle = useCallback(() => {
111
107
  const newIsRelative = !isRelative
112
- setToolEnabled(id, RELATIVE_DATA_TOOL_ID, newIsRelative)
108
+ widgetStoreActions.setToolEnabled(id, RELATIVE_DATA_TOOL_ID, newIsRelative)
113
109
 
114
110
  if (newIsRelative) {
115
- const widget = getWidget(id) as {
111
+ const widget = widgetStoreActions.getWidget(id) as {
116
112
  formatter?: (value: number) => string
117
113
  locale?: string
118
114
  max?: number
119
115
  }
120
- updateToolConfig(id, RELATIVE_DATA_CONFIG_TOOL_ID, {
116
+ widgetStoreActions.updateToolConfig(id, RELATIVE_DATA_CONFIG_TOOL_ID, {
121
117
  isRelative: true,
122
118
  originalFormatter: widget?.formatter,
123
119
  originalMax: widget?.max,
@@ -125,11 +121,11 @@ export function RelativeData({
125
121
  })
126
122
  } else {
127
123
  percentFormatterRef.current = undefined
128
- updateToolConfig(id, RELATIVE_DATA_CONFIG_TOOL_ID, {
124
+ widgetStoreActions.updateToolConfig(id, RELATIVE_DATA_CONFIG_TOOL_ID, {
129
125
  isRelative: false,
130
126
  })
131
127
  }
132
- }, [isRelative, id, getWidget, setToolEnabled, updateToolConfig])
128
+ }, [isRelative, id])
133
129
 
134
130
  const tooltipLabel = isRelative
135
131
  ? (labels?.absolute ?? 'Show absolute values')
@@ -4,8 +4,8 @@ import { useCallback, useEffect } from 'react'
4
4
  import type { SearcherToggleProps, SearcherState } from './types'
5
5
  import { actionButtonStyles } from '../shared/styles'
6
6
  import { Tooltip } from '../../../components'
7
- import { useWidgetStore } from '../../stores/widget-store'
8
- import { useShallow } from 'zustand/shallow'
7
+ import { widgetStoreActions } from '../../stores/widget-store'
8
+ import { useWidgetSelector } from '../../stores/use-widget-selector'
9
9
 
10
10
  /**
11
11
  * Widget action button to toggle search functionality.
@@ -30,25 +30,24 @@ export function SearcherToggle({
30
30
  Icon,
31
31
  IconButtonProps,
32
32
  }: SearcherToggleProps) {
33
- const setWidget = useWidgetStore((state) => state.setWidget)
34
- const getWidget = useWidgetStore((state) => state.getWidget)
35
- const storeIsEnabled = useWidgetStore(
36
- useShallow((state) => state.getWidget<SearcherState>(id)?.isSearchEnabled),
37
- )
33
+ const { storeIsEnabled } = useWidgetSelector(id, (w) => ({
34
+ storeIsEnabled: (w as SearcherState | undefined)?.isSearchEnabled,
35
+ }))
38
36
 
39
37
  const isEnabled = storeIsEnabled ?? defaultEnabled
40
38
 
41
39
  // Initialize store with default value on mount
42
40
  useEffect(() => {
43
- const currentValue = getWidget<SearcherState>(id)?.isSearchEnabled
41
+ const currentValue =
42
+ widgetStoreActions.getWidget<SearcherState>(id)?.isSearchEnabled
44
43
  if (currentValue === undefined) {
45
- setWidget(id, { isSearchEnabled: defaultEnabled })
44
+ widgetStoreActions.setWidget(id, { isSearchEnabled: defaultEnabled })
46
45
  }
47
- }, [defaultEnabled, getWidget, id, setWidget])
46
+ }, [defaultEnabled, id])
48
47
 
49
48
  const handleToggle = useCallback(() => {
50
- setWidget(id, { isSearchEnabled: !isEnabled })
51
- }, [id, isEnabled, setWidget])
49
+ widgetStoreActions.setWidget(id, { isSearchEnabled: !isEnabled })
50
+ }, [id, isEnabled])
52
51
 
53
52
  const tooltipLabel = isEnabled
54
53
  ? (labels?.disable ?? 'Disable search')
@@ -1,7 +1,8 @@
1
1
  import { TextField, InputAdornment, IconButton } from '@mui/material'
2
2
  import { ClearOutlined, SearchOutlined } from '@mui/icons-material'
3
3
  import { useEffect, useRef, useCallback } from 'react'
4
- import { useWidgetStore } from '../../stores/widget-store'
4
+ import { widgetStoreActions } from '../../stores/widget-store'
5
+ import { useWidgetSelector } from '../../stores/use-widget-selector'
5
6
  import type { SearcherProps, SearcherFilterFn, SearcherState } from './types'
6
7
  import type { EchartWidgetData } from '../../echart/types'
7
8
  import { LOCK_SELECTION_TOOL_ID } from '../lock-selection/lock-selection'
@@ -41,30 +42,24 @@ export function Searcher({
41
42
  const debounceTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
42
43
 
43
44
  // Read enabled state and search text from widget store
44
- const widgetState = useWidgetStore((state) =>
45
- state.getWidget<SearcherState>(id),
46
- )
47
- const enabled = widgetState?.isSearchEnabled ?? false
48
- const searchText = widgetState?.searchText ?? ''
45
+ const { enabled, searchText } = useWidgetSelector(id, (w) => ({
46
+ enabled: (w as SearcherState | undefined)?.isSearchEnabled ?? false,
47
+ searchText: (w as SearcherState | undefined)?.searchText ?? '',
48
+ }))
49
49
  const prevEnabledRef = useRef(enabled)
50
50
 
51
51
  const filter = filterFn ?? defaultFilterFn
52
52
 
53
- const setWidget = useWidgetStore((state) => state.setWidget)
54
- const registerTool = useWidgetStore((state) => state.registerTool)
55
- const unregisterTool = useWidgetStore((state) => state.unregisterTool)
56
- const updateToolConfig = useWidgetStore((state) => state.updateToolConfig)
57
-
58
53
  const setSearchText = useCallback(
59
54
  (text: string) => {
60
- setWidget(id, { searchText: text })
55
+ widgetStoreActions.setWidget(id, { searchText: text })
61
56
  },
62
- [id, setWidget],
57
+ [id],
63
58
  )
64
59
 
65
- // Register tool on mount
60
+ // Register tool with all reactive deps — store's no-op detection handles performance
66
61
  useEffect(() => {
67
- registerTool(id, {
62
+ widgetStoreActions.registerTool(id, {
68
63
  id: SEARCHER_TOOL_ID,
69
64
  order,
70
65
  enabled,
@@ -81,8 +76,8 @@ export function Searcher({
81
76
  disables: [LOCK_SELECTION_TOOL_ID],
82
77
  })
83
78
 
84
- return () => unregisterTool(id, SEARCHER_TOOL_ID)
85
- }, [id, order, filter, registerTool, unregisterTool, enabled, searchText])
79
+ return () => widgetStoreActions.unregisterTool(id, SEARCHER_TOOL_ID)
80
+ }, [id, order, enabled, searchText, filter])
86
81
 
87
82
  // Update config when search text changes (debounced)
88
83
  const debouncedUpdateConfig = useCallback(
@@ -91,10 +86,12 @@ export function Searcher({
91
86
  clearTimeout(debounceTimeoutRef.current)
92
87
  }
93
88
  debounceTimeoutRef.current = setTimeout(() => {
94
- updateToolConfig(id, SEARCHER_TOOL_ID, { searchText: text })
89
+ widgetStoreActions.updateToolConfig(id, SEARCHER_TOOL_ID, {
90
+ searchText: text,
91
+ })
95
92
  }, debounceDelay)
96
93
  },
97
- [id, updateToolConfig, debounceDelay],
94
+ [id, debounceDelay],
98
95
  )
99
96
 
100
97
  // Auto-focus when enabled becomes true
@@ -127,11 +124,13 @@ export function Searcher({
127
124
 
128
125
  const handleClear = useCallback(() => {
129
126
  setSearchText('')
130
- updateToolConfig(id, SEARCHER_TOOL_ID, { searchText: '' })
127
+ widgetStoreActions.updateToolConfig(id, SEARCHER_TOOL_ID, {
128
+ searchText: '',
129
+ })
131
130
  if (inputRef.current) {
132
131
  inputRef.current.focus()
133
132
  }
134
- }, [id, setSearchText, updateToolConfig])
133
+ }, [id, setSearchText])
135
134
 
136
135
  if (!enabled) {
137
136
  return null
@@ -1,6 +1,6 @@
1
1
  import { IconButton } from '@mui/material'
2
2
  import { useCallback, useEffect, useMemo } from 'react'
3
- import { useWidgetStore } from '../../stores/widget-store'
3
+ import { widgetStoreActions } from '../../stores/widget-store'
4
4
  import type { StackToggleProps, StackToggleState } from './types'
5
5
  import { actionButtonStyles } from '../shared/styles'
6
6
  import { Tooltip } from '../../../components'
@@ -9,7 +9,7 @@ import { getEChartStackConfig } from '../../echart/utils'
9
9
  import { DEFAULT_STACK_GROUP } from '../../echart/const'
10
10
  import type { EchartWidgetState } from '../../echart/types'
11
11
  import type { EchartOptionsProps } from '../../echart/types'
12
- import { useShallow } from 'zustand/shallow'
12
+ import { useWidgetSelector } from '../../stores/use-widget-selector'
13
13
 
14
14
  export const STACK_TOGGLE_TOOL_ID = 'stack-toggle'
15
15
 
@@ -34,16 +34,10 @@ export function StackToggle({
34
34
  Icon,
35
35
  IconButtonProps,
36
36
  }: StackToggleProps) {
37
- const setWidget = useWidgetStore((state) => state.setWidget)
38
- const registerTool = useWidgetStore((state) => state.registerTool)
39
- const unregisterTool = useWidgetStore((state) => state.unregisterTool)
40
- const storeIsStacked = useWidgetStore(
41
- useShallow((state) => state.getWidget<StackToggleState>(id)?.isStacked),
42
- )
43
-
44
- const option = useWidgetStore(
45
- useShallow((state) => state.getWidget<EchartWidgetState>(id)?.option),
46
- )
37
+ const { storeIsStacked, option } = useWidgetSelector(id, (w) => ({
38
+ storeIsStacked: (w as StackToggleState | undefined)?.isStacked,
39
+ option: (w as EchartWidgetState | undefined)?.option,
40
+ }))
47
41
 
48
42
  const { hasMultiSeries, hasStackInSeries } = useMemo(() => {
49
43
  if (!option) return { hasMultiSeries: false, hasStackInSeries: false }
@@ -60,14 +54,14 @@ export function StackToggle({
60
54
  const effectiveDefaultIsStacked = hasStackInSeries || defaultIsStacked
61
55
  const isStacked = storeIsStacked ?? effectiveDefaultIsStacked
62
56
 
63
- // Register config tool on mount
57
+ // Register config tool with all reactive deps — store's no-op detection handles performance
64
58
  useEffect(() => {
65
- registerTool(id, {
59
+ widgetStoreActions.registerTool(id, {
66
60
  id: STACK_TOGGLE_TOOL_ID,
67
61
  type: 'config',
68
62
  order: 10,
69
63
  enabled: isStacked && hasMultiSeries,
70
- fn: (currentConfig) => {
64
+ fn: (currentConfig: unknown) => {
71
65
  const config = currentConfig as Record<string, unknown>
72
66
  const option = config.option as EchartOptionsProps | undefined
73
67
  if (!option) return currentConfig
@@ -87,18 +81,18 @@ export function StackToggle({
87
81
  return { ...config, option: { ...option, series: updatedSeries } }
88
82
  },
89
83
  })
90
- return () => unregisterTool(id, STACK_TOGGLE_TOOL_ID)
91
- }, [id, registerTool, unregisterTool, isStacked, hasMultiSeries])
84
+ return () => widgetStoreActions.unregisterTool(id, STACK_TOGGLE_TOOL_ID)
85
+ }, [id, isStacked, hasMultiSeries])
92
86
 
93
87
  // Initialize store with default value only if not already configured
94
88
  useEffect(() => {
95
89
  if (storeIsStacked !== undefined) return
96
- setWidget(id, { isStacked: effectiveDefaultIsStacked })
97
- }, [effectiveDefaultIsStacked, id, setWidget, storeIsStacked])
90
+ widgetStoreActions.setWidget(id, { isStacked: effectiveDefaultIsStacked })
91
+ }, [effectiveDefaultIsStacked, id, storeIsStacked])
98
92
 
99
93
  const handleToggle = useCallback(() => {
100
- setWidget(id, { isStacked: !isStacked })
101
- }, [isStacked, id, setWidget])
94
+ widgetStoreActions.setWidget(id, { isStacked: !isStacked })
95
+ }, [isStacked, id])
102
96
 
103
97
  const tooltipLabel = isStacked
104
98
  ? (labels?.unstacked ?? 'Disable stacking')