@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,101 @@
1
+ import { describe, test, expect, vi } from 'vitest'
2
+ import { render, screen, fireEvent } from '@testing-library/react'
3
+ import { ZoomControlsUI } from './zoom-controls'
4
+
5
+ describe('ZoomControlsUI', () => {
6
+ const defaultProps = {
7
+ zoom: 10,
8
+ onChange: vi.fn(),
9
+ }
10
+
11
+ test('renders correctly with default props', () => {
12
+ render(<ZoomControlsUI {...defaultProps} />)
13
+
14
+ expect(screen.getByLabelText('Increase zoom')).toBeTruthy()
15
+ expect(screen.getByLabelText('Decrease zoom')).toBeTruthy()
16
+ expect(screen.getByText('10')).toBeTruthy()
17
+ })
18
+
19
+ test('handles zoom increase correctly', () => {
20
+ render(<ZoomControlsUI {...defaultProps} />)
21
+
22
+ fireEvent.click(screen.getByLabelText('Increase zoom'))
23
+ expect(defaultProps.onChange).toHaveBeenCalledWith(11)
24
+ })
25
+
26
+ test('handles zoom decrease correctly', () => {
27
+ render(<ZoomControlsUI {...defaultProps} />)
28
+
29
+ fireEvent.click(screen.getByLabelText('Decrease zoom'))
30
+ expect(defaultProps.onChange).toHaveBeenCalledWith(9)
31
+ })
32
+
33
+ test('respects min and max zoom limits', () => {
34
+ const props = {
35
+ ...defaultProps,
36
+ zoom: 24,
37
+ maxZoom: 24,
38
+ minZoom: 5,
39
+ }
40
+
41
+ const { rerender } = render(<ZoomControlsUI {...props} />)
42
+
43
+ fireEvent.click(screen.getByLabelText('Increase zoom'))
44
+ expect(props.onChange).toHaveBeenCalledWith(24) // Should not exceed maxZoom
45
+
46
+ props.zoom = 5
47
+ rerender(<ZoomControlsUI {...props} />)
48
+
49
+ fireEvent.click(screen.getByLabelText('Decrease zoom'))
50
+ expect(props.onChange).toHaveBeenCalledWith(5) // Should not go below minZoom
51
+ })
52
+
53
+ test('renders reset button when onReset is provided', () => {
54
+ const props = {
55
+ ...defaultProps,
56
+ onReset: vi.fn(),
57
+ }
58
+
59
+ render(<ZoomControlsUI {...props} />)
60
+
61
+ const resetButton = screen.getByLabelText('Reset action')
62
+ expect(resetButton).toBeTruthy()
63
+
64
+ fireEvent.click(resetButton)
65
+ expect(props.onReset).toHaveBeenCalledTimes(1)
66
+ })
67
+
68
+ test('does not render reset button when onReset is not provided', () => {
69
+ render(<ZoomControlsUI {...defaultProps} />)
70
+
71
+ expect(screen.queryByLabelText('Reset action')).toBeNull()
72
+ })
73
+
74
+ test('hides zoom display when showZoom is false', () => {
75
+ render(<ZoomControlsUI {...defaultProps} showZoom={false} />)
76
+
77
+ expect(screen.queryByText('10')).toBeNull()
78
+ })
79
+
80
+ test('shows loading indicator when isLoading is true', () => {
81
+ render(<ZoomControlsUI {...defaultProps} isLoading={true} />)
82
+
83
+ expect(screen.getByRole('progressbar')).toBeTruthy()
84
+ })
85
+
86
+ test('renders in horizontal direction when specified', () => {
87
+ render(<ZoomControlsUI {...defaultProps} direction='horizontal' />)
88
+
89
+ // We can't check styles directly without jest-dom, so we'll skip this assertion
90
+ expect(screen.getByText('10')).toBeTruthy()
91
+ })
92
+
93
+ test('disables buttons when disabled prop is true', () => {
94
+ render(<ZoomControlsUI {...defaultProps} disabled={true} />)
95
+
96
+ const increaseButton = screen.getByLabelText('Increase zoom')
97
+ const decreaseButton = screen.getByLabelText('Decrease zoom')
98
+ expect(increaseButton.hasAttribute('disabled')).toBeTruthy()
99
+ expect(decreaseButton.hasAttribute('disabled')).toBeTruthy()
100
+ })
101
+ })
@@ -0,0 +1,114 @@
1
+ import {
2
+ IconButton,
3
+ Divider,
4
+ Box,
5
+ Typography,
6
+ CircularProgress,
7
+ Paper,
8
+ } from '@mui/material'
9
+ import {
10
+ AddOutlined,
11
+ RemoveOutlined,
12
+ CropFreeOutlined,
13
+ } from '@mui/icons-material'
14
+ import { styles } from './styles'
15
+ import type { ZoomControlProps } from './types'
16
+ import { useCallback, type JSX } from 'react'
17
+
18
+ export function ZoomControlsUI({
19
+ zoom,
20
+ disabled,
21
+ direction = 'vertical',
22
+ reverse = false,
23
+ isLoading,
24
+ maxZoom = 24,
25
+ minZoom = 0,
26
+ PaperProps,
27
+ ResetViewProps = {
28
+ Icon: CropFreeOutlined,
29
+ },
30
+ showZoom = true,
31
+ onChange,
32
+ onReset,
33
+ }: ZoomControlProps): JSX.Element {
34
+ const increaseZoom = useCallback(() => {
35
+ const newZoom = Math.min(maxZoom, zoom + 1)
36
+ onChange(newZoom)
37
+ }, [maxZoom, onChange, zoom])
38
+
39
+ const decreaseZoom = useCallback(() => {
40
+ const newZoom = Math.max(minZoom, zoom - 1)
41
+ onChange(newZoom)
42
+ }, [minZoom, onChange, zoom])
43
+
44
+ const displayZoom = Math.floor(zoom)
45
+
46
+ const dividerOrientation =
47
+ direction === 'vertical' ? 'horizontal' : 'vertical'
48
+
49
+ let flexDirection = direction === 'vertical' ? 'column' : 'row'
50
+
51
+ if (reverse) {
52
+ flexDirection += '-reverse'
53
+ }
54
+
55
+ return (
56
+ <Paper
57
+ sx={{
58
+ ...styles.paper,
59
+ flexDirection,
60
+ }}
61
+ {...PaperProps}
62
+ >
63
+ {onReset && (
64
+ <>
65
+ <IconButton
66
+ onClick={onReset}
67
+ aria-label='Reset action'
68
+ disabled={disabled}
69
+ >
70
+ <ResetViewProps.Icon />
71
+ </IconButton>
72
+ <Divider orientation={dividerOrientation} flexItem />
73
+ </>
74
+ )}
75
+ <IconButton
76
+ onClick={increaseZoom}
77
+ aria-label='Increase zoom'
78
+ disabled={disabled}
79
+ >
80
+ <AddOutlined />
81
+ </IconButton>
82
+ <Divider orientation={dividerOrientation} flexItem />
83
+ {showZoom && (
84
+ <>
85
+ <Box sx={styles.zoom}>
86
+ <Typography
87
+ display='block'
88
+ align='center'
89
+ color='textSecondary'
90
+ variant='overline'
91
+ >
92
+ {displayZoom}
93
+ </Typography>
94
+ {isLoading && (
95
+ <CircularProgress
96
+ sx={styles.circularProgress}
97
+ variant='indeterminate'
98
+ size={24}
99
+ />
100
+ )}
101
+ </Box>
102
+ <Divider orientation={dividerOrientation} flexItem />
103
+ </>
104
+ )}
105
+ <IconButton
106
+ onClick={decreaseZoom}
107
+ aria-label='Decrease zoom'
108
+ disabled={disabled}
109
+ >
110
+ <RemoveOutlined />
111
+ </IconButton>
112
+ </Paper>
113
+ )
114
+ }
@@ -0,0 +1,2 @@
1
+ export { useDebounce } from './use-debounce'
2
+ export { useWidgetRef } from './use-widget-ref'
@@ -0,0 +1,55 @@
1
+ import { useEffect, useRef, useCallback } from 'react'
2
+
3
+ /**
4
+ * Custom hook for debouncing a callback function.
5
+ *
6
+ * @param callback - The function to debounce
7
+ * @param delay - The delay in milliseconds (default: 300ms)
8
+ * @returns A debounced version of the callback
9
+ *
10
+ * @example
11
+ * ```tsx
12
+ * const debouncedSearch = useDebounce((searchText: string) => {
13
+ * // Perform search
14
+ * console.log('Searching for:', searchText)
15
+ * }, 300)
16
+ *
17
+ * // Call it anywhere
18
+ * debouncedSearch('react')
19
+ * ```
20
+ */
21
+ export function useDebounce<TArgs extends unknown[], TReturn = void>(
22
+ callback: (...args: TArgs) => TReturn,
23
+ delay = 300,
24
+ ): (...args: TArgs) => void {
25
+ const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
26
+ const callbackRef = useRef(callback)
27
+
28
+ // Update callback ref when callback changes
29
+ useEffect(() => {
30
+ callbackRef.current = callback
31
+ }, [callback])
32
+
33
+ // Cleanup timeout on unmount
34
+ useEffect(() => {
35
+ return () => {
36
+ if (timeoutRef.current) {
37
+ clearTimeout(timeoutRef.current)
38
+ }
39
+ }
40
+ }, [])
41
+
42
+ const debouncedCallback = useCallback(
43
+ (...args: TArgs) => {
44
+ if (timeoutRef.current) {
45
+ clearTimeout(timeoutRef.current)
46
+ }
47
+ timeoutRef.current = setTimeout(() => {
48
+ callbackRef.current(...args)
49
+ }, delay)
50
+ },
51
+ [delay],
52
+ )
53
+
54
+ return debouncedCallback
55
+ }
@@ -0,0 +1,32 @@
1
+ import { describe, expect, test } from 'vitest'
2
+ import { renderHook } from '@testing-library/react'
3
+ import { useSkeleton } from './use-skeleton'
4
+
5
+ describe('useSkeleton', () => {
6
+ test('should return true if loading', () => {
7
+ const { result } = renderHook(() => useSkeleton(true))
8
+
9
+ expect(result.current).toBe(true)
10
+ })
11
+
12
+ test('should return false if not loading', () => {
13
+ const { result } = renderHook(() => useSkeleton(false))
14
+
15
+ expect(result.current).toBe(false)
16
+ })
17
+
18
+ test('should return false if skeleton was already rendered', () => {
19
+ const { result, rerender } = renderHook(
20
+ ({ showSkeleton }) => useSkeleton(showSkeleton),
21
+ { initialProps: { showSkeleton: true } },
22
+ )
23
+
24
+ expect(result.current).toBe(true)
25
+
26
+ rerender({ showSkeleton: false })
27
+ expect(result.current).toBe(false)
28
+
29
+ rerender({ showSkeleton: true })
30
+ expect(result.current).toBe(false)
31
+ })
32
+ })
@@ -0,0 +1,19 @@
1
+ import { useState } from 'react'
2
+
3
+ export function useSkeleton(isLoading: boolean) {
4
+ // Track render phase: 0=never shown, 1=showing now, 2=already shown
5
+ const [phase, setPhase] = useState(() => (isLoading ? 1 : 0))
6
+
7
+ // Transition from phase 0 to 1 when loading starts
8
+ if (isLoading && phase === 0) {
9
+ setPhase(1)
10
+ }
11
+
12
+ // Transition from phase 1 to 2 when loading ends (skeleton was shown, now done)
13
+ if (!isLoading && phase === 1) {
14
+ setPhase(2)
15
+ }
16
+
17
+ // Show skeleton only in phase 1 (first time loading)
18
+ return phase === 1
19
+ }
@@ -0,0 +1,33 @@
1
+ import { useEffect, useRef } from 'react'
2
+ import { useWidgetStore } from '../widgets/stores/widget-store'
3
+
4
+ /**
5
+ * Custom hook for registering a DOM element ref with the widget store.
6
+ * This allows other parts of the application to access the widget's DOM element.
7
+ *
8
+ * @param widgetId - The widget ID to register the ref under
9
+ * @returns A ref object to attach to the DOM element
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * function MyWidget({ id }: { id: string }) {
14
+ * const ref = useWidgetRef<HTMLDivElement>(id)
15
+ *
16
+ * return <div ref={ref}>Widget content</div>
17
+ * }
18
+ * ```
19
+ */
20
+ export function useWidgetRef<T extends HTMLElement = HTMLElement>(
21
+ widgetId: string,
22
+ ) {
23
+ const ref = useRef<T | null>(null)
24
+ const setWidget = useWidgetStore((store) => store.setWidget)
25
+
26
+ useEffect(() => {
27
+ if (ref.current) {
28
+ setWidget(widgetId, { refUI: ref })
29
+ }
30
+ }, [widgetId, setWidget])
31
+
32
+ return ref
33
+ }
@@ -0,0 +1,277 @@
1
+ # Widgets Architecture
2
+
3
+ > **⚠️ Project-Side Implementation Required**
4
+ >
5
+ > Starting with v4.3.x, widgets should be implemented on the **project side** using the building blocks provided by this library. The library exports UI components, configuration helpers, and state management hooks—you compose them in your project to create complete widgets.
6
+ >
7
+ > **Benefits:**
8
+ >
9
+ > - Full customization tailored to your project's specific needs
10
+ > - Direct integration with your data sources and state management
11
+ > - Independent updates without waiting for library releases
12
+ > - Include only the widget features you actually need
13
+ >
14
+ > **See the internal Storybook for complete implementation examples** of all widget types (Bar, Pie, Histogram, Timeseries, Scatterplot, Formula, etc.).
15
+
16
+ ---
17
+
18
+ This directory contains the CARTO PS widget system with an optimized architecture for code reuse and maintainability.
19
+
20
+ ## Directory Structure
21
+
22
+ ```
23
+ widgets/
24
+ ├── _shared/ # Shared utilities (internal, not exported)
25
+ │ ├── chart-config/ # Chart widget configuration utilities
26
+ │ │ ├── config-factory.ts # Factory for creating chart widget configs
27
+ │ │ ├── csv-modifiers.ts # CSV export utilities
28
+ │ │ ├── option-builders.ts # EChart option builders
29
+ │ │ └── index.ts # Exports
30
+ │ └── skeleton/ # Shared skeleton styles
31
+ │ ├── styles.ts # Base skeleton container styles
32
+ │ └── index.ts # Exports
33
+ ├── bar/ # Bar chart widget
34
+ ├── histogram/ # Histogram widget
35
+ ├── pie/ # Pie chart widget
36
+ ├── scatterplot/ # Scatterplot widget
37
+ ├── timeseries/ # Timeseries/line chart widget
38
+ ├── formula/ # Formula/KPI widget
39
+ ├── note/ # Note widget
40
+ ├── markdown/ # Markdown renderer
41
+ ├── echart/ # Base EChart component
42
+ ├── wrapper/ # Widget wrapper with header/actions
43
+ ├── actions/ # Download, fullscreen actions
44
+ ├── config-loader/ # Config loading system
45
+ ├── skeleton-loader/ # Skeleton components
46
+ ├── stores/ # Zustand widget state
47
+ ├── loader/ # WidgetLoader container
48
+ └── widget/ # Main widget orchestrator
49
+ ```
50
+
51
+ ## Shared Utilities (`_shared/`)
52
+
53
+ The `_shared` directory contains internal utilities used by multiple widgets. These are **not exported** from the package and are only for internal use.
54
+
55
+ ### Chart Config Factory
56
+
57
+ **File:** `_shared/chart-config/config-factory.ts`
58
+
59
+ Creates standardized chart widget configurations, eliminating code duplication.
60
+
61
+ **Example:**
62
+
63
+ ```typescript
64
+ import {
65
+ createChartWidgetConfig,
66
+ flattenObjectArrayToCSV,
67
+ } from '../_shared/chart-config'
68
+
69
+ export const myWidgetConfig = createChartWidgetConfig({
70
+ type: 'my-widget',
71
+ getOptions: ({ data, theme }) => ({
72
+ // EChart configuration
73
+ legend: buildLegendConfig(hasLegend),
74
+ // ...
75
+ }),
76
+ csvModifier: (data) => flattenObjectArrayToCSV(data),
77
+ })
78
+ ```
79
+
80
+ ### CSV Modifiers
81
+
82
+ **File:** `_shared/chart-config/csv-modifiers.ts`
83
+
84
+ - `flattenObjectArrayToCSV()` - For widgets with object-based data (bar, pie, histogram, timeseries)
85
+ - `scatterplotDataToCSV()` - For scatterplot with array-based data
86
+
87
+ ### EChart Option Builders
88
+
89
+ **File:** `_shared/chart-config/option-builders.ts`
90
+
91
+ - `buildLegendConfig(hasLegend)` - Standard legend configuration
92
+ - `buildGridConfig(hasLegend, theme, additionalConfig?)` - Grid with legend-aware spacing
93
+ - `createTooltipPositioner(theme)` - Tooltip positioning with overflow handling
94
+
95
+ ### Skeleton Styles
96
+
97
+ **File:** `_shared/skeleton/styles.ts`
98
+
99
+ - `baseSkeletonStyles.graph.container` - Base container styles for all chart skeletons
100
+
101
+ ## Creating a New Chart Widget
102
+
103
+ 1. **Create widget directory:**
104
+
105
+ ```bash
106
+ mkdir src/widgets/my-widget
107
+ ```
108
+
109
+ 2. **Create `types.ts`:**
110
+
111
+ ```typescript
112
+ import type { BaseWidgetProps } from '../widget/types'
113
+ import type { BaseConfig } from '../config-loader'
114
+ import type {
115
+ EchartWidgetConfig,
116
+ EchartWidgetData,
117
+ EchartWidgetState,
118
+ } from '../echart'
119
+
120
+ export interface MyWidgetProps extends BaseWidgetProps<MyWidgetConfig> {
121
+ type: 'my-widget'
122
+ data: MyWidgetData | undefined
123
+ }
124
+
125
+ export type MyWidgetData = EchartWidgetData
126
+ export type MyWidgetState = EchartWidgetState
127
+ export type MyWidgetConfig = MyConfig &
128
+ EchartWidgetConfig & { type: MyWidgetProps['type'] }
129
+
130
+ export interface MyConfig extends Omit<BaseConfig, 'data'> {
131
+ data?: MyWidgetData
132
+ }
133
+ ```
134
+
135
+ 3. **Create `config.ts` using shared factory:**
136
+
137
+ ```typescript
138
+ import type { EchartOptionsProps } from '../echart'
139
+ import type { MyConfig, MyWidgetConfig, MyWidgetData } from './types'
140
+ import {
141
+ createChartWidgetConfig,
142
+ flattenObjectArrayToCSV,
143
+ buildLegendConfig,
144
+ buildGridConfig,
145
+ } from '../_shared/chart-config'
146
+
147
+ export const myWidgetConfig = createChartWidgetConfig<
148
+ MyWidgetData,
149
+ MyConfig
150
+ >({
151
+ type: 'my-widget',
152
+ getOptions,
153
+ csvModifier: (data) => flattenObjectArrayToCSV(data),
154
+ })
155
+
156
+ function getOptions({
157
+ data = [],
158
+ theme,
159
+ }: Omit<MyConfig, 'refUI'>): EchartOptionsProps {
160
+ const hasLegend = (data?.length ?? 0) > 1
161
+ return {
162
+ legend: buildLegendConfig(hasLegend),
163
+ grid: buildGridConfig(hasLegend, theme),
164
+ // ... widget-specific configuration
165
+ }
166
+ }
167
+ ```
168
+
169
+ 4. **Create `skeleton.tsx` and `style.ts`:**
170
+
171
+ ```typescript
172
+ // style.ts
173
+ import type { SxProps, Theme } from '@mui/material'
174
+ import { baseSkeletonStyles } from '../_shared/skeleton'
175
+
176
+ export const styles = {
177
+ skeleton: {
178
+ graph: baseSkeletonStyles.graph,
179
+ // Add widget-specific styles here
180
+ },
181
+ } satisfies Record<string, SxProps<Theme>>
182
+ ```
183
+
184
+ 5. **Create `index.ts`:**
185
+
186
+ ```typescript
187
+ export type {
188
+ MyWidgetProps,
189
+ MyWidgetData,
190
+ MyWidgetState,
191
+ MyWidgetConfig,
192
+ MyConfig,
193
+ } from './types'
194
+ export { myWidgetConfig } from './config'
195
+ export { MyWidgetSkeleton as Skeleton } from './skeleton'
196
+ ```
197
+
198
+ 6. **Run build automation:**
199
+
200
+ ```bash
201
+ pnpm generate:exports
202
+ ```
203
+
204
+ This will automatically:
205
+ - Add the widget to vite.config.ts entry points
206
+ - Add the widget to package.json exports
207
+ - Validate all exports
208
+
209
+ ## Build Automation
210
+
211
+ ### Scripts
212
+
213
+ - `pnpm exports:validate` - Validate all package.json exports have corresponding source files
214
+
215
+ ### How It Works
216
+
217
+ **package.json is the source of truth!**
218
+
219
+ The `scripts/generate-widget-config.ts` script:
220
+
221
+ 1. Reads widget exports from `package.json`
222
+ 2. Converts export paths to source file paths
223
+ 3. Generates vite entry points dynamically
224
+ 4. Validates that all exports have corresponding source files
225
+
226
+ The script uses **Node 22's native TypeScript support** with the `--experimental-strip-types` flag, so no compilation step is needed!
227
+
228
+ ### Adding a New Widget
229
+
230
+ 1. **Create widget folder and files:**
231
+
232
+ ```bash
233
+ mkdir src/widgets/my-widget
234
+ # Create index.ts, config.ts, types.ts, etc.
235
+ ```
236
+
237
+ 2. **Add export to package.json:**
238
+
239
+ ```json
240
+ {
241
+ "exports": {
242
+ "./widgets/my-widget": {
243
+ "import": "./dist/widgets/my-widget.js",
244
+ "types": "./dist/types/widgets/my-widget/index.d.ts"
245
+ }
246
+ }
247
+ }
248
+ ```
249
+
250
+ 3. **Build:**
251
+ ```bash
252
+ pnpm build
253
+ ```
254
+
255
+ The vite config automatically reads from package.json and builds all declared exports!
256
+
257
+ ## Code Reduction Achieved
258
+
259
+ | Widget | Before | After | Reduction |
260
+ | --------------------- | ---------- | --------- | --------- |
261
+ | bar/config.ts | ~150 lines | ~65 lines | ~57% |
262
+ | histogram/config.ts | ~155 lines | ~68 lines | ~56% |
263
+ | pie/config.ts | ~160 lines | ~50 lines | ~69% |
264
+ | scatterplot/config.ts | ~145 lines | ~90 lines | ~38% |
265
+ | timeseries/config.ts | ~130 lines | ~70 lines | ~46% |
266
+
267
+ **Total:** ~400-500 lines of duplicated code eliminated
268
+
269
+ ## Benefits
270
+
271
+ ✅ **Reduced Duplication** - Shared utilities eliminate repeated code
272
+ ✅ **Better Maintainability** - Changes to common patterns update all widgets
273
+ ✅ **Automated Builds** - No manual vite/package.json updates needed
274
+ ✅ **Consistent Patterns** - All widgets follow the same structure
275
+ ✅ **Type Safety** - Full TypeScript support throughout
276
+ ✅ **Easy to Extend** - Adding new widgets is straightforward
277
+ ✅ **Backwards Compatible** - All external imports remain unchanged
@@ -0,0 +1,67 @@
1
+ import type { EchartOptionsProps, EchartWidgetData } from '../../echart'
2
+
3
+ /**
4
+ * Base configuration interface for chart widgets
5
+ */
6
+ export interface ChartWidgetBaseConfig<TData = EchartWidgetData> {
7
+ data?: TData
8
+ }
9
+
10
+ /**
11
+ * Parameters for creating a chart widget configuration
12
+ */
13
+ export interface CreateChartWidgetConfigParams<
14
+ TData = EchartWidgetData,
15
+ TConfig extends ChartWidgetBaseConfig<TData> = ChartWidgetBaseConfig<TData>,
16
+ TType extends string = string,
17
+ > {
18
+ /** Widget type identifier (e.g., 'bar', 'pie', 'histogram') */
19
+ type: TType
20
+ /** Function to get EChart options from config */
21
+ getOptions: (config: TConfig) => EchartOptionsProps
22
+ }
23
+
24
+ /**
25
+ * Return type of the chart widget config function
26
+ */
27
+ export type ChartWidgetConfigResult<
28
+ TData = EchartWidgetData,
29
+ TConfig extends ChartWidgetBaseConfig<TData> = ChartWidgetBaseConfig<TData>,
30
+ TType extends string = string,
31
+ > = TConfig & {
32
+ type: TType
33
+ }
34
+
35
+ /**
36
+ * Factory function to create a standardized chart widget config function.
37
+ * This eliminates duplication across chart widgets by providing a common structure.
38
+ *
39
+ * @example
40
+ * ```ts
41
+ * export const barConfig = createChartWidgetConfig({
42
+ * type: 'bar' as const,
43
+ * getOptions: ({ data, theme }) => ({
44
+ * // EChart configuration
45
+ * }),
46
+ * csvModifier: (data) => flattenObjectArrayToCSV(data),
47
+ * })
48
+ * ```
49
+ */
50
+ export function createChartWidgetConfig<
51
+ TData = EchartWidgetData,
52
+ TConfig extends ChartWidgetBaseConfig<TData> = ChartWidgetBaseConfig<TData>,
53
+ TType extends string = string,
54
+ >({
55
+ type,
56
+ getOptions,
57
+ }: CreateChartWidgetConfigParams<TData, TConfig, TType>): (
58
+ config: TConfig,
59
+ ) => ChartWidgetConfigResult<TData, TConfig, TType> {
60
+ return function (config: TConfig) {
61
+ return {
62
+ ...config,
63
+ option: getOptions(config),
64
+ type: type,
65
+ } as ChartWidgetConfigResult<TData, TConfig, TType>
66
+ }
67
+ }