@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,537 @@
1
+ import { describe, test, expect, vi, beforeEach, afterEach } from 'vitest'
2
+ import { render } from '@testing-library/react'
3
+ import { EchartUI } from './echart-ui'
4
+ import type { EChartsOption } from 'echarts'
5
+ import * as echarts from 'echarts'
6
+
7
+ // Mock echarts module
8
+ vi.mock('echarts', () => {
9
+ const mockChart = {
10
+ setOption: vi.fn(),
11
+ dispose: vi.fn(),
12
+ resize: vi.fn(),
13
+ on: vi.fn(),
14
+ off: vi.fn(),
15
+ getZr: vi.fn(() => ({
16
+ setCursorStyle: vi.fn(),
17
+ })),
18
+ }
19
+
20
+ return {
21
+ init: vi.fn(() => mockChart),
22
+ }
23
+ })
24
+
25
+ describe('EchartUI', () => {
26
+ let mockChart: {
27
+ setOption: ReturnType<typeof vi.fn>
28
+ dispose: ReturnType<typeof vi.fn>
29
+ resize: ReturnType<typeof vi.fn>
30
+ on: ReturnType<typeof vi.fn>
31
+ off: ReturnType<typeof vi.fn>
32
+ getZr: ReturnType<typeof vi.fn>
33
+ }
34
+ let mockResizeObserver: {
35
+ observe: ReturnType<typeof vi.fn>
36
+ disconnect: ReturnType<typeof vi.fn>
37
+ unobserve: ReturnType<typeof vi.fn>
38
+ }
39
+
40
+ const defaultProps = {
41
+ id: 'test-echart',
42
+ }
43
+
44
+ const basicOption: EChartsOption = {
45
+ title: {
46
+ text: 'Test Chart',
47
+ },
48
+ series: [
49
+ {
50
+ type: 'bar',
51
+ data: [1, 2, 3, 4, 5],
52
+ },
53
+ ],
54
+ }
55
+
56
+ beforeEach(() => {
57
+ vi.clearAllMocks()
58
+
59
+ // Create a fresh mock chart instance for each test
60
+ mockChart = {
61
+ setOption: vi.fn(),
62
+ dispose: vi.fn(),
63
+ resize: vi.fn(),
64
+ on: vi.fn(),
65
+ off: vi.fn(),
66
+ getZr: vi.fn(() => ({
67
+ setCursorStyle: vi.fn(),
68
+ })),
69
+ }
70
+
71
+ // Make echarts.init return our mock chart
72
+ vi.mocked(echarts.init).mockReturnValue(
73
+ mockChart as unknown as echarts.ECharts,
74
+ )
75
+
76
+ // Mock ResizeObserver
77
+ mockResizeObserver = {
78
+ observe: vi.fn(),
79
+ disconnect: vi.fn(),
80
+ unobserve: vi.fn(),
81
+ }
82
+
83
+ global.ResizeObserver = class {
84
+ observe = mockResizeObserver.observe
85
+ disconnect = mockResizeObserver.disconnect
86
+ unobserve = mockResizeObserver.unobserve
87
+ } as unknown as typeof ResizeObserver
88
+ })
89
+
90
+ afterEach(() => {
91
+ vi.restoreAllMocks()
92
+ })
93
+
94
+ test('renders div container', () => {
95
+ const { container } = render(
96
+ <EchartUI {...defaultProps} option={basicOption} />,
97
+ )
98
+ const div = container.querySelector('div')
99
+ expect(div).toBeTruthy()
100
+ })
101
+
102
+ test('initializes echarts with default options', () => {
103
+ render(<EchartUI {...defaultProps} option={basicOption} />)
104
+
105
+ expect(echarts.init).toHaveBeenCalledWith(
106
+ expect.any(HTMLDivElement),
107
+ null,
108
+ expect.objectContaining({
109
+ renderer: 'svg',
110
+ height: 304,
111
+ }),
112
+ )
113
+ })
114
+
115
+ test('initializes echarts with custom renderer', () => {
116
+ render(
117
+ <EchartUI
118
+ {...defaultProps}
119
+ option={basicOption}
120
+ init={{ renderer: 'canvas' }}
121
+ />,
122
+ )
123
+
124
+ expect(echarts.init).toHaveBeenCalledWith(
125
+ expect.any(HTMLDivElement),
126
+ null,
127
+ expect.objectContaining({
128
+ renderer: 'canvas',
129
+ height: 304,
130
+ }),
131
+ )
132
+ })
133
+
134
+ test('initializes echarts with custom height', () => {
135
+ render(
136
+ <EchartUI
137
+ {...defaultProps}
138
+ option={basicOption}
139
+ init={{ height: 500 }}
140
+ />,
141
+ )
142
+
143
+ expect(echarts.init).toHaveBeenCalledWith(
144
+ expect.any(HTMLDivElement),
145
+ null,
146
+ expect.objectContaining({
147
+ renderer: 'svg',
148
+ height: 500,
149
+ }),
150
+ )
151
+ })
152
+
153
+ test('initializes echarts with custom init options', () => {
154
+ render(
155
+ <EchartUI
156
+ {...defaultProps}
157
+ option={basicOption}
158
+ init={{ renderer: 'canvas', height: 600, width: 800 }}
159
+ />,
160
+ )
161
+
162
+ expect(echarts.init).toHaveBeenCalledWith(
163
+ expect.any(HTMLDivElement),
164
+ null,
165
+ expect.objectContaining({
166
+ renderer: 'canvas',
167
+ height: 600,
168
+ width: 800,
169
+ }),
170
+ )
171
+ })
172
+
173
+ test('sets chart options on mount', () => {
174
+ render(<EchartUI {...defaultProps} option={basicOption} />)
175
+
176
+ expect(mockChart.setOption).toHaveBeenCalledWith(basicOption, {
177
+ lazyUpdate: true,
178
+ replaceMerge: ['dataset', 'series'],
179
+ })
180
+ })
181
+
182
+ test('updates chart options when option prop changes', () => {
183
+ const { rerender } = render(
184
+ <EchartUI {...defaultProps} option={basicOption} />,
185
+ )
186
+
187
+ const newOption: EChartsOption = {
188
+ title: {
189
+ text: 'Updated Chart',
190
+ },
191
+ series: [
192
+ {
193
+ type: 'line',
194
+ data: [5, 4, 3, 2, 1],
195
+ },
196
+ ],
197
+ }
198
+
199
+ rerender(<EchartUI {...defaultProps} option={newOption} />)
200
+
201
+ expect(mockChart.setOption).toHaveBeenCalledWith(newOption, {
202
+ lazyUpdate: true,
203
+ replaceMerge: ['dataset', 'series'],
204
+ })
205
+ })
206
+
207
+ test('uses provided replaceMerge values', () => {
208
+ render(
209
+ <EchartUI
210
+ {...defaultProps}
211
+ option={basicOption}
212
+ replaceMerge={['series']}
213
+ />,
214
+ )
215
+
216
+ expect(mockChart.setOption).toHaveBeenCalledWith(basicOption, {
217
+ lazyUpdate: true,
218
+ replaceMerge: ['series'],
219
+ })
220
+ })
221
+
222
+ test('applies custom className', () => {
223
+ const { container } = render(
224
+ <EchartUI
225
+ {...defaultProps}
226
+ option={basicOption}
227
+ className='custom-chart'
228
+ />,
229
+ )
230
+ const div = container.querySelector('div')
231
+ expect(div?.className).toBe('custom-chart')
232
+ })
233
+
234
+ test('applies custom style', () => {
235
+ const customStyle = { width: '100%', height: '400px' }
236
+ const { container } = render(
237
+ <EchartUI {...defaultProps} option={basicOption} style={customStyle} />,
238
+ )
239
+ const div = container.querySelector('div')!
240
+ expect(div?.style.width).toBe('100%')
241
+ expect(div?.style.height).toBe('400px')
242
+ })
243
+
244
+ test('disposes chart instance on unmount', () => {
245
+ const { unmount } = render(
246
+ <EchartUI {...defaultProps} option={basicOption} />,
247
+ )
248
+
249
+ unmount()
250
+
251
+ expect(mockChart.dispose).toHaveBeenCalled()
252
+ })
253
+
254
+ test('sets up ResizeObserver on mount', () => {
255
+ render(<EchartUI {...defaultProps} option={basicOption} />)
256
+
257
+ // Verify ResizeObserver was used by checking observe was called
258
+ expect(mockResizeObserver.observe).toHaveBeenCalledWith(
259
+ expect.any(HTMLDivElement),
260
+ )
261
+ })
262
+
263
+ test('disconnects ResizeObserver on unmount', () => {
264
+ const { unmount } = render(
265
+ <EchartUI {...defaultProps} option={basicOption} />,
266
+ )
267
+
268
+ unmount()
269
+
270
+ expect(mockResizeObserver.disconnect).toHaveBeenCalled()
271
+ })
272
+
273
+ test('calls resize on chart when ResizeObserver triggers', () => {
274
+ let resizeCallback: ResizeObserverCallback
275
+
276
+ global.ResizeObserver = class {
277
+ constructor(callback: ResizeObserverCallback) {
278
+ resizeCallback = callback
279
+ }
280
+ observe = mockResizeObserver.observe
281
+ disconnect = mockResizeObserver.disconnect
282
+ unobserve = mockResizeObserver.unobserve
283
+ } as unknown as typeof ResizeObserver
284
+
285
+ render(<EchartUI {...defaultProps} option={basicOption} />)
286
+
287
+ // Trigger resize callback
288
+ resizeCallback!([], mockResizeObserver as unknown as ResizeObserver)
289
+
290
+ expect(mockChart.resize).toHaveBeenCalled()
291
+ })
292
+
293
+ test('accepts ref prop without errors', () => {
294
+ const chartRef = {
295
+ current: null,
296
+ } as unknown as React.RefObject<echarts.ECharts>
297
+
298
+ // Component accepts ref as a prop but since it doesn't use forwardRef,
299
+ // the useImperativeHandle won't properly set the ref.
300
+ // This test verifies the component doesn't crash when ref is provided.
301
+ expect(() => {
302
+ render(<EchartUI {...defaultProps} ref={chartRef} option={basicOption} />)
303
+ }).not.toThrow()
304
+ })
305
+
306
+ test('attaches event listeners when onEvents prop is provided', () => {
307
+ const clickHandler = vi.fn()
308
+ const hoverHandler = vi.fn()
309
+
310
+ render(
311
+ <EchartUI
312
+ {...defaultProps}
313
+ option={basicOption}
314
+ onEvents={{
315
+ click: clickHandler,
316
+ mouseover: hoverHandler,
317
+ }}
318
+ />,
319
+ )
320
+
321
+ expect(mockChart.on).toHaveBeenCalledWith('click', clickHandler)
322
+ expect(mockChart.on).toHaveBeenCalledWith('mouseover', hoverHandler)
323
+ })
324
+
325
+ test('updates event listeners when onEvents prop changes', () => {
326
+ const clickHandler1 = vi.fn()
327
+ const clickHandler2 = vi.fn()
328
+
329
+ const { rerender } = render(
330
+ <EchartUI
331
+ {...defaultProps}
332
+ option={basicOption}
333
+ onEvents={{
334
+ click: clickHandler1,
335
+ }}
336
+ />,
337
+ )
338
+
339
+ expect(mockChart.on).toHaveBeenCalledWith('click', clickHandler1)
340
+
341
+ rerender(
342
+ <EchartUI
343
+ {...defaultProps}
344
+ option={basicOption}
345
+ onEvents={{
346
+ click: clickHandler2,
347
+ }}
348
+ />,
349
+ )
350
+
351
+ expect(mockChart.off).toHaveBeenCalledWith('click')
352
+ expect(mockChart.on).toHaveBeenCalledWith('click', clickHandler2)
353
+ })
354
+
355
+ test('sets default cursor style when no click event is provided', () => {
356
+ const mockGetZr = vi.fn(() => ({
357
+ setCursorStyle: vi.fn(),
358
+ }))
359
+ mockChart.getZr = mockGetZr
360
+
361
+ render(<EchartUI {...defaultProps} option={basicOption} />)
362
+
363
+ expect(mockChart.on).toHaveBeenCalledWith('mousemove', expect.any(Function))
364
+ })
365
+
366
+ test('does not set default cursor style when click event is provided', () => {
367
+ const clickHandler = vi.fn()
368
+
369
+ render(
370
+ <EchartUI
371
+ {...defaultProps}
372
+ option={basicOption}
373
+ onEvents={{
374
+ click: clickHandler,
375
+ }}
376
+ />,
377
+ )
378
+
379
+ // Should not attach mousemove handler when click is provided
380
+ const mousemoveCalls = mockChart.on.mock.calls.filter(
381
+ (call) => call[0] === 'mousemove',
382
+ )
383
+
384
+ expect(mousemoveCalls.length).toBe(0)
385
+ })
386
+
387
+ test('handles multiple event types simultaneously', () => {
388
+ const clickHandler = vi.fn()
389
+ const dblclickHandler = vi.fn()
390
+ const mouseoverHandler = vi.fn()
391
+ const mouseoutHandler = vi.fn()
392
+
393
+ render(
394
+ <EchartUI
395
+ {...defaultProps}
396
+ option={basicOption}
397
+ onEvents={{
398
+ click: clickHandler,
399
+ dblclick: dblclickHandler,
400
+ mouseover: mouseoverHandler,
401
+ mouseout: mouseoutHandler,
402
+ }}
403
+ />,
404
+ )
405
+
406
+ expect(mockChart.on).toHaveBeenCalledWith('click', clickHandler)
407
+ expect(mockChart.on).toHaveBeenCalledWith('dblclick', dblclickHandler)
408
+ expect(mockChart.on).toHaveBeenCalledWith('mouseover', mouseoverHandler)
409
+ expect(mockChart.on).toHaveBeenCalledWith('mouseout', mouseoutHandler)
410
+ })
411
+
412
+ test('reinitializes chart when init options change', () => {
413
+ vi.clearAllMocks() // Clear mocks from beforeEach
414
+
415
+ const { rerender } = render(
416
+ <EchartUI
417
+ {...defaultProps}
418
+ option={basicOption}
419
+ init={{ height: 300 }}
420
+ />,
421
+ )
422
+
423
+ expect(echarts.init).toHaveBeenCalledTimes(1)
424
+ expect(mockChart.dispose).not.toHaveBeenCalled()
425
+
426
+ rerender(
427
+ <EchartUI
428
+ {...defaultProps}
429
+ option={basicOption}
430
+ init={{ height: 400 }}
431
+ />,
432
+ )
433
+
434
+ expect(mockChart.dispose).toHaveBeenCalled()
435
+ expect(echarts.init).toHaveBeenCalledTimes(2)
436
+ })
437
+
438
+ test('handles empty onEvents object', () => {
439
+ render(<EchartUI {...defaultProps} option={basicOption} onEvents={{}} />)
440
+
441
+ // Should still set default mousemove handler
442
+ expect(mockChart.on).toHaveBeenCalledWith('mousemove', expect.any(Function))
443
+ })
444
+
445
+ test('generates unique id for container', () => {
446
+ const { container: container1 } = render(
447
+ <EchartUI {...defaultProps} id='test-echart-1' option={basicOption} />,
448
+ )
449
+ const { container: container2 } = render(
450
+ <EchartUI {...defaultProps} id='test-echart-2' option={basicOption} />,
451
+ )
452
+
453
+ const div1 = container1.querySelector('div')
454
+ const div2 = container2.querySelector('div')
455
+
456
+ expect(div1?.id).toBe('test-echart-1')
457
+ expect(div2?.id).toBe('test-echart-2')
458
+ expect(div1?.id).not.toBe(div2?.id)
459
+ })
460
+
461
+ test('handles complex chart options', () => {
462
+ const complexOption: EChartsOption = {
463
+ title: {
464
+ text: 'Complex Chart',
465
+ left: 'center',
466
+ },
467
+ tooltip: {
468
+ trigger: 'axis',
469
+ },
470
+ legend: {
471
+ data: ['Series 1', 'Series 2'],
472
+ bottom: 10,
473
+ },
474
+ xAxis: {
475
+ type: 'category',
476
+ data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
477
+ },
478
+ yAxis: {
479
+ type: 'value',
480
+ },
481
+ series: [
482
+ {
483
+ name: 'Series 1',
484
+ type: 'line',
485
+ data: [120, 200, 150, 80, 70, 110, 130],
486
+ },
487
+ {
488
+ name: 'Series 2',
489
+ type: 'bar',
490
+ data: [220, 180, 190, 234, 290, 330, 310],
491
+ },
492
+ ],
493
+ }
494
+
495
+ render(<EchartUI {...defaultProps} option={complexOption} />)
496
+
497
+ expect(mockChart.setOption).toHaveBeenCalledWith(complexOption, {
498
+ lazyUpdate: true,
499
+ replaceMerge: ['dataset', 'series'],
500
+ })
501
+ })
502
+
503
+ test('does not break when ref is not provided', () => {
504
+ expect(() => {
505
+ render(<EchartUI {...defaultProps} option={basicOption} />)
506
+ }).not.toThrow()
507
+ })
508
+
509
+ test('handles undefined onEvents gracefully', () => {
510
+ render(
511
+ <EchartUI {...defaultProps} option={basicOption} onEvents={undefined} />,
512
+ )
513
+
514
+ expect(mockChart.on).toHaveBeenCalledWith('mousemove', expect.any(Function))
515
+ })
516
+
517
+ test('cleans up properly with multiple rerenders', () => {
518
+ const { rerender, unmount } = render(
519
+ <EchartUI {...defaultProps} option={basicOption} />,
520
+ )
521
+
522
+ const option2: EChartsOption = {
523
+ series: [{ type: 'line', data: [1, 2, 3] }],
524
+ }
525
+ rerender(<EchartUI {...defaultProps} option={option2} />)
526
+
527
+ const option3: EChartsOption = {
528
+ series: [{ type: 'bar', data: [3, 2, 1] }],
529
+ }
530
+ rerender(<EchartUI {...defaultProps} option={option3} />)
531
+
532
+ unmount()
533
+
534
+ expect(mockChart.dispose).toHaveBeenCalled()
535
+ expect(mockResizeObserver.disconnect).toHaveBeenCalled()
536
+ })
537
+ })
@@ -0,0 +1,92 @@
1
+ import { useEffect, useRef, useImperativeHandle } from 'react'
2
+ import * as echarts from 'echarts'
3
+ import type { EchartUIProps } from './types'
4
+ import { useWidgetRef } from '../../hooks'
5
+
6
+ const DEFAULT_REPLACE_MERGE: string[] = ['dataset', 'series']
7
+
8
+ export function EchartUI(props: EchartUIProps) {
9
+ const { id, ref, init, option, className, style, onEvents } = props
10
+ const replaceMerge = toReplaceMerge(
11
+ (props as { replaceMerge?: unknown }).replaceMerge,
12
+ )
13
+
14
+ const chartRef = useWidgetRef<HTMLDivElement>(id)
15
+ const chartInstance = useRef<echarts.ECharts>(null)
16
+ const resizeObserverRef = useRef<ResizeObserver | null>(null)
17
+
18
+ useImperativeHandle(ref, () => chartInstance.current!, [])
19
+
20
+ useEffect(() => {
21
+ if (!chartRef.current) return
22
+
23
+ chartInstance.current = echarts.init(chartRef.current, null, {
24
+ renderer: 'svg',
25
+ height: 304,
26
+ ...init,
27
+ })
28
+ // Cleanup function
29
+ return () => {
30
+ chartInstance.current?.dispose()
31
+ chartInstance.current = null
32
+ }
33
+ }, [chartRef, init])
34
+
35
+ // Update chart when options change
36
+ useEffect(() => {
37
+ const setOptionConfig = {
38
+ lazyUpdate: true,
39
+ replaceMerge: replaceMerge ?? DEFAULT_REPLACE_MERGE,
40
+ }
41
+
42
+ chartInstance.current?.setOption(
43
+ option,
44
+ setOptionConfig as Parameters<echarts.ECharts['setOption']>[1],
45
+ )
46
+ }, [option, replaceMerge])
47
+
48
+ // Handle resize using ResizeObserver
49
+ useEffect(() => {
50
+ const handleResize = () => {
51
+ chartInstance.current?.resize()
52
+ }
53
+
54
+ resizeObserverRef.current = new ResizeObserver(handleResize)
55
+ resizeObserverRef.current.observe(chartRef.current!)
56
+
57
+ return () => {
58
+ resizeObserverRef.current?.disconnect()
59
+ resizeObserverRef.current = null
60
+ }
61
+ }, [chartRef])
62
+
63
+ useEffect(() => {
64
+ const _onEvents = { ...(onEvents ?? {}) }
65
+ if (!_onEvents.click) {
66
+ _onEvents.mousemove = () => {
67
+ chartInstance.current?.getZr().setCursorStyle('default')
68
+ }
69
+ }
70
+
71
+ // Attach event listeners
72
+ Object.entries(_onEvents).forEach(([event, handler]) => {
73
+ chartInstance.current?.on(event, handler)
74
+ })
75
+ // Cleanup function to remove event listeners
76
+ return () => {
77
+ Object.entries(_onEvents).forEach(([event]) => {
78
+ chartInstance.current?.off(event)
79
+ })
80
+ }
81
+ }, [onEvents])
82
+
83
+ return <div id={id} ref={chartRef} style={style} className={className} />
84
+ }
85
+
86
+ function toReplaceMerge(value: unknown): string[] | undefined {
87
+ if (!Array.isArray(value)) {
88
+ return undefined
89
+ }
90
+
91
+ return value.filter((item): item is string => typeof item === 'string')
92
+ }