@carto/ps-react-ui 4.3.3 → 4.3.5

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 (301) hide show
  1. package/dist/components.js +3 -3
  2. package/dist/components.js.map +1 -1
  3. package/dist/{lasso-tool-BwRzEW7k.js → lasso-tool-wFqOD6wk.js} +13 -13
  4. package/dist/lasso-tool-wFqOD6wk.js.map +1 -0
  5. package/dist/types/components/common-types.d.ts +41 -0
  6. package/dist/types/components/types.d.ts +1 -1
  7. package/dist/types/widgets/echart/echart-ui.d.ts +1 -1
  8. package/dist/types/widgets/echart/types.d.ts +4 -0
  9. package/dist/widgets/actions.js +1 -1
  10. package/dist/widgets/bar.js +1 -1
  11. package/dist/widgets/category.js +1 -1
  12. package/dist/widgets/echart.js +96 -85
  13. package/dist/widgets/echart.js.map +1 -1
  14. package/dist/widgets/formula.js +1 -1
  15. package/dist/widgets/histogram.js +1 -1
  16. package/dist/widgets/markdown.js +1 -1
  17. package/dist/widgets/pie.js +1 -1
  18. package/dist/widgets/scatterplot.js +1 -1
  19. package/dist/widgets/spread.js +1 -1
  20. package/dist/widgets/table.js +1 -1
  21. package/dist/widgets/timeseries.js +1 -1
  22. package/dist/widgets/toolbar-actions.js.map +1 -1
  23. package/dist/widgets/wrapper.js +1 -1
  24. package/package.json +8 -3
  25. package/src/components/basemaps/basemaps.test.tsx +196 -0
  26. package/src/components/basemaps/basemaps.tsx +128 -0
  27. package/src/components/basemaps/const.ts +13 -0
  28. package/src/components/basemaps/group-wrapper.test.tsx +38 -0
  29. package/src/components/basemaps/group-wrapper.tsx +28 -0
  30. package/src/components/basemaps/group.test.tsx +52 -0
  31. package/src/components/basemaps/group.tsx +42 -0
  32. package/src/components/basemaps/header.test.tsx +54 -0
  33. package/src/components/basemaps/header.tsx +36 -0
  34. package/src/components/basemaps/styles.ts +76 -0
  35. package/src/components/basemaps/types.ts +30 -0
  36. package/src/components/common-types.ts +1 -0
  37. package/src/components/geolocation-controls/const.ts +6 -0
  38. package/src/components/geolocation-controls/geolocation-controls.test.tsx +133 -0
  39. package/src/components/geolocation-controls/geolocation-controls.tsx +95 -0
  40. package/src/components/geolocation-controls/types.ts +17 -0
  41. package/src/components/index.ts +64 -0
  42. package/src/components/lasso-tool/chip.tsx +37 -0
  43. package/src/components/lasso-tool/const.tsx +70 -0
  44. package/src/components/lasso-tool/icons.tsx +91 -0
  45. package/src/components/lasso-tool/lasso-tool-inline.test.tsx +168 -0
  46. package/src/components/lasso-tool/lasso-tool-inline.tsx +245 -0
  47. package/src/components/lasso-tool/lasso-tool.test.tsx +212 -0
  48. package/src/components/lasso-tool/lasso-tool.tsx +479 -0
  49. package/src/components/lasso-tool/styles.ts +143 -0
  50. package/src/components/lasso-tool/types.ts +114 -0
  51. package/src/components/list-data/list-data-skeleton.test.tsx +10 -0
  52. package/src/components/list-data/list-data-skeleton.tsx +40 -0
  53. package/src/components/list-data/list-data.test.tsx +94 -0
  54. package/src/components/list-data/list-data.tsx +106 -0
  55. package/src/components/list-data/styles.ts +37 -0
  56. package/src/components/list-data/types.ts +25 -0
  57. package/src/components/measurement-tools/const.tsx +108 -0
  58. package/src/components/measurement-tools/icons.tsx +54 -0
  59. package/src/components/measurement-tools/measurement-tools.test.tsx +165 -0
  60. package/src/components/measurement-tools/measurement-tools.tsx +443 -0
  61. package/src/components/measurement-tools/styles.ts +91 -0
  62. package/src/components/measurement-tools/types.ts +77 -0
  63. package/src/components/no-data-alert/no-data-alert.test.tsx +31 -0
  64. package/src/components/no-data-alert/no-data-alert.tsx +59 -0
  65. package/src/components/responsive-drawer/responsive-drawer.test.tsx +91 -0
  66. package/src/components/responsive-drawer/responsive-drawer.tsx +53 -0
  67. package/src/components/smart-tooltip/smart-tooltip.test.tsx +168 -0
  68. package/src/components/smart-tooltip/smart-tooltip.tsx +40 -0
  69. package/src/components/tooltip/tooltip.test.tsx +86 -0
  70. package/src/components/tooltip/tooltip.tsx +30 -0
  71. package/src/components/types.ts +1 -0
  72. package/src/components/zoom-controls/styles.ts +27 -0
  73. package/src/components/zoom-controls/types.ts +19 -0
  74. package/src/components/zoom-controls/zoom-controls.test.tsx +101 -0
  75. package/src/components/zoom-controls/zoom-controls.tsx +114 -0
  76. package/src/hooks/index.ts +2 -0
  77. package/src/hooks/use-debounce.ts +55 -0
  78. package/src/hooks/use-skeleton.test.tsx +32 -0
  79. package/src/hooks/use-skeleton.ts +19 -0
  80. package/src/hooks/use-widget-ref.ts +33 -0
  81. package/src/widgets/README.md +277 -0
  82. package/src/widgets/_shared/chart-config/config-factory.ts +67 -0
  83. package/src/widgets/_shared/chart-config/csv-modifiers.ts +56 -0
  84. package/src/widgets/_shared/chart-config/index.ts +21 -0
  85. package/src/widgets/_shared/chart-config/option-builders.ts +203 -0
  86. package/src/widgets/_shared/skeleton/index.ts +5 -0
  87. package/src/widgets/_shared/skeleton/styles.ts +20 -0
  88. package/src/widgets/actions/change-column/change-column-icon.tsx +10 -0
  89. package/src/widgets/actions/change-column/change-column.test.tsx +163 -0
  90. package/src/widgets/actions/change-column/change-column.tsx +141 -0
  91. package/src/widgets/actions/change-column/sortable-column-item.tsx +49 -0
  92. package/src/widgets/actions/change-column/types.ts +20 -0
  93. package/src/widgets/actions/download/download.test.tsx +322 -0
  94. package/src/widgets/actions/download/download.tsx +118 -0
  95. package/src/widgets/actions/download/exports.test.tsx +275 -0
  96. package/src/widgets/actions/download/exports.tsx +103 -0
  97. package/src/widgets/actions/download/types.ts +21 -0
  98. package/src/widgets/actions/fullscreen/fullscreen.test.tsx +269 -0
  99. package/src/widgets/actions/fullscreen/fullscreen.tsx +82 -0
  100. package/src/widgets/actions/fullscreen/styles.ts +17 -0
  101. package/src/widgets/actions/fullscreen/types.ts +27 -0
  102. package/src/widgets/actions/index.ts +51 -0
  103. package/src/widgets/actions/lock-selection/lock-selection.test.tsx +186 -0
  104. package/src/widgets/actions/lock-selection/lock-selection.tsx +133 -0
  105. package/src/widgets/actions/lock-selection/types.ts +41 -0
  106. package/src/widgets/actions/relative-data/relative-data.test.tsx +267 -0
  107. package/src/widgets/actions/relative-data/relative-data.tsx +133 -0
  108. package/src/widgets/actions/relative-data/style.ts +9 -0
  109. package/src/widgets/actions/relative-data/types.ts +31 -0
  110. package/src/widgets/actions/relative-data/utils.test.ts +223 -0
  111. package/src/widgets/actions/relative-data/utils.ts +58 -0
  112. package/src/widgets/actions/searcher/searcher-toggle.test.tsx +354 -0
  113. package/src/widgets/actions/searcher/searcher-toggle.tsx +73 -0
  114. package/src/widgets/actions/searcher/searcher.tsx +205 -0
  115. package/src/widgets/actions/searcher/types.ts +72 -0
  116. package/src/widgets/actions/shared/styles.ts +12 -0
  117. package/src/widgets/actions/stack-toggle/grouped-bar-chart-icon.tsx +14 -0
  118. package/src/widgets/actions/stack-toggle/stack-toggle.test.tsx +270 -0
  119. package/src/widgets/actions/stack-toggle/stack-toggle.tsx +146 -0
  120. package/src/widgets/actions/stack-toggle/types.ts +29 -0
  121. package/src/widgets/actions/zoom-toggle/index.ts +2 -0
  122. package/src/widgets/actions/zoom-toggle/style.ts +14 -0
  123. package/src/widgets/actions/zoom-toggle/types.ts +44 -0
  124. package/src/widgets/actions/zoom-toggle/zoom-toggle.tsx +186 -0
  125. package/src/widgets/bar/config.ts +122 -0
  126. package/src/widgets/bar/index.ts +9 -0
  127. package/src/widgets/bar/skeleton.tsx +60 -0
  128. package/src/widgets/bar/style.ts +33 -0
  129. package/src/widgets/bar/types.ts +16 -0
  130. package/src/widgets/category/category-ui.test.tsx +399 -0
  131. package/src/widgets/category/category-ui.tsx +156 -0
  132. package/src/widgets/category/components/category-bar.tsx +28 -0
  133. package/src/widgets/category/components/category-legend.tsx +30 -0
  134. package/src/widgets/category/components/category-row-multi.tsx +50 -0
  135. package/src/widgets/category/components/category-row-other.tsx +23 -0
  136. package/src/widgets/category/components/category-row-single.tsx +47 -0
  137. package/src/widgets/category/components/index.ts +14 -0
  138. package/src/widgets/category/config.ts +85 -0
  139. package/src/widgets/category/index.ts +30 -0
  140. package/src/widgets/category/skeleton.tsx +24 -0
  141. package/src/widgets/category/style.ts +133 -0
  142. package/src/widgets/category/types.ts +40 -0
  143. package/src/widgets/echart/const.ts +1 -0
  144. package/src/widgets/echart/echart-ui.test.tsx +537 -0
  145. package/src/widgets/echart/echart-ui.tsx +92 -0
  146. package/src/widgets/echart/echart.test.tsx +562 -0
  147. package/src/widgets/echart/echart.tsx +68 -0
  148. package/src/widgets/echart/index.ts +16 -0
  149. package/src/widgets/echart/options.ts +53 -0
  150. package/src/widgets/echart/types.ts +45 -0
  151. package/src/widgets/echart/utils.ts +169 -0
  152. package/src/widgets/error/error.test.tsx +331 -0
  153. package/src/widgets/error/error.tsx +40 -0
  154. package/src/widgets/error/index.ts +2 -0
  155. package/src/widgets/error/types.ts +14 -0
  156. package/src/widgets/formula/components/item.test.tsx +249 -0
  157. package/src/widgets/formula/components/item.tsx +18 -0
  158. package/src/widgets/formula/components/prefix.test.tsx +341 -0
  159. package/src/widgets/formula/components/prefix.tsx +18 -0
  160. package/src/widgets/formula/components/row.test.tsx +364 -0
  161. package/src/widgets/formula/components/row.tsx +21 -0
  162. package/src/widgets/formula/components/series.tsx +34 -0
  163. package/src/widgets/formula/components/suffix.test.tsx +383 -0
  164. package/src/widgets/formula/components/suffix.tsx +28 -0
  165. package/src/widgets/formula/components/value.test.tsx +329 -0
  166. package/src/widgets/formula/components/value.tsx +29 -0
  167. package/src/widgets/formula/config.ts +27 -0
  168. package/src/widgets/formula/formula-ui.test.tsx +399 -0
  169. package/src/widgets/formula/formula-ui.tsx +27 -0
  170. package/src/widgets/formula/index.ts +24 -0
  171. package/src/widgets/formula/serializer.test.tsx +144 -0
  172. package/src/widgets/formula/serializer.ts +28 -0
  173. package/src/widgets/formula/skeleton.tsx +10 -0
  174. package/src/widgets/formula/style.ts +23 -0
  175. package/src/widgets/formula/types.ts +50 -0
  176. package/src/widgets/histogram/config.ts +143 -0
  177. package/src/widgets/histogram/index.ts +8 -0
  178. package/src/widgets/histogram/skeleton.tsx +52 -0
  179. package/src/widgets/histogram/style.ts +8 -0
  180. package/src/widgets/histogram/types.ts +17 -0
  181. package/src/widgets/index.ts +25 -0
  182. package/src/widgets/loader/index.ts +4 -0
  183. package/src/widgets/loader/loader.tsx +70 -0
  184. package/src/widgets/loader/types.ts +11 -0
  185. package/src/widgets/loader/utils.test.ts +112 -0
  186. package/src/widgets/loader/utils.ts +35 -0
  187. package/src/widgets/markdown/config.ts +18 -0
  188. package/src/widgets/markdown/index.ts +14 -0
  189. package/src/widgets/markdown/markdown-ui.test.tsx +341 -0
  190. package/src/widgets/markdown/markdown-ui.tsx +52 -0
  191. package/src/widgets/markdown/markdown.tsx +20 -0
  192. package/src/widgets/markdown/skeleton.tsx +12 -0
  193. package/src/widgets/markdown/style.ts +28 -0
  194. package/src/widgets/markdown/types.ts +28 -0
  195. package/src/widgets/no-data/index.ts +2 -0
  196. package/src/widgets/no-data/no-data.test.tsx +447 -0
  197. package/src/widgets/no-data/no-data.tsx +116 -0
  198. package/src/widgets/no-data/style.ts +18 -0
  199. package/src/widgets/no-data/types.ts +72 -0
  200. package/src/widgets/note/index.ts +2 -0
  201. package/src/widgets/note/note.test.tsx +391 -0
  202. package/src/widgets/note/note.tsx +114 -0
  203. package/src/widgets/note/style.ts +29 -0
  204. package/src/widgets/note/types.ts +9 -0
  205. package/src/widgets/pie/config.ts +177 -0
  206. package/src/widgets/pie/index.ts +8 -0
  207. package/src/widgets/pie/skeleton.tsx +70 -0
  208. package/src/widgets/pie/style.ts +8 -0
  209. package/src/widgets/pie/types.ts +16 -0
  210. package/src/widgets/range/components/range-item.tsx +213 -0
  211. package/src/widgets/range/config.ts +10 -0
  212. package/src/widgets/range/index.ts +16 -0
  213. package/src/widgets/range/range-ui.test.tsx +203 -0
  214. package/src/widgets/range/range-ui.tsx +11 -0
  215. package/src/widgets/range/serializer.test.ts +70 -0
  216. package/src/widgets/range/serializer.ts +27 -0
  217. package/src/widgets/range/skeleton.tsx +14 -0
  218. package/src/widgets/range/style.ts +37 -0
  219. package/src/widgets/range/types.ts +39 -0
  220. package/src/widgets/scatterplot/config.ts +138 -0
  221. package/src/widgets/scatterplot/index.ts +8 -0
  222. package/src/widgets/scatterplot/skeleton.tsx +59 -0
  223. package/src/widgets/scatterplot/style.ts +21 -0
  224. package/src/widgets/scatterplot/types.ts +17 -0
  225. package/src/widgets/selection-summary/index.ts +6 -0
  226. package/src/widgets/selection-summary/selection-summary.tsx +46 -0
  227. package/src/widgets/selection-summary/style.ts +10 -0
  228. package/src/widgets/selection-summary/types.ts +14 -0
  229. package/src/widgets/skeleton-loader/index.ts +2 -0
  230. package/src/widgets/skeleton-loader/skeleton-loader.test.tsx +139 -0
  231. package/src/widgets/skeleton-loader/skeleton-loader.tsx +28 -0
  232. package/src/widgets/skeleton-loader/types.ts +8 -0
  233. package/src/widgets/spread/components/max-value.tsx +29 -0
  234. package/src/widgets/spread/components/min-value.tsx +29 -0
  235. package/src/widgets/spread/components/separator.tsx +6 -0
  236. package/src/widgets/spread/config.ts +34 -0
  237. package/src/widgets/spread/index.ts +23 -0
  238. package/src/widgets/spread/skeleton.tsx +10 -0
  239. package/src/widgets/spread/spread-ui.test.tsx +368 -0
  240. package/src/widgets/spread/spread-ui.tsx +29 -0
  241. package/src/widgets/spread/style.ts +22 -0
  242. package/src/widgets/spread/types.ts +47 -0
  243. package/src/widgets/stores/index.ts +9 -0
  244. package/src/widgets/stores/types.ts +192 -0
  245. package/src/widgets/stores/widget-store.test.ts +601 -0
  246. package/src/widgets/stores/widget-store.ts +239 -0
  247. package/src/widgets/subheader/index.ts +3 -0
  248. package/src/widgets/subheader/style.ts +20 -0
  249. package/src/widgets/subheader/subheader.test.tsx +45 -0
  250. package/src/widgets/subheader/subheader.tsx +16 -0
  251. package/src/widgets/subheader/types.ts +11 -0
  252. package/src/widgets/table/components/cell-header.tsx +58 -0
  253. package/src/widgets/table/components/cell.tsx +80 -0
  254. package/src/widgets/table/components/index.ts +4 -0
  255. package/src/widgets/table/components/pagination-actions.tsx +67 -0
  256. package/src/widgets/table/components/pagination.tsx +41 -0
  257. package/src/widgets/table/components/row.tsx +60 -0
  258. package/src/widgets/table/config.ts +71 -0
  259. package/src/widgets/table/helpers.test.ts +244 -0
  260. package/src/widgets/table/helpers.ts +107 -0
  261. package/src/widgets/table/hooks/index.ts +7 -0
  262. package/src/widgets/table/hooks/use-pagination.test.ts +294 -0
  263. package/src/widgets/table/hooks/use-pagination.ts +155 -0
  264. package/src/widgets/table/hooks/use-selection.test.ts +504 -0
  265. package/src/widgets/table/hooks/use-selection.ts +189 -0
  266. package/src/widgets/table/hooks/use-sort.test.ts +296 -0
  267. package/src/widgets/table/hooks/use-sort.ts +138 -0
  268. package/src/widgets/table/index.ts +53 -0
  269. package/src/widgets/table/serializer.ts +54 -0
  270. package/src/widgets/table/skeleton.tsx +48 -0
  271. package/src/widgets/table/style.ts +34 -0
  272. package/src/widgets/table/table-ui.tsx +64 -0
  273. package/src/widgets/table/types.ts +223 -0
  274. package/src/widgets/timeseries/config.ts +135 -0
  275. package/src/widgets/timeseries/index.ts +8 -0
  276. package/src/widgets/timeseries/skeleton.tsx +55 -0
  277. package/src/widgets/timeseries/style.ts +36 -0
  278. package/src/widgets/timeseries/types.ts +17 -0
  279. package/src/widgets/toolbar-actions/index.ts +6 -0
  280. package/src/widgets/toolbar-actions/styles.ts +38 -0
  281. package/src/widgets/toolbar-actions/toolbar-actions.test.tsx +691 -0
  282. package/src/widgets/toolbar-actions/toolbar-actions.tsx +145 -0
  283. package/src/widgets/toolbar-actions/types.ts +60 -0
  284. package/src/widgets/wrapper/components/actions.test.tsx +101 -0
  285. package/src/widgets/wrapper/components/actions.tsx +30 -0
  286. package/src/widgets/wrapper/components/options.test.tsx +323 -0
  287. package/src/widgets/wrapper/components/options.tsx +73 -0
  288. package/src/widgets/wrapper/components/title.test.tsx +126 -0
  289. package/src/widgets/wrapper/components/title.tsx +32 -0
  290. package/src/widgets/wrapper/index.ts +16 -0
  291. package/src/widgets/wrapper/styles.ts +98 -0
  292. package/src/widgets/wrapper/types.ts +55 -0
  293. package/src/widgets/wrapper/wrapper-ui.test.tsx +232 -0
  294. package/src/widgets/wrapper/wrapper-ui.tsx +57 -0
  295. package/src/widgets/wrapper/wrapper.test.tsx +365 -0
  296. package/src/widgets/wrapper/wrapper.tsx +50 -0
  297. package/dist/lasso-tool-BwRzEW7k.js.map +0 -1
  298. package/dist/types/common/common.d.ts +0 -3
  299. package/dist/types/common/index.d.ts +0 -26
  300. package/dist/types/common/lasso-tools.d.ts +0 -36
  301. package/dist/types/common/measurement-tools.d.ts +0 -65
@@ -0,0 +1,239 @@
1
+ import { create } from 'zustand'
2
+ import type { WidgetState, WidgetStore, ToolRegistration } from './types'
3
+
4
+ // Track active pipeline executions for cancellation
5
+ const activePipelines = new Map<string, number>()
6
+
7
+ /**
8
+ * Zustand store for managing widget state across the application.
9
+ *
10
+ * This store provides centralized state management for all widget UI components
11
+ *
12
+ * @example
13
+ * ```tsx
14
+ * // Import the store
15
+ * import { useWidgetStore } from '@carto/ps-react-ui/widgets'
16
+ *
17
+ * // Use in a component
18
+ * function MyWidget() {
19
+ * const setWidget = useWidgetStore((state) => state.setWidget)
20
+ * const widget = useWidgetStore((state) => state.widgets['my-widget'])
21
+ *
22
+ * useEffect(() => {
23
+ * setWidget({
24
+ * id: 'my-widget',
25
+ * type: 'formula',
26
+ * title: 'Total Sales',
27
+ * isLoading: false,
28
+ * visible: true,
29
+ * data: { value: 1000, prefix: '$' }
30
+ * })
31
+ * }, [setWidget])
32
+ *
33
+ * return <div>{widget?.data?.value}</div>
34
+ * }
35
+ * ```
36
+ *
37
+ * @example
38
+ * ```tsx
39
+ * // Get widgets by type
40
+ * const getWidgetsByType = useWidgetStore((state) => state.getWidgetsByType)
41
+ * const formulaWidgets = getWidgetsByType('formula')
42
+ * ```
43
+ */
44
+ export const useWidgetStore = create<WidgetStore>()((set, get) => ({
45
+ // State
46
+ widgets: {},
47
+
48
+ // Actions
49
+ setWidget: (id, widget) =>
50
+ set((state) => {
51
+ const current =
52
+ state.widgets[id] ?? ({} as WidgetStore['widgets'][string])
53
+ return {
54
+ widgets: {
55
+ ...state.widgets,
56
+ [id]: {
57
+ ...current,
58
+ ...widget,
59
+ id,
60
+ },
61
+ },
62
+ }
63
+ }),
64
+
65
+ removeWidget: (id) =>
66
+ set((state) => {
67
+ const widgets = { ...state.widgets }
68
+
69
+ delete widgets[id]
70
+
71
+ return { widgets }
72
+ }),
73
+
74
+ clearWidgets: () =>
75
+ set({
76
+ widgets: {},
77
+ }),
78
+
79
+ getWidget: <T extends WidgetState>(id: string) => {
80
+ return get().widgets[id] as T | undefined
81
+ },
82
+
83
+ registerTool: (widgetId: string, tool: ToolRegistration) =>
84
+ set((state) => {
85
+ const current = state.widgets[widgetId] ?? ({} as WidgetState)
86
+ const registeredTools = current.registeredTools ?? []
87
+
88
+ // Remove existing tool with same id if present
89
+ const filteredTools = registeredTools.filter(
90
+ (t: ToolRegistration) => t.id !== tool.id,
91
+ )
92
+
93
+ return {
94
+ widgets: {
95
+ ...state.widgets,
96
+ [widgetId]: {
97
+ ...current,
98
+ id: widgetId,
99
+ registeredTools: [...filteredTools, tool],
100
+ },
101
+ },
102
+ }
103
+ }),
104
+
105
+ unregisterTool: (widgetId: string, toolId: string) =>
106
+ set((state) => {
107
+ const current = state.widgets[widgetId]
108
+ if (!current) return state
109
+
110
+ const registeredTools = current.registeredTools ?? []
111
+ const filteredTools = registeredTools.filter(
112
+ (t: ToolRegistration) => t.id !== toolId,
113
+ )
114
+
115
+ return {
116
+ widgets: {
117
+ ...state.widgets,
118
+ [widgetId]: {
119
+ ...current,
120
+ registeredTools: filteredTools,
121
+ },
122
+ },
123
+ }
124
+ }),
125
+
126
+ updateToolConfig: (
127
+ widgetId: string,
128
+ toolId: string,
129
+ config: Record<string, unknown>,
130
+ ) =>
131
+ set((state) => {
132
+ const current = state.widgets[widgetId]
133
+ if (!current) return state
134
+
135
+ const registeredTools = current.registeredTools ?? []
136
+ const updatedTools = registeredTools.map((tool: ToolRegistration) =>
137
+ tool.id === toolId
138
+ ? {
139
+ ...tool,
140
+ config: { ...tool.config, ...config },
141
+ }
142
+ : tool,
143
+ )
144
+
145
+ return {
146
+ widgets: {
147
+ ...state.widgets,
148
+ [widgetId]: {
149
+ ...current,
150
+ registeredTools: updatedTools,
151
+ },
152
+ },
153
+ }
154
+ }),
155
+
156
+ setToolEnabled: (widgetId: string, toolId: string, enabled: boolean) =>
157
+ set((state) => {
158
+ const current = state.widgets[widgetId]
159
+ if (!current) return state
160
+
161
+ const registeredTools = current.registeredTools ?? []
162
+ const updatedTools = registeredTools.map((tool: ToolRegistration) =>
163
+ tool.id === toolId ? { ...tool, enabled } : tool,
164
+ )
165
+
166
+ return {
167
+ widgets: {
168
+ ...state.widgets,
169
+ [widgetId]: {
170
+ ...current,
171
+ registeredTools: updatedTools,
172
+ },
173
+ },
174
+ }
175
+ }),
176
+
177
+ executeToolPipeline: async (widgetId: string, sourceData: unknown) => {
178
+ const widget = get().widgets[widgetId]
179
+ if (!widget) return
180
+
181
+ // Cancel any in-progress pipeline for this widget
182
+ const currentExecution = (activePipelines.get(widgetId) ?? 0) + 1
183
+ activePipelines.set(widgetId, currentExecution)
184
+
185
+ const widgetWithTools = widget
186
+
187
+ // Build set of tool IDs that should be disabled
188
+ const disabledToolIds = new Set<string>()
189
+ for (const tool of widgetWithTools.registeredTools ?? []) {
190
+ if (tool.enabled && tool.disables) {
191
+ tool.disables.forEach((id) => disabledToolIds.add(id))
192
+ }
193
+ }
194
+
195
+ // Sort tools by order and filter enabled only, excluding disabled tools
196
+ const sortedTools = [...(widgetWithTools.registeredTools ?? [])]
197
+ .filter((tool) => tool.enabled && !disabledToolIds.has(tool.id))
198
+ .sort((a, b) => a.order - b.order)
199
+
200
+ // Execute pipeline - handle both sync and async tools
201
+ let transformedData = sourceData
202
+ for (const tool of sortedTools) {
203
+ // Check if this execution was cancelled
204
+ if (activePipelines.get(widgetId) !== currentExecution) {
205
+ return
206
+ }
207
+
208
+ try {
209
+ // Call tool function (may return Promise or direct value)
210
+ transformedData = await tool.fn(transformedData, tool.config)
211
+ } catch (error) {
212
+ // eslint-disable-next-line no-console
213
+ console.error(`Tool ${tool.id} failed for widget ${widgetId}:`, error)
214
+ // Continue with current data to prevent one tool from breaking all
215
+ }
216
+ }
217
+
218
+ // Single store update with final transformed data
219
+ set((state) => {
220
+ const currentWidget = state.widgets[widgetId]
221
+ if (!currentWidget) return state
222
+
223
+ return {
224
+ widgets: {
225
+ ...state.widgets,
226
+ [widgetId]: {
227
+ ...currentWidget,
228
+ data: transformedData,
229
+ },
230
+ },
231
+ }
232
+ })
233
+
234
+ // Clean up tracking
235
+ if (activePipelines.get(widgetId) === currentExecution) {
236
+ activePipelines.delete(widgetId)
237
+ }
238
+ },
239
+ }))
@@ -0,0 +1,3 @@
1
+ export { WidgetSubHeader } from './subheader'
2
+
3
+ export type { WidgetSubHeaderProps } from './types'
@@ -0,0 +1,20 @@
1
+ import type { SxProps, Theme } from '@mui/material'
2
+
3
+ export const styles = {
4
+ root: {
5
+ display: 'flex',
6
+ alignItems: 'center',
7
+ gap: ({ spacing }) => spacing(1),
8
+ minHeight: ({ spacing }) => spacing(3),
9
+ },
10
+ slotLeft: {
11
+ flexShrink: 0,
12
+ },
13
+ slotRight: {
14
+ flexGrow: 1,
15
+ display: 'flex',
16
+ justifyContent: 'flex-end',
17
+ alignItems: 'center',
18
+ gap: ({ spacing }) => spacing(1),
19
+ },
20
+ } satisfies Record<string, SxProps<Theme>>
@@ -0,0 +1,45 @@
1
+ import { describe, test, expect } from 'vitest'
2
+ import { render, screen } from '@testing-library/react'
3
+ import { WidgetSubHeader } from './subheader'
4
+
5
+ describe('WidgetSubHeader', () => {
6
+ test('renders empty right slot by default', () => {
7
+ const { container } = render(<WidgetSubHeader />)
8
+ expect(container.firstChild).toBeTruthy()
9
+ })
10
+
11
+ test('renders slotLeft content', () => {
12
+ render(<WidgetSubHeader slotLeft={<span>Left Content</span>} />)
13
+ expect(screen.getByText('Left Content')).toBeTruthy()
14
+ })
15
+
16
+ test('renders slotRight content', () => {
17
+ render(<WidgetSubHeader slotRight={<span>Right Content</span>} />)
18
+ expect(screen.getByText('Right Content')).toBeTruthy()
19
+ })
20
+
21
+ test('renders both slots when provided', () => {
22
+ render(
23
+ <WidgetSubHeader
24
+ slotLeft={<span>Left</span>}
25
+ slotRight={<span>Right</span>}
26
+ />,
27
+ )
28
+ expect(screen.getByText('Left')).toBeTruthy()
29
+ expect(screen.getByText('Right')).toBeTruthy()
30
+ })
31
+
32
+ test('right slot takes full width when left is empty (flex layout)', () => {
33
+ const { container } = render(<WidgetSubHeader />)
34
+ // The right slot should have flexGrow: 1 for full width behavior
35
+ const rightSlot = container.querySelector('[class*="MuiBox-root"]')
36
+ expect(rightSlot).toBeTruthy()
37
+ })
38
+
39
+ test('renders with custom sx prop', () => {
40
+ const { container } = render(
41
+ <WidgetSubHeader sx={{ backgroundColor: 'red' }} />,
42
+ )
43
+ expect(container.firstChild).toBeTruthy()
44
+ })
45
+ })
@@ -0,0 +1,16 @@
1
+ import { Box } from '@mui/material'
2
+ import { styles } from './style'
3
+ import type { WidgetSubHeaderProps } from './types'
4
+
5
+ export function WidgetSubHeader({
6
+ slotLeft,
7
+ slotRight,
8
+ sx,
9
+ }: WidgetSubHeaderProps) {
10
+ return (
11
+ <Box sx={{ ...styles.root, ...sx }}>
12
+ {slotLeft && <Box sx={styles.slotLeft}>{slotLeft}</Box>}
13
+ <Box sx={styles.slotRight}>{slotRight}</Box>
14
+ </Box>
15
+ )
16
+ }
@@ -0,0 +1,11 @@
1
+ import type { SxProps, Theme } from '@mui/material'
2
+ import type { ReactNode } from 'react'
3
+
4
+ export interface WidgetSubHeaderProps {
5
+ /** Content for the left slot */
6
+ slotLeft?: ReactNode
7
+ /** Content for the right slot*/
8
+ slotRight?: ReactNode
9
+ /** Custom styles */
10
+ sx?: SxProps<Theme>
11
+ }
@@ -0,0 +1,58 @@
1
+ import { TableCell, TableSortLabel, Checkbox } from '@mui/material'
2
+ import type { CellHeaderProps } from '../types'
3
+
4
+ /**
5
+ * Table cell header component with optional sorting and select-all checkbox
6
+ */
7
+ export function CellHeader({
8
+ column,
9
+ sortState,
10
+ onSort,
11
+ isSelectAll,
12
+ isAllSelected,
13
+ isIndeterminate,
14
+ onSelectAll,
15
+ }: CellHeaderProps) {
16
+ // Render select-all checkbox
17
+ if (isSelectAll) {
18
+ return (
19
+ <TableCell padding='checkbox'>
20
+ <Checkbox
21
+ checked={isAllSelected}
22
+ indeterminate={isIndeterminate}
23
+ onChange={onSelectAll}
24
+ inputProps={{ 'aria-label': 'select all rows' }}
25
+ />
26
+ </TableCell>
27
+ )
28
+ }
29
+
30
+ const isSorted = sortState?.columnId === column.id
31
+ const sortDirection = isSorted ? sortState.direction : undefined
32
+
33
+ // Render sortable column header
34
+ if (column.sortable && onSort) {
35
+ return (
36
+ <TableCell
37
+ align={column.align}
38
+ style={{ width: column.width }}
39
+ sortDirection={sortDirection}
40
+ >
41
+ <TableSortLabel
42
+ active={isSorted}
43
+ direction={sortDirection ?? 'asc'}
44
+ onClick={() => onSort(column.id)}
45
+ >
46
+ {column.label}
47
+ </TableSortLabel>
48
+ </TableCell>
49
+ )
50
+ }
51
+
52
+ // Render non-sortable column header
53
+ return (
54
+ <TableCell align={column.align} style={{ width: column.width }}>
55
+ {column.label}
56
+ </TableCell>
57
+ )
58
+ }
@@ -0,0 +1,80 @@
1
+ import { TableCell as MuiTableCell, Link, Typography } from '@mui/material'
2
+ import ReactMarkdown, { type Components } from 'react-markdown'
3
+ import type { TableColumn } from '../types'
4
+ import { getCellValue } from '../helpers'
5
+ import type { ReactNode } from 'react'
6
+
7
+ /**
8
+ * Props for Cell component
9
+ */
10
+ export interface CellProps {
11
+ /** Column definition */
12
+ column: TableColumn
13
+ /** Cell value */
14
+ value: unknown
15
+ }
16
+
17
+ const DEFAULT_P = ({ children }: { children?: ReactNode }) => (
18
+ <Typography component='span' variant='body2' color='inherit'>
19
+ {children}
20
+ </Typography>
21
+ )
22
+
23
+ /**
24
+ * Markdown components for cell content
25
+ * Uses compact styling suitable for table cells (reusing Note widget pattern)
26
+ */
27
+ const CELL_MARKDOWN_COMPONENTS: Components = {
28
+ h1: DEFAULT_P,
29
+ h2: DEFAULT_P,
30
+ h3: DEFAULT_P,
31
+ p: DEFAULT_P,
32
+ a: ({ children, href }) => {
33
+ const isExternal = href?.startsWith('http')
34
+ return (
35
+ <Link
36
+ href={href}
37
+ target={isExternal ? '_blank' : undefined}
38
+ rel={isExternal ? 'noopener noreferrer' : undefined}
39
+ color='text.primary'
40
+ underline='always'
41
+ >
42
+ {children}
43
+ </Link>
44
+ )
45
+ },
46
+ img: () => null,
47
+ ul: DEFAULT_P,
48
+ ol: DEFAULT_P,
49
+ li: DEFAULT_P,
50
+ }
51
+
52
+ /**
53
+ * Table cell component with automatic markdown support for string values
54
+ * Markdown is rendered when the raw value is a string and no formatter is defined.
55
+ * If a formatter is used, its output is rendered directly without markdown parsing.
56
+ */
57
+ export function Cell({ column, value }: CellProps) {
58
+ // If formatter is defined, use it directly without markdown
59
+ if (column.formatter) {
60
+ return (
61
+ <MuiTableCell align={column.align}>
62
+ {column.formatter(value)}
63
+ </MuiTableCell>
64
+ )
65
+ }
66
+
67
+ // For string values without formatter, render as markdown
68
+ if (typeof value === 'string') {
69
+ return (
70
+ <MuiTableCell align={column.align}>
71
+ <ReactMarkdown components={CELL_MARKDOWN_COMPONENTS}>
72
+ {value}
73
+ </ReactMarkdown>
74
+ </MuiTableCell>
75
+ )
76
+ }
77
+
78
+ // For non-string values, render directly
79
+ return <MuiTableCell align={column.align}>{getCellValue(value)}</MuiTableCell>
80
+ }
@@ -0,0 +1,4 @@
1
+ export { CellHeader } from './cell-header'
2
+ export { Row } from './row'
3
+ export { Pagination } from './pagination'
4
+ export { PaginationActions } from './pagination-actions'
@@ -0,0 +1,67 @@
1
+ import { Box, IconButton } from '@mui/material'
2
+ import FirstPageIcon from '@mui/icons-material/FirstPage'
3
+ import KeyboardArrowLeft from '@mui/icons-material/KeyboardArrowLeft'
4
+ import KeyboardArrowRight from '@mui/icons-material/KeyboardArrowRight'
5
+ import LastPageIcon from '@mui/icons-material/LastPage'
6
+ import type { PaginationActionsProps } from '../types'
7
+
8
+ /**
9
+ * Custom pagination actions with first/prev/next/last buttons
10
+ */
11
+ export function PaginationActions({
12
+ count,
13
+ page,
14
+ rowsPerPage,
15
+ onPageChange,
16
+ }: PaginationActionsProps) {
17
+ const lastPage = Math.max(0, Math.ceil(count / rowsPerPage) - 1)
18
+
19
+ const handleFirstPage = (event: React.MouseEvent<HTMLButtonElement>) => {
20
+ onPageChange(event, 0)
21
+ }
22
+
23
+ const handlePreviousPage = (event: React.MouseEvent<HTMLButtonElement>) => {
24
+ onPageChange(event, page - 1)
25
+ }
26
+
27
+ const handleNextPage = (event: React.MouseEvent<HTMLButtonElement>) => {
28
+ onPageChange(event, page + 1)
29
+ }
30
+
31
+ const handleLastPage = (event: React.MouseEvent<HTMLButtonElement>) => {
32
+ onPageChange(event, lastPage)
33
+ }
34
+
35
+ return (
36
+ <Box sx={{ flexShrink: 0, ml: 2.5 }}>
37
+ <IconButton
38
+ onClick={handleFirstPage}
39
+ disabled={page === 0}
40
+ aria-label='first page'
41
+ >
42
+ <FirstPageIcon />
43
+ </IconButton>
44
+ <IconButton
45
+ onClick={handlePreviousPage}
46
+ disabled={page === 0}
47
+ aria-label='previous page'
48
+ >
49
+ <KeyboardArrowLeft />
50
+ </IconButton>
51
+ <IconButton
52
+ onClick={handleNextPage}
53
+ disabled={page >= lastPage}
54
+ aria-label='next page'
55
+ >
56
+ <KeyboardArrowRight />
57
+ </IconButton>
58
+ <IconButton
59
+ onClick={handleLastPage}
60
+ disabled={page >= lastPage}
61
+ aria-label='last page'
62
+ >
63
+ <LastPageIcon />
64
+ </IconButton>
65
+ </Box>
66
+ )
67
+ }
@@ -0,0 +1,41 @@
1
+ import { TablePagination as MuiTablePagination } from '@mui/material'
2
+ import type { PaginationProps } from '../types'
3
+ import { PaginationActions } from './pagination-actions'
4
+
5
+ /**
6
+ * Table pagination component
7
+ */
8
+ export function Pagination({
9
+ page,
10
+ rowsPerPage,
11
+ total,
12
+ rowsPerPageOptions = [5, 10, 25, 50],
13
+ onPageChange,
14
+ onRowsPerPageChange,
15
+ }: PaginationProps) {
16
+ const handlePageChange = (
17
+ _event: React.MouseEvent<HTMLButtonElement> | null,
18
+ newPage: number,
19
+ ) => {
20
+ onPageChange(newPage)
21
+ }
22
+
23
+ const handleRowsPerPageChange = (
24
+ event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
25
+ ) => {
26
+ onRowsPerPageChange(parseInt(event.target.value, 10))
27
+ }
28
+
29
+ return (
30
+ <MuiTablePagination
31
+ component='div'
32
+ count={total}
33
+ page={page}
34
+ rowsPerPage={rowsPerPage}
35
+ rowsPerPageOptions={rowsPerPageOptions}
36
+ onPageChange={handlePageChange}
37
+ onRowsPerPageChange={handleRowsPerPageChange}
38
+ ActionsComponent={PaginationActions}
39
+ />
40
+ )
41
+ }
@@ -0,0 +1,60 @@
1
+ import { TableRow as MuiTableRow, TableCell, Checkbox } from '@mui/material'
2
+ import type { RowProps } from '../types'
3
+ import { Cell } from './cell'
4
+
5
+ /**
6
+ * Table row component with selection and hover support
7
+ */
8
+ export function Row({
9
+ row,
10
+ columns,
11
+ isSelected = false,
12
+ selectable = false,
13
+ onClick,
14
+ onSelect,
15
+ onMouseEnter,
16
+ onMouseLeave,
17
+ }: RowProps) {
18
+ const handleClick = () => {
19
+ onClick?.(row)
20
+ }
21
+
22
+ const handleCheckboxClick = (event: React.MouseEvent) => {
23
+ event.stopPropagation()
24
+ onSelect?.(row)
25
+ }
26
+
27
+ const handleMouseEnter = () => {
28
+ onMouseEnter?.(row)
29
+ }
30
+
31
+ const handleMouseLeave = () => {
32
+ onMouseLeave?.()
33
+ }
34
+
35
+ return (
36
+ <MuiTableRow
37
+ hover={!!onMouseEnter || !!onMouseLeave || !!onClick}
38
+ selected={isSelected}
39
+ onClick={handleClick}
40
+ onMouseEnter={handleMouseEnter}
41
+ onMouseLeave={handleMouseLeave}
42
+ sx={{
43
+ cursor: onClick ? 'pointer' : 'default',
44
+ }}
45
+ >
46
+ {selectable && (
47
+ <TableCell padding='checkbox'>
48
+ <Checkbox
49
+ checked={isSelected}
50
+ onClick={handleCheckboxClick}
51
+ inputProps={{ 'aria-label': `select row ${row.id}` }}
52
+ />
53
+ </TableCell>
54
+ )}
55
+ {columns.map((column) => (
56
+ <Cell key={column.id} column={column} value={row[column.id]} />
57
+ ))}
58
+ </MuiTableRow>
59
+ )
60
+ }