@carto/ps-react-ui 4.7.1 → 4.8.0

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 (574) hide show
  1. package/dist/category-DwaeYjpX.js +656 -0
  2. package/dist/category-DwaeYjpX.js.map +1 -0
  3. package/dist/change-column-Cidl_M-4.js +1110 -0
  4. package/dist/change-column-Cidl_M-4.js.map +1 -0
  5. package/dist/data-zoom-layout-BH0LPwSy.js +28 -0
  6. package/dist/data-zoom-layout-BH0LPwSy.js.map +1 -0
  7. package/dist/echart-CU0KmClP.js +176 -0
  8. package/dist/echart-CU0KmClP.js.map +1 -0
  9. package/dist/exports-Cx-f6m6U.js +63 -0
  10. package/dist/exports-Cx-f6m6U.js.map +1 -0
  11. package/dist/formula-DuC0NQLH.js +79 -0
  12. package/dist/formula-DuC0NQLH.js.map +1 -0
  13. package/dist/markdown-BD1jcknS.js +8326 -0
  14. package/dist/markdown-BD1jcknS.js.map +1 -0
  15. package/dist/merge-options-DCkkHZIf.js +34 -0
  16. package/dist/merge-options-DCkkHZIf.js.map +1 -0
  17. package/dist/{styles-BYTyKQFP.js → option-builders-F-c9ELi1.js} +25 -45
  18. package/dist/option-builders-F-c9ELi1.js.map +1 -0
  19. package/dist/png-item-CS4z1iSH.js +45 -0
  20. package/dist/png-item-CS4z1iSH.js.map +1 -0
  21. package/dist/range-DsqTjSpg.js +186 -0
  22. package/dist/range-DsqTjSpg.js.map +1 -0
  23. package/dist/spread-CTuIXZSM.js +67 -0
  24. package/dist/spread-CTuIXZSM.js.map +1 -0
  25. package/dist/style-DVnT6HC1.js +131 -0
  26. package/dist/style-DVnT6HC1.js.map +1 -0
  27. package/dist/styles-cohnxh9F.js +23 -0
  28. package/dist/styles-cohnxh9F.js.map +1 -0
  29. package/dist/table-HIpXuq4G.js +390 -0
  30. package/dist/table-HIpXuq4G.js.map +1 -0
  31. package/dist/transforms-Cdx4fkU5.js +106 -0
  32. package/dist/transforms-Cdx4fkU5.js.map +1 -0
  33. package/dist/types/widgets/echart/utils.test.d.ts +1 -0
  34. package/dist/types/widgets/formula/config.test.d.ts +1 -0
  35. package/dist/types/widgets/stores/widget-store-branches.test.d.ts +1 -0
  36. package/dist/types/widgets/table/config.test.d.ts +1 -0
  37. package/dist/types/widgets-v2/actions/brush-toggle/brush-toggle.d.ts +56 -0
  38. package/dist/types/widgets-v2/actions/brush-toggle/index.d.ts +3 -0
  39. package/dist/types/widgets-v2/actions/brush-toggle/labels.d.ts +5 -0
  40. package/dist/types/widgets-v2/actions/brush-toggle/style.d.ts +12 -0
  41. package/dist/types/widgets-v2/actions/brush-toggle/transforms.d.ts +11 -0
  42. package/dist/types/widgets-v2/actions/brush-toggle/transforms.test.d.ts +1 -0
  43. package/dist/types/widgets-v2/actions/change-column/change-column-icon.d.ts +2 -0
  44. package/dist/types/widgets-v2/actions/change-column/change-column.d.ts +29 -0
  45. package/dist/types/widgets-v2/actions/change-column/index.d.ts +3 -0
  46. package/dist/types/widgets-v2/actions/change-column/labels.d.ts +5 -0
  47. package/dist/types/widgets-v2/actions/change-column/sortable-column-item.d.ts +14 -0
  48. package/dist/types/widgets-v2/actions/change-column/style.d.ts +33 -0
  49. package/dist/types/widgets-v2/actions/change-column/types.d.ts +10 -0
  50. package/dist/types/widgets-v2/actions/download/download.d.ts +18 -0
  51. package/dist/types/widgets-v2/actions/download/exports.d.ts +37 -0
  52. package/dist/types/widgets-v2/actions/download/icons.d.ts +12 -0
  53. package/dist/types/widgets-v2/actions/download/index.d.ts +6 -0
  54. package/dist/types/widgets-v2/actions/download/labels.d.ts +11 -0
  55. package/dist/types/widgets-v2/actions/download/png-item.d.ts +24 -0
  56. package/dist/types/widgets-v2/actions/download/style.d.ts +1 -0
  57. package/dist/types/widgets-v2/actions/download/types.d.ts +35 -0
  58. package/dist/types/widgets-v2/actions/fullscreen/fullscreen.d.ts +59 -0
  59. package/dist/types/widgets-v2/actions/fullscreen/index.d.ts +3 -0
  60. package/dist/types/widgets-v2/actions/fullscreen/labels.d.ts +5 -0
  61. package/dist/types/widgets-v2/actions/fullscreen/style.d.ts +48 -0
  62. package/dist/types/widgets-v2/actions/fullscreen/types.d.ts +14 -0
  63. package/dist/types/widgets-v2/actions/index.d.ts +9 -0
  64. package/dist/types/widgets-v2/actions/lock-selection/index.d.ts +3 -0
  65. package/dist/types/widgets-v2/actions/lock-selection/labels.d.ts +6 -0
  66. package/dist/types/widgets-v2/actions/lock-selection/lock-selection.d.ts +36 -0
  67. package/dist/types/widgets-v2/actions/lock-selection/style.d.ts +12 -0
  68. package/dist/types/widgets-v2/actions/lock-selection/transforms.d.ts +6 -0
  69. package/dist/types/widgets-v2/actions/relative-data/index.d.ts +3 -0
  70. package/dist/types/widgets-v2/actions/relative-data/labels.d.ts +5 -0
  71. package/dist/types/widgets-v2/actions/relative-data/relative-data.d.ts +39 -0
  72. package/dist/types/widgets-v2/actions/relative-data/style.d.ts +12 -0
  73. package/dist/types/widgets-v2/actions/relative-data/transforms.d.ts +30 -0
  74. package/dist/types/widgets-v2/actions/relative-data/transforms.test.d.ts +1 -0
  75. package/dist/types/widgets-v2/actions/searcher/filter.d.ts +6 -0
  76. package/dist/types/widgets-v2/actions/searcher/index.d.ts +4 -0
  77. package/dist/types/widgets-v2/actions/searcher/labels.d.ts +7 -0
  78. package/dist/types/widgets-v2/actions/searcher/searcher-toggle.d.ts +23 -0
  79. package/dist/types/widgets-v2/actions/searcher/searcher.d.ts +11 -0
  80. package/dist/types/widgets-v2/actions/searcher/style.d.ts +16 -0
  81. package/dist/types/widgets-v2/actions/stack-toggle/index.d.ts +3 -0
  82. package/dist/types/widgets-v2/actions/stack-toggle/labels.d.ts +5 -0
  83. package/dist/types/widgets-v2/actions/stack-toggle/stack-toggle.d.ts +10 -0
  84. package/dist/types/widgets-v2/actions/stack-toggle/style.d.ts +12 -0
  85. package/dist/types/widgets-v2/actions/stack-toggle/transforms.d.ts +13 -0
  86. package/dist/types/widgets-v2/actions/stack-toggle/transforms.test.d.ts +1 -0
  87. package/dist/types/widgets-v2/actions/zoom-toggle/index.d.ts +3 -0
  88. package/dist/types/widgets-v2/actions/zoom-toggle/labels.d.ts +5 -0
  89. package/dist/types/widgets-v2/actions/zoom-toggle/style.d.ts +12 -0
  90. package/dist/types/widgets-v2/actions/zoom-toggle/transforms.d.ts +51 -0
  91. package/dist/types/widgets-v2/actions/zoom-toggle/transforms.test.d.ts +1 -0
  92. package/dist/types/widgets-v2/actions/zoom-toggle/zoom-toggle.d.ts +35 -0
  93. package/dist/types/widgets-v2/bar/download.d.ts +24 -0
  94. package/dist/types/widgets-v2/bar/index.d.ts +4 -0
  95. package/dist/types/widgets-v2/bar/options.d.ts +43 -0
  96. package/dist/types/widgets-v2/bar/options.test.d.ts +1 -0
  97. package/dist/types/widgets-v2/bar/skeleton.d.ts +6 -0
  98. package/dist/types/widgets-v2/bar/types.d.ts +41 -0
  99. package/dist/types/widgets-v2/category/category-ui.d.ts +81 -0
  100. package/dist/types/widgets-v2/category/category.d.ts +48 -0
  101. package/dist/types/widgets-v2/category/components/category-bar-stacked.d.ts +28 -0
  102. package/dist/types/widgets-v2/category/components/category-bar.d.ts +23 -0
  103. package/dist/types/widgets-v2/category/components/category-legend.d.ts +18 -0
  104. package/dist/types/widgets-v2/category/components/category-row-multi.d.ts +31 -0
  105. package/dist/types/widgets-v2/category/components/category-row-other.d.ts +13 -0
  106. package/dist/types/widgets-v2/category/components/category-row-single.d.ts +28 -0
  107. package/dist/types/widgets-v2/category/components/category-row-stacked.d.ts +38 -0
  108. package/dist/types/widgets-v2/category/download.d.ts +16 -0
  109. package/dist/types/widgets-v2/category/download.test.d.ts +1 -0
  110. package/dist/types/widgets-v2/category/index.d.ts +10 -0
  111. package/dist/types/widgets-v2/category/skeleton.d.ts +11 -0
  112. package/dist/types/widgets-v2/category/style.d.ts +166 -0
  113. package/dist/types/widgets-v2/category/types.d.ts +49 -0
  114. package/dist/types/widgets-v2/echart/echart-ui.d.ts +44 -0
  115. package/dist/types/widgets-v2/echart/echart.d.ts +75 -0
  116. package/dist/types/widgets-v2/echart/index.d.ts +4 -0
  117. package/dist/types/widgets-v2/echart/shared-resize-observer.d.ts +5 -0
  118. package/dist/types/widgets-v2/echart/shared-resize-observer.test.d.ts +1 -0
  119. package/dist/types/widgets-v2/echart/style.d.ts +6 -0
  120. package/dist/types/widgets-v2/echart/use-chart-selection.d.ts +51 -0
  121. package/dist/types/widgets-v2/formula/delta.d.ts +22 -0
  122. package/dist/types/widgets-v2/formula/download.d.ts +20 -0
  123. package/dist/types/widgets-v2/formula/formula-ui.d.ts +20 -0
  124. package/dist/types/widgets-v2/formula/formula.d.ts +8 -0
  125. package/dist/types/widgets-v2/formula/index.d.ts +11 -0
  126. package/dist/types/widgets-v2/formula/note.d.ts +11 -0
  127. package/dist/types/widgets-v2/formula/prefix.d.ts +12 -0
  128. package/dist/types/widgets-v2/formula/series.d.ts +16 -0
  129. package/dist/types/widgets-v2/formula/skeleton.d.ts +4 -0
  130. package/dist/types/widgets-v2/formula/style.d.ts +29 -0
  131. package/dist/types/widgets-v2/formula/suffix.d.ts +12 -0
  132. package/dist/types/widgets-v2/formula/types.d.ts +40 -0
  133. package/dist/types/widgets-v2/formula/value.d.ts +14 -0
  134. package/dist/types/widgets-v2/histogram/download.d.ts +17 -0
  135. package/dist/types/widgets-v2/histogram/download.test.d.ts +1 -0
  136. package/dist/types/widgets-v2/histogram/index.d.ts +5 -0
  137. package/dist/types/widgets-v2/histogram/options.d.ts +42 -0
  138. package/dist/types/widgets-v2/histogram/options.test.d.ts +1 -0
  139. package/dist/types/widgets-v2/histogram/skeleton.d.ts +9 -0
  140. package/dist/types/widgets-v2/histogram/transforms.d.ts +17 -0
  141. package/dist/types/widgets-v2/histogram/transforms.test.d.ts +1 -0
  142. package/dist/types/widgets-v2/histogram/types.d.ts +47 -0
  143. package/dist/types/widgets-v2/index.d.ts +107 -0
  144. package/dist/types/widgets-v2/markdown/download.d.ts +16 -0
  145. package/dist/types/widgets-v2/markdown/download.test.d.ts +1 -0
  146. package/dist/types/widgets-v2/markdown/index.d.ts +6 -0
  147. package/dist/types/widgets-v2/markdown/markdown-content.d.ts +34 -0
  148. package/dist/types/widgets-v2/markdown/markdown-ui.d.ts +12 -0
  149. package/dist/types/widgets-v2/markdown/markdown.d.ts +6 -0
  150. package/dist/types/widgets-v2/markdown/skeleton.d.ts +4 -0
  151. package/dist/types/widgets-v2/markdown/style.d.ts +61 -0
  152. package/dist/types/widgets-v2/markdown/types.d.ts +4 -0
  153. package/dist/types/widgets-v2/note/labels.d.ts +5 -0
  154. package/dist/types/widgets-v2/note/style.d.ts +26 -0
  155. package/dist/types/widgets-v2/note/widget-note.d.ts +46 -0
  156. package/dist/types/widgets-v2/pie/download.d.ts +17 -0
  157. package/dist/types/widgets-v2/pie/download.test.d.ts +1 -0
  158. package/dist/types/widgets-v2/pie/index.d.ts +4 -0
  159. package/dist/types/widgets-v2/pie/options.d.ts +35 -0
  160. package/dist/types/widgets-v2/pie/options.test.d.ts +1 -0
  161. package/dist/types/widgets-v2/pie/skeleton.d.ts +4 -0
  162. package/dist/types/widgets-v2/pie/types.d.ts +50 -0
  163. package/dist/types/widgets-v2/provider/widget-provider.d.ts +32 -0
  164. package/dist/types/widgets-v2/range/index.d.ts +4 -0
  165. package/dist/types/widgets-v2/range/range-ui.d.ts +19 -0
  166. package/dist/types/widgets-v2/range/range.d.ts +19 -0
  167. package/dist/types/widgets-v2/range/skeleton.d.ts +9 -0
  168. package/dist/types/widgets-v2/range/style.d.ts +40 -0
  169. package/dist/types/widgets-v2/range/types.d.ts +37 -0
  170. package/dist/types/widgets-v2/scatterplot/download.d.ts +16 -0
  171. package/dist/types/widgets-v2/scatterplot/download.test.d.ts +1 -0
  172. package/dist/types/widgets-v2/scatterplot/index.d.ts +5 -0
  173. package/dist/types/widgets-v2/scatterplot/options.d.ts +42 -0
  174. package/dist/types/widgets-v2/scatterplot/options.test.d.ts +1 -0
  175. package/dist/types/widgets-v2/scatterplot/skeleton.d.ts +12 -0
  176. package/dist/types/widgets-v2/scatterplot/transforms.d.ts +17 -0
  177. package/dist/types/widgets-v2/scatterplot/transforms.test.d.ts +1 -0
  178. package/dist/types/widgets-v2/scatterplot/types.d.ts +50 -0
  179. package/dist/types/widgets-v2/selection-summary/labels.d.ts +6 -0
  180. package/dist/types/widgets-v2/selection-summary/selection-summary.d.ts +22 -0
  181. package/dist/types/widgets-v2/selection-summary/style.d.ts +23 -0
  182. package/dist/types/widgets-v2/spread/download.d.ts +15 -0
  183. package/dist/types/widgets-v2/spread/download.test.d.ts +1 -0
  184. package/dist/types/widgets-v2/spread/index.d.ts +6 -0
  185. package/dist/types/widgets-v2/spread/separator.d.ts +7 -0
  186. package/dist/types/widgets-v2/spread/skeleton.d.ts +9 -0
  187. package/dist/types/widgets-v2/spread/spread-ui.d.ts +18 -0
  188. package/dist/types/widgets-v2/spread/spread.d.ts +5 -0
  189. package/dist/types/widgets-v2/spread/types.d.ts +25 -0
  190. package/dist/types/widgets-v2/state/labels.d.ts +7 -0
  191. package/dist/types/widgets-v2/state/labels.test.d.ts +1 -0
  192. package/dist/types/widgets-v2/state/style.d.ts +19 -0
  193. package/dist/types/widgets-v2/state/widget-state.d.ts +19 -0
  194. package/dist/types/widgets-v2/stores/index.d.ts +8 -0
  195. package/dist/types/widgets-v2/stores/pipeline-middleware.d.ts +5 -0
  196. package/dist/types/widgets-v2/stores/pipeline-middleware.test.d.ts +1 -0
  197. package/dist/types/widgets-v2/stores/transforms.d.ts +4 -0
  198. package/dist/types/widgets-v2/stores/transforms.test.d.ts +1 -0
  199. package/dist/types/widgets-v2/stores/types.d.ts +55 -0
  200. package/dist/types/widgets-v2/stores/use-echart-instance.d.ts +15 -0
  201. package/dist/types/widgets-v2/stores/use-transform-enabled.d.ts +17 -0
  202. package/dist/types/widgets-v2/stores/use-transform.d.ts +12 -0
  203. package/dist/types/widgets-v2/stores/widget-context.d.ts +2 -0
  204. package/dist/types/widgets-v2/stores/widget-store-registry.d.ts +74 -0
  205. package/dist/types/widgets-v2/stores/widget-store-registry.test.d.ts +1 -0
  206. package/dist/types/widgets-v2/subheader/style.d.ts +10 -0
  207. package/dist/types/widgets-v2/subheader/subheader.d.ts +11 -0
  208. package/dist/types/widgets-v2/table/download.d.ts +18 -0
  209. package/dist/types/widgets-v2/table/download.test.d.ts +1 -0
  210. package/dist/types/widgets-v2/table/helpers.d.ts +32 -0
  211. package/dist/types/widgets-v2/table/helpers.test.d.ts +1 -0
  212. package/dist/types/widgets-v2/table/index.d.ts +7 -0
  213. package/dist/types/widgets-v2/table/labels.d.ts +22 -0
  214. package/dist/types/widgets-v2/table/skeleton.d.ts +22 -0
  215. package/dist/types/widgets-v2/table/style.d.ts +44 -0
  216. package/dist/types/widgets-v2/table/table-ui.d.ts +38 -0
  217. package/dist/types/widgets-v2/table/table.d.ts +50 -0
  218. package/dist/types/widgets-v2/table/types.d.ts +37 -0
  219. package/dist/types/widgets-v2/test-utils.d.ts +52 -0
  220. package/dist/types/widgets-v2/timeseries/download.d.ts +17 -0
  221. package/dist/types/widgets-v2/timeseries/download.test.d.ts +1 -0
  222. package/dist/types/widgets-v2/timeseries/index.d.ts +4 -0
  223. package/dist/types/widgets-v2/timeseries/options.d.ts +39 -0
  224. package/dist/types/widgets-v2/timeseries/options.test.d.ts +1 -0
  225. package/dist/types/widgets-v2/timeseries/skeleton.d.ts +8 -0
  226. package/dist/types/widgets-v2/timeseries/types.d.ts +56 -0
  227. package/dist/types/widgets-v2/toolbox/labels.d.ts +5 -0
  228. package/dist/types/widgets-v2/toolbox/style.d.ts +30 -0
  229. package/dist/types/widgets-v2/toolbox/toolbox.d.ts +49 -0
  230. package/dist/types/widgets-v2/utils/data-zoom-layout.d.ts +11 -0
  231. package/dist/types/widgets-v2/utils/index.d.ts +2 -0
  232. package/dist/types/widgets-v2/utils/merge-options.d.ts +12 -0
  233. package/dist/types/widgets-v2/utils/merge-options.test.d.ts +1 -0
  234. package/dist/types/widgets-v2/wrapper/index.d.ts +4 -0
  235. package/dist/types/widgets-v2/wrapper/labels.d.ts +6 -0
  236. package/dist/types/widgets-v2/wrapper/style.d.ts +111 -0
  237. package/dist/types/widgets-v2/wrapper/widget-actions.d.ts +22 -0
  238. package/dist/types/widgets-v2/wrapper/widget-content.d.ts +12 -0
  239. package/dist/types/widgets-v2/wrapper/widget-wrapper.d.ts +51 -0
  240. package/dist/use-transform-DXPN3nY7.js +110 -0
  241. package/dist/use-transform-DXPN3nY7.js.map +1 -0
  242. package/dist/widget-context-DTGO0Yta.js +13 -0
  243. package/dist/widget-context-DTGO0Yta.js.map +1 -0
  244. package/dist/widget-store-registry-_W4Z4xp-.js +178 -0
  245. package/dist/widget-store-registry-_W4Z4xp-.js.map +1 -0
  246. package/dist/widgets/bar.js +14 -13
  247. package/dist/widgets/bar.js.map +1 -1
  248. package/dist/widgets/histogram.js +8 -7
  249. package/dist/widgets/histogram.js.map +1 -1
  250. package/dist/widgets/pie.js +19 -18
  251. package/dist/widgets/pie.js.map +1 -1
  252. package/dist/widgets/scatterplot.js +8 -7
  253. package/dist/widgets/scatterplot.js.map +1 -1
  254. package/dist/widgets/timeseries.js +11 -10
  255. package/dist/widgets/timeseries.js.map +1 -1
  256. package/dist/widgets/utils.js +8 -7
  257. package/dist/widgets/utils.js.map +1 -1
  258. package/dist/widgets-v2/actions.js +43 -0
  259. package/dist/widgets-v2/actions.js.map +1 -0
  260. package/dist/widgets-v2/bar.js +327 -0
  261. package/dist/widgets-v2/bar.js.map +1 -0
  262. package/dist/widgets-v2/category.js +104 -0
  263. package/dist/widgets-v2/category.js.map +1 -0
  264. package/dist/widgets-v2/echart.js +57 -0
  265. package/dist/widgets-v2/echart.js.map +1 -0
  266. package/dist/widgets-v2/formula.js +74 -0
  267. package/dist/widgets-v2/formula.js.map +1 -0
  268. package/dist/widgets-v2/histogram.js +350 -0
  269. package/dist/widgets-v2/histogram.js.map +1 -0
  270. package/dist/widgets-v2/markdown.js +68 -0
  271. package/dist/widgets-v2/markdown.js.map +1 -0
  272. package/dist/widgets-v2/pie.js +381 -0
  273. package/dist/widgets-v2/pie.js.map +1 -0
  274. package/dist/widgets-v2/range.js +52 -0
  275. package/dist/widgets-v2/range.js.map +1 -0
  276. package/dist/widgets-v2/scatterplot.js +405 -0
  277. package/dist/widgets-v2/scatterplot.js.map +1 -0
  278. package/dist/widgets-v2/spread.js +72 -0
  279. package/dist/widgets-v2/spread.js.map +1 -0
  280. package/dist/widgets-v2/stores.js +42 -0
  281. package/dist/widgets-v2/stores.js.map +1 -0
  282. package/dist/widgets-v2/table.js +78 -0
  283. package/dist/widgets-v2/table.js.map +1 -0
  284. package/dist/widgets-v2/timeseries.js +352 -0
  285. package/dist/widgets-v2/timeseries.js.map +1 -0
  286. package/dist/widgets-v2/utils.js +7 -0
  287. package/dist/widgets-v2/utils.js.map +1 -0
  288. package/dist/widgets-v2.js +953 -0
  289. package/dist/widgets-v2.js.map +1 -0
  290. package/package.json +73 -5
  291. package/src/components/lasso-tool/chip.test.tsx +176 -0
  292. package/src/components/lasso-tool/lasso-tool-inline.test.tsx +171 -0
  293. package/src/components/lasso-tool/lasso-tool.test.tsx +198 -0
  294. package/src/components/list-data/list-data.test.tsx +73 -0
  295. package/src/components/no-data-alert/no-data-alert.test.tsx +38 -0
  296. package/src/components/responsive-drawer/responsive-drawer.test.tsx +68 -0
  297. package/src/widgets/actions/brush-toggle/brush-overlay.test.tsx +465 -0
  298. package/src/widgets/actions/brush-toggle/brush-toggle.test.tsx +208 -0
  299. package/src/widgets/actions/change-column/change-column-dnd.test.tsx +193 -0
  300. package/src/widgets/actions/change-column/sortable-column-item.test.tsx +124 -0
  301. package/src/widgets/actions/zoom-toggle/zoom-toggle.test.tsx +322 -0
  302. package/src/widgets/category/components/category-rows.test.tsx +213 -0
  303. package/src/widgets/echart/utils.test.ts +277 -0
  304. package/src/widgets/formula/config.test.ts +37 -0
  305. package/src/widgets/range/components/range-item.test.tsx +243 -0
  306. package/src/widgets/stores/widget-store-branches.test.ts +275 -0
  307. package/src/widgets/table/config.test.ts +65 -0
  308. package/src/widgets/utils/chart-config/option-builders.test.ts +188 -0
  309. package/src/widgets-v2/PERFORMANCE.md +189 -0
  310. package/src/widgets-v2/actions/brush-toggle/brush-toggle.test.tsx +180 -0
  311. package/src/widgets-v2/actions/brush-toggle/brush-toggle.tsx +154 -0
  312. package/src/widgets-v2/actions/brush-toggle/index.ts +3 -0
  313. package/src/widgets-v2/actions/brush-toggle/labels.ts +9 -0
  314. package/src/widgets-v2/actions/brush-toggle/style.ts +11 -0
  315. package/src/widgets-v2/actions/brush-toggle/transforms.test.ts +47 -0
  316. package/src/widgets-v2/actions/brush-toggle/transforms.ts +31 -0
  317. package/src/widgets-v2/actions/change-column/change-column-icon.tsx +14 -0
  318. package/src/widgets-v2/actions/change-column/change-column.test.tsx +59 -0
  319. package/src/widgets-v2/actions/change-column/change-column.tsx +180 -0
  320. package/src/widgets-v2/actions/change-column/index.ts +7 -0
  321. package/src/widgets-v2/actions/change-column/labels.ts +9 -0
  322. package/src/widgets-v2/actions/change-column/sortable-column-item.tsx +56 -0
  323. package/src/widgets-v2/actions/change-column/style.ts +32 -0
  324. package/src/widgets-v2/actions/change-column/types.ts +11 -0
  325. package/src/widgets-v2/actions/download/download.test.tsx +327 -0
  326. package/src/widgets-v2/actions/download/download.tsx +144 -0
  327. package/src/widgets-v2/actions/download/exports.test.tsx +198 -0
  328. package/src/widgets-v2/actions/download/exports.ts +115 -0
  329. package/src/widgets-v2/actions/download/icons.tsx +26 -0
  330. package/src/widgets-v2/actions/download/index.ts +13 -0
  331. package/src/widgets-v2/actions/download/labels.ts +16 -0
  332. package/src/widgets-v2/actions/download/png-item.test.tsx +72 -0
  333. package/src/widgets-v2/actions/download/png-item.tsx +52 -0
  334. package/src/widgets-v2/actions/download/style.ts +3 -0
  335. package/src/widgets-v2/actions/download/types.ts +32 -0
  336. package/src/widgets-v2/actions/fullscreen/fullscreen.test.tsx +150 -0
  337. package/src/widgets-v2/actions/fullscreen/fullscreen.tsx +230 -0
  338. package/src/widgets-v2/actions/fullscreen/index.ts +7 -0
  339. package/src/widgets-v2/actions/fullscreen/labels.ts +9 -0
  340. package/src/widgets-v2/actions/fullscreen/style.ts +59 -0
  341. package/src/widgets-v2/actions/fullscreen/types.ts +15 -0
  342. package/src/widgets-v2/actions/index.ts +82 -0
  343. package/src/widgets-v2/actions/lock-selection/index.ts +10 -0
  344. package/src/widgets-v2/actions/lock-selection/labels.ts +11 -0
  345. package/src/widgets-v2/actions/lock-selection/lock-selection.test.tsx +187 -0
  346. package/src/widgets-v2/actions/lock-selection/lock-selection.tsx +130 -0
  347. package/src/widgets-v2/actions/lock-selection/style.ts +11 -0
  348. package/src/widgets-v2/actions/lock-selection/transforms.ts +27 -0
  349. package/src/widgets-v2/actions/relative-data/index.ts +3 -0
  350. package/src/widgets-v2/actions/relative-data/labels.ts +9 -0
  351. package/src/widgets-v2/actions/relative-data/relative-data.test.tsx +71 -0
  352. package/src/widgets-v2/actions/relative-data/relative-data.tsx +107 -0
  353. package/src/widgets-v2/actions/relative-data/style.ts +11 -0
  354. package/src/widgets-v2/actions/relative-data/transforms.test.ts +151 -0
  355. package/src/widgets-v2/actions/relative-data/transforms.ts +70 -0
  356. package/src/widgets-v2/actions/searcher/filter.ts +28 -0
  357. package/src/widgets-v2/actions/searcher/index.ts +8 -0
  358. package/src/widgets-v2/actions/searcher/labels.ts +13 -0
  359. package/src/widgets-v2/actions/searcher/searcher-toggle.tsx +91 -0
  360. package/src/widgets-v2/actions/searcher/searcher.test.tsx +92 -0
  361. package/src/widgets-v2/actions/searcher/searcher.tsx +112 -0
  362. package/src/widgets-v2/actions/searcher/style.ts +15 -0
  363. package/src/widgets-v2/actions/stack-toggle/index.ts +3 -0
  364. package/src/widgets-v2/actions/stack-toggle/labels.ts +9 -0
  365. package/src/widgets-v2/actions/stack-toggle/stack-toggle.test.tsx +61 -0
  366. package/src/widgets-v2/actions/stack-toggle/stack-toggle.tsx +54 -0
  367. package/src/widgets-v2/actions/stack-toggle/style.ts +11 -0
  368. package/src/widgets-v2/actions/stack-toggle/transforms.test.ts +43 -0
  369. package/src/widgets-v2/actions/stack-toggle/transforms.ts +25 -0
  370. package/src/widgets-v2/actions/zoom-toggle/index.ts +9 -0
  371. package/src/widgets-v2/actions/zoom-toggle/labels.ts +9 -0
  372. package/src/widgets-v2/actions/zoom-toggle/style.ts +11 -0
  373. package/src/widgets-v2/actions/zoom-toggle/transforms.test.ts +148 -0
  374. package/src/widgets-v2/actions/zoom-toggle/transforms.ts +171 -0
  375. package/src/widgets-v2/actions/zoom-toggle/zoom-toggle.test.tsx +107 -0
  376. package/src/widgets-v2/actions/zoom-toggle/zoom-toggle.tsx +106 -0
  377. package/src/widgets-v2/bar/download.test.tsx +91 -0
  378. package/src/widgets-v2/bar/download.tsx +66 -0
  379. package/src/widgets-v2/bar/index.ts +10 -0
  380. package/src/widgets-v2/bar/options.test.ts +317 -0
  381. package/src/widgets-v2/bar/options.ts +326 -0
  382. package/src/widgets-v2/bar/skeleton.test.tsx +19 -0
  383. package/src/widgets-v2/bar/skeleton.tsx +69 -0
  384. package/src/widgets-v2/bar/types.ts +46 -0
  385. package/src/widgets-v2/category/category-ui.test.tsx +746 -0
  386. package/src/widgets-v2/category/category-ui.tsx +389 -0
  387. package/src/widgets-v2/category/category.relative-data.test.tsx +107 -0
  388. package/src/widgets-v2/category/category.stack-toggle.test.tsx +85 -0
  389. package/src/widgets-v2/category/category.test.tsx +305 -0
  390. package/src/widgets-v2/category/category.tsx +121 -0
  391. package/src/widgets-v2/category/components/category-bar-stacked.test.tsx +121 -0
  392. package/src/widgets-v2/category/components/category-bar-stacked.tsx +73 -0
  393. package/src/widgets-v2/category/components/category-bar.test.tsx +64 -0
  394. package/src/widgets-v2/category/components/category-bar.tsx +49 -0
  395. package/src/widgets-v2/category/components/category-legend.test.tsx +51 -0
  396. package/src/widgets-v2/category/components/category-legend.tsx +39 -0
  397. package/src/widgets-v2/category/components/category-row-multi.tsx +86 -0
  398. package/src/widgets-v2/category/components/category-row-other.test.tsx +28 -0
  399. package/src/widgets-v2/category/components/category-row-other.tsx +33 -0
  400. package/src/widgets-v2/category/components/category-row-single.tsx +76 -0
  401. package/src/widgets-v2/category/components/category-row-stacked.test.tsx +244 -0
  402. package/src/widgets-v2/category/components/category-row-stacked.tsx +99 -0
  403. package/src/widgets-v2/category/download.test.ts +71 -0
  404. package/src/widgets-v2/category/download.ts +54 -0
  405. package/src/widgets-v2/category/index.ts +32 -0
  406. package/src/widgets-v2/category/skeleton.test.tsx +26 -0
  407. package/src/widgets-v2/category/skeleton.tsx +74 -0
  408. package/src/widgets-v2/category/style.ts +290 -0
  409. package/src/widgets-v2/category/types.ts +54 -0
  410. package/src/widgets-v2/echart/echart-ui.test.tsx +232 -0
  411. package/src/widgets-v2/echart/echart-ui.tsx +184 -0
  412. package/src/widgets-v2/echart/echart.test.tsx +229 -0
  413. package/src/widgets-v2/echart/echart.tsx +199 -0
  414. package/src/widgets-v2/echart/index.ts +22 -0
  415. package/src/widgets-v2/echart/shared-resize-observer.test.ts +91 -0
  416. package/src/widgets-v2/echart/shared-resize-observer.ts +56 -0
  417. package/src/widgets-v2/echart/style.ts +8 -0
  418. package/src/widgets-v2/echart/use-chart-selection.test.tsx +118 -0
  419. package/src/widgets-v2/echart/use-chart-selection.ts +115 -0
  420. package/src/widgets-v2/formula/delta.tsx +61 -0
  421. package/src/widgets-v2/formula/download.test.tsx +65 -0
  422. package/src/widgets-v2/formula/download.tsx +69 -0
  423. package/src/widgets-v2/formula/formula-ui.test.tsx +91 -0
  424. package/src/widgets-v2/formula/formula-ui.tsx +66 -0
  425. package/src/widgets-v2/formula/formula.test.tsx +50 -0
  426. package/src/widgets-v2/formula/formula.tsx +34 -0
  427. package/src/widgets-v2/formula/index.ts +17 -0
  428. package/src/widgets-v2/formula/note.tsx +25 -0
  429. package/src/widgets-v2/formula/prefix.tsx +25 -0
  430. package/src/widgets-v2/formula/series.tsx +67 -0
  431. package/src/widgets-v2/formula/skeleton.test.tsx +21 -0
  432. package/src/widgets-v2/formula/skeleton.tsx +27 -0
  433. package/src/widgets-v2/formula/style.ts +31 -0
  434. package/src/widgets-v2/formula/subcomponents.test.tsx +107 -0
  435. package/src/widgets-v2/formula/suffix.tsx +25 -0
  436. package/src/widgets-v2/formula/types.ts +44 -0
  437. package/src/widgets-v2/formula/value.tsx +31 -0
  438. package/src/widgets-v2/histogram/download.test.ts +94 -0
  439. package/src/widgets-v2/histogram/download.ts +60 -0
  440. package/src/widgets-v2/histogram/index.ts +10 -0
  441. package/src/widgets-v2/histogram/options.test.ts +304 -0
  442. package/src/widgets-v2/histogram/options.ts +337 -0
  443. package/src/widgets-v2/histogram/skeleton.test.tsx +16 -0
  444. package/src/widgets-v2/histogram/skeleton.tsx +70 -0
  445. package/src/widgets-v2/histogram/transforms.test.ts +46 -0
  446. package/src/widgets-v2/histogram/transforms.ts +30 -0
  447. package/src/widgets-v2/histogram/types.ts +51 -0
  448. package/src/widgets-v2/index.ts +201 -0
  449. package/src/widgets-v2/markdown/download.test.ts +66 -0
  450. package/src/widgets-v2/markdown/download.ts +53 -0
  451. package/src/widgets-v2/markdown/index.ts +6 -0
  452. package/src/widgets-v2/markdown/markdown-content.test.tsx +155 -0
  453. package/src/widgets-v2/markdown/markdown-content.tsx +72 -0
  454. package/src/widgets-v2/markdown/markdown-ui.test.tsx +75 -0
  455. package/src/widgets-v2/markdown/markdown-ui.tsx +55 -0
  456. package/src/widgets-v2/markdown/markdown.test.tsx +39 -0
  457. package/src/widgets-v2/markdown/markdown.tsx +17 -0
  458. package/src/widgets-v2/markdown/skeleton.test.tsx +15 -0
  459. package/src/widgets-v2/markdown/skeleton.tsx +32 -0
  460. package/src/widgets-v2/markdown/style.ts +53 -0
  461. package/src/widgets-v2/markdown/types.ts +4 -0
  462. package/src/widgets-v2/note/labels.ts +9 -0
  463. package/src/widgets-v2/note/style.ts +26 -0
  464. package/src/widgets-v2/note/widget-note.test.tsx +158 -0
  465. package/src/widgets-v2/note/widget-note.tsx +172 -0
  466. package/src/widgets-v2/pie/download.test.ts +78 -0
  467. package/src/widgets-v2/pie/download.ts +55 -0
  468. package/src/widgets-v2/pie/index.ts +10 -0
  469. package/src/widgets-v2/pie/options.test.ts +585 -0
  470. package/src/widgets-v2/pie/options.ts +509 -0
  471. package/src/widgets-v2/pie/skeleton.test.tsx +17 -0
  472. package/src/widgets-v2/pie/skeleton.tsx +32 -0
  473. package/src/widgets-v2/pie/types.ts +55 -0
  474. package/src/widgets-v2/provider/widget-provider.test.tsx +119 -0
  475. package/src/widgets-v2/provider/widget-provider.tsx +111 -0
  476. package/src/widgets-v2/range/index.ts +4 -0
  477. package/src/widgets-v2/range/range-ui.test.tsx +130 -0
  478. package/src/widgets-v2/range/range-ui.tsx +211 -0
  479. package/src/widgets-v2/range/range.test.tsx +68 -0
  480. package/src/widgets-v2/range/range.tsx +46 -0
  481. package/src/widgets-v2/range/skeleton.test.tsx +17 -0
  482. package/src/widgets-v2/range/skeleton.tsx +47 -0
  483. package/src/widgets-v2/range/style.ts +41 -0
  484. package/src/widgets-v2/range/types.ts +37 -0
  485. package/src/widgets-v2/scatterplot/download.test.ts +71 -0
  486. package/src/widgets-v2/scatterplot/download.ts +54 -0
  487. package/src/widgets-v2/scatterplot/index.ts +11 -0
  488. package/src/widgets-v2/scatterplot/options.test.ts +399 -0
  489. package/src/widgets-v2/scatterplot/options.ts +421 -0
  490. package/src/widgets-v2/scatterplot/skeleton.test.tsx +17 -0
  491. package/src/widgets-v2/scatterplot/skeleton.tsx +84 -0
  492. package/src/widgets-v2/scatterplot/transforms.test.ts +97 -0
  493. package/src/widgets-v2/scatterplot/transforms.ts +38 -0
  494. package/src/widgets-v2/scatterplot/types.ts +55 -0
  495. package/src/widgets-v2/selection-summary/labels.ts +11 -0
  496. package/src/widgets-v2/selection-summary/selection-summary.test.tsx +53 -0
  497. package/src/widgets-v2/selection-summary/selection-summary.tsx +62 -0
  498. package/src/widgets-v2/selection-summary/style.ts +23 -0
  499. package/src/widgets-v2/spread/download.test.ts +64 -0
  500. package/src/widgets-v2/spread/download.ts +59 -0
  501. package/src/widgets-v2/spread/index.ts +6 -0
  502. package/src/widgets-v2/spread/separator.tsx +11 -0
  503. package/src/widgets-v2/spread/skeleton.test.tsx +17 -0
  504. package/src/widgets-v2/spread/skeleton.tsx +38 -0
  505. package/src/widgets-v2/spread/spread-ui.test.tsx +108 -0
  506. package/src/widgets-v2/spread/spread-ui.tsx +52 -0
  507. package/src/widgets-v2/spread/spread.test.tsx +50 -0
  508. package/src/widgets-v2/spread/spread.tsx +31 -0
  509. package/src/widgets-v2/spread/types.ts +27 -0
  510. package/src/widgets-v2/state/labels.test.ts +33 -0
  511. package/src/widgets-v2/state/labels.ts +20 -0
  512. package/src/widgets-v2/state/style.ts +25 -0
  513. package/src/widgets-v2/state/widget-state.test.tsx +294 -0
  514. package/src/widgets-v2/state/widget-state.tsx +184 -0
  515. package/src/widgets-v2/stores/index.ts +49 -0
  516. package/src/widgets-v2/stores/pipeline-middleware.test.ts +187 -0
  517. package/src/widgets-v2/stores/pipeline-middleware.ts +91 -0
  518. package/src/widgets-v2/stores/transforms.test.ts +162 -0
  519. package/src/widgets-v2/stores/transforms.ts +70 -0
  520. package/src/widgets-v2/stores/types.ts +64 -0
  521. package/src/widgets-v2/stores/use-echart-instance.test.tsx +91 -0
  522. package/src/widgets-v2/stores/use-echart-instance.ts +29 -0
  523. package/src/widgets-v2/stores/use-transform-enabled.test.tsx +127 -0
  524. package/src/widgets-v2/stores/use-transform-enabled.ts +25 -0
  525. package/src/widgets-v2/stores/use-transform.test.tsx +262 -0
  526. package/src/widgets-v2/stores/use-transform.ts +158 -0
  527. package/src/widgets-v2/stores/widget-context.test.tsx +58 -0
  528. package/src/widgets-v2/stores/widget-context.ts +15 -0
  529. package/src/widgets-v2/stores/widget-store-registry.test.ts +292 -0
  530. package/src/widgets-v2/stores/widget-store-registry.ts +248 -0
  531. package/src/widgets-v2/subheader/style.ts +12 -0
  532. package/src/widgets-v2/subheader/subheader.test.tsx +30 -0
  533. package/src/widgets-v2/subheader/subheader.tsx +16 -0
  534. package/src/widgets-v2/table/download.test.ts +75 -0
  535. package/src/widgets-v2/table/download.ts +47 -0
  536. package/src/widgets-v2/table/helpers.test.ts +214 -0
  537. package/src/widgets-v2/table/helpers.ts +136 -0
  538. package/src/widgets-v2/table/index.ts +23 -0
  539. package/src/widgets-v2/table/labels.tsx +41 -0
  540. package/src/widgets-v2/table/skeleton.test.tsx +26 -0
  541. package/src/widgets-v2/table/skeleton.tsx +65 -0
  542. package/src/widgets-v2/table/style.ts +46 -0
  543. package/src/widgets-v2/table/table-ui.test.tsx +200 -0
  544. package/src/widgets-v2/table/table-ui.tsx +331 -0
  545. package/src/widgets-v2/table/table.test.tsx +119 -0
  546. package/src/widgets-v2/table/table.tsx +174 -0
  547. package/src/widgets-v2/table/types.ts +44 -0
  548. package/src/widgets-v2/test-utils.ts +107 -0
  549. package/src/widgets-v2/timeseries/download.test.ts +95 -0
  550. package/src/widgets-v2/timeseries/download.ts +86 -0
  551. package/src/widgets-v2/timeseries/index.ts +10 -0
  552. package/src/widgets-v2/timeseries/options.test.ts +379 -0
  553. package/src/widgets-v2/timeseries/options.ts +341 -0
  554. package/src/widgets-v2/timeseries/skeleton.test.tsx +13 -0
  555. package/src/widgets-v2/timeseries/skeleton.tsx +76 -0
  556. package/src/widgets-v2/timeseries/types.ts +61 -0
  557. package/src/widgets-v2/toolbox/labels.ts +9 -0
  558. package/src/widgets-v2/toolbox/style.ts +33 -0
  559. package/src/widgets-v2/toolbox/toolbox.test.tsx +200 -0
  560. package/src/widgets-v2/toolbox/toolbox.tsx +309 -0
  561. package/src/widgets-v2/utils/data-zoom-layout.ts +26 -0
  562. package/src/widgets-v2/utils/index.ts +2 -0
  563. package/src/widgets-v2/utils/merge-options.test.ts +52 -0
  564. package/src/widgets-v2/utils/merge-options.ts +50 -0
  565. package/src/widgets-v2/wrapper/index.ts +14 -0
  566. package/src/widgets-v2/wrapper/labels.ts +11 -0
  567. package/src/widgets-v2/wrapper/style.ts +134 -0
  568. package/src/widgets-v2/wrapper/widget-actions.test.tsx +52 -0
  569. package/src/widgets-v2/wrapper/widget-actions.tsx +43 -0
  570. package/src/widgets-v2/wrapper/widget-content.test.tsx +27 -0
  571. package/src/widgets-v2/wrapper/widget-content.tsx +29 -0
  572. package/src/widgets-v2/wrapper/widget-wrapper.test.tsx +159 -0
  573. package/src/widgets-v2/wrapper/widget-wrapper.tsx +178 -0
  574. package/dist/styles-BYTyKQFP.js.map +0 -1
@@ -0,0 +1,327 @@
1
+ import { describe, it, expect, vi } from 'vitest'
2
+ import { fireEvent, render, screen, waitFor } from '@testing-library/react'
3
+ import { Download } from './download'
4
+ import type { DownloadItem } from './types'
5
+ import * as exports from './exports'
6
+
7
+ function fakeItem(overrides?: Partial<DownloadItem>): DownloadItem {
8
+ return {
9
+ id: 'csv',
10
+ label: 'Download as CSV',
11
+ resolve: () =>
12
+ Promise.resolve({
13
+ url: 'blob:fake',
14
+ filename: 'data.csv',
15
+ revoke: vi.fn(),
16
+ }),
17
+ ...overrides,
18
+ }
19
+ }
20
+
21
+ describe('<Download>', () => {
22
+ it('renders a trigger and lists every item when opened', () => {
23
+ render(
24
+ <Download
25
+ items={[
26
+ fakeItem({ id: 'csv', label: 'Download CSV' }),
27
+ fakeItem({ id: 'png', label: 'Download PNG' }),
28
+ ]}
29
+ />,
30
+ )
31
+ fireEvent.click(screen.getByRole('button', { name: 'Download' }))
32
+ expect(screen.getByText('Download CSV')).toBeTruthy()
33
+ expect(screen.getByText('Download PNG')).toBeTruthy()
34
+ })
35
+
36
+ it('honors custom trigger label', () => {
37
+ render(
38
+ <Download items={[fakeItem()]} labels={{ trigger: 'Save to disk' }} />,
39
+ )
40
+ expect(screen.getByRole('button', { name: 'Save to disk' })).toBeTruthy()
41
+ })
42
+
43
+ it('selecting an item calls its resolve(); the menu then closes', async () => {
44
+ const resolve = vi.fn(() =>
45
+ Promise.resolve({
46
+ url: 'blob:abc',
47
+ filename: 'x.csv',
48
+ revoke: vi.fn(),
49
+ }),
50
+ )
51
+ render(<Download items={[fakeItem({ resolve })]} />)
52
+ fireEvent.click(screen.getByRole('button', { name: 'Download' }))
53
+ fireEvent.click(screen.getByText('Download as CSV'))
54
+ expect(resolve).toHaveBeenCalledTimes(1)
55
+ // Wait a tick for the resolve promise to settle.
56
+ await Promise.resolve()
57
+ })
58
+
59
+ it('renders a custom icon component when provided', () => {
60
+ function CustomIcon(props: React.SVGProps<SVGSVGElement>) {
61
+ return <svg data-testid='custom-dl-icon' {...props} />
62
+ }
63
+ render(<Download items={[fakeItem()]} icon={CustomIcon} />)
64
+ expect(screen.getByTestId('custom-dl-icon')).toBeTruthy()
65
+ })
66
+
67
+ it('renders a per-item leading icon inside ListItemIcon', () => {
68
+ render(
69
+ <Download
70
+ items={[
71
+ fakeItem({
72
+ icon: <svg data-testid='item-icon' />,
73
+ }),
74
+ ]}
75
+ />,
76
+ )
77
+ fireEvent.click(screen.getByRole('button', { name: 'Download' }))
78
+ const icon = screen.getByTestId('item-icon')
79
+ expect(icon.closest('.MuiListItemIcon-root')).not.toBeNull()
80
+ })
81
+
82
+ it('disables the menu item when the item is `disabled` and skips resolve()', () => {
83
+ const resolve = vi.fn(() =>
84
+ Promise.resolve({ url: 'blob:abc', filename: 'x.csv' }),
85
+ )
86
+ render(
87
+ <Download
88
+ items={[fakeItem({ id: 'csv', label: 'CSV', disabled: true, resolve })]}
89
+ />,
90
+ )
91
+ fireEvent.click(screen.getByRole('button', { name: 'Download' }))
92
+ const menuItem = screen.getByText('CSV').closest('li')
93
+ expect(menuItem?.getAttribute('aria-disabled')).toBe('true')
94
+ fireEvent.click(screen.getByText('CSV'))
95
+ expect(resolve).not.toHaveBeenCalled()
96
+ })
97
+
98
+ it('overrides the resolved filename when `item.filename` is set', async () => {
99
+ const trigger = vi
100
+ .spyOn(exports, 'triggerLinkDownload')
101
+ .mockImplementation(() => undefined)
102
+ try {
103
+ render(
104
+ <Download
105
+ items={[
106
+ fakeItem({
107
+ id: 'csv',
108
+ label: 'CSV',
109
+ filename: 'override.csv',
110
+ resolve: () =>
111
+ Promise.resolve({ url: 'blob:fake', filename: 'auto.csv' }),
112
+ }),
113
+ ]}
114
+ />,
115
+ )
116
+ fireEvent.click(screen.getByRole('button', { name: 'Download' }))
117
+ fireEvent.click(screen.getByText('CSV'))
118
+ // wait for the resolve() promise to settle
119
+ await Promise.resolve()
120
+ await Promise.resolve()
121
+ expect(trigger).toHaveBeenCalledWith({
122
+ url: 'blob:fake',
123
+ filename: 'override.csv',
124
+ })
125
+ } finally {
126
+ trigger.mockRestore()
127
+ }
128
+ })
129
+
130
+ it('uses the resolved filename when `item.filename` is not set', async () => {
131
+ const trigger = vi
132
+ .spyOn(exports, 'triggerLinkDownload')
133
+ .mockImplementation(() => undefined)
134
+ try {
135
+ render(
136
+ <Download
137
+ items={[
138
+ fakeItem({
139
+ id: 'csv',
140
+ label: 'CSV',
141
+ resolve: () =>
142
+ Promise.resolve({ url: 'blob:fake', filename: 'auto.csv' }),
143
+ }),
144
+ ]}
145
+ />,
146
+ )
147
+ fireEvent.click(screen.getByRole('button', { name: 'Download' }))
148
+ fireEvent.click(screen.getByText('CSV'))
149
+ await Promise.resolve()
150
+ await Promise.resolve()
151
+ expect(trigger).toHaveBeenCalledWith({
152
+ url: 'blob:fake',
153
+ filename: 'auto.csv',
154
+ })
155
+ } finally {
156
+ trigger.mockRestore()
157
+ }
158
+ })
159
+
160
+ it('shows a spinner and disables the trigger while resolve() is pending; clears once it settles', async () => {
161
+ const trigger = vi
162
+ .spyOn(exports, 'triggerLinkDownload')
163
+ .mockImplementation(() => undefined)
164
+ let releaseResolve: (v: {
165
+ url: string
166
+ filename: string
167
+ revoke?: () => void
168
+ }) => void = () => undefined
169
+ const pending = new Promise<{
170
+ url: string
171
+ filename: string
172
+ revoke?: () => void
173
+ }>((res) => {
174
+ releaseResolve = res
175
+ })
176
+ try {
177
+ render(
178
+ <Download
179
+ items={[
180
+ fakeItem({ id: 'csv', label: 'CSV', resolve: () => pending }),
181
+ ]}
182
+ />,
183
+ )
184
+ const triggerBtn = screen.getByRole('button', { name: 'Download' })
185
+ fireEvent.click(triggerBtn)
186
+ fireEvent.click(screen.getByText('CSV'))
187
+
188
+ // In flight: trigger label and aria-busy switch, button is disabled,
189
+ // wrapper hover-fade `.active` className is applied, spinner is shown.
190
+ const busyBtn = screen.getByRole('button', { name: 'Downloading…' })
191
+ expect(busyBtn.getAttribute('aria-busy')).toBe('true')
192
+ expect((busyBtn as HTMLButtonElement).disabled).toBe(true)
193
+ expect(busyBtn.className).toMatch(/\bactive\b/)
194
+ expect(busyBtn.querySelector('[role="progressbar"]')).not.toBeNull()
195
+
196
+ // Settle the resolve and wait for the trigger to flip back to idle.
197
+ releaseResolve({ url: 'blob:fake', filename: 'data.csv' })
198
+ const idleBtn = await screen.findByRole('button', { name: 'Download' })
199
+ expect(idleBtn.getAttribute('aria-busy')).toBeNull()
200
+ expect((idleBtn as HTMLButtonElement).disabled).toBe(false)
201
+ expect(idleBtn.querySelector('[role="progressbar"]')).toBeNull()
202
+ } finally {
203
+ trigger.mockRestore()
204
+ }
205
+ })
206
+
207
+ it('clears the spinner when resolve() rejects and fires onError', async () => {
208
+ const onError = vi.fn()
209
+ render(
210
+ <Download
211
+ onError={onError}
212
+ items={[
213
+ fakeItem({
214
+ id: 'csv',
215
+ label: 'CSV',
216
+ resolve: () => Promise.reject(new Error('boom')),
217
+ }),
218
+ ]}
219
+ />,
220
+ )
221
+ fireEvent.click(screen.getByRole('button', { name: 'Download' }))
222
+ fireEvent.click(screen.getByText('CSV'))
223
+ // After the rejection the trigger label flips to the error label and
224
+ // `data-error` is set; the spinner is cleared and the button is enabled.
225
+ const errBtn = await screen.findByRole('button', {
226
+ name: 'Download failed',
227
+ })
228
+ expect((errBtn as HTMLButtonElement).disabled).toBe(false)
229
+ expect(errBtn.querySelector('[role="progressbar"]')).toBeNull()
230
+ expect(errBtn.getAttribute('data-error')).toBe('true')
231
+ await waitFor(() => expect(onError).toHaveBeenCalledTimes(1))
232
+ const first = onError.mock.calls[0]?.[0] as Error | undefined
233
+ expect(first).toBeInstanceOf(Error)
234
+ expect(first?.message).toBe('boom')
235
+ })
236
+
237
+ it('normalises non-Error rejections into a real Error before onError', async () => {
238
+ const onError = vi.fn()
239
+ render(
240
+ <Download
241
+ onError={onError}
242
+ items={[
243
+ fakeItem({
244
+ id: 'csv',
245
+ label: 'CSV',
246
+ // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
247
+ resolve: () => Promise.reject('non-error-value'),
248
+ }),
249
+ ]}
250
+ />,
251
+ )
252
+ fireEvent.click(screen.getByRole('button', { name: 'Download' }))
253
+ fireEvent.click(screen.getByText('CSV'))
254
+ await screen.findByRole('button', { name: 'Download failed' })
255
+ const first = onError.mock.calls[0]?.[0] as Error | undefined
256
+ expect(first).toBeInstanceOf(Error)
257
+ expect(first?.message).toBe('non-error-value')
258
+ })
259
+
260
+ it('clears the error state when the menu is re-opened', async () => {
261
+ render(
262
+ <Download
263
+ items={[
264
+ fakeItem({
265
+ id: 'csv',
266
+ label: 'CSV',
267
+ resolve: () => Promise.reject(new Error('boom')),
268
+ }),
269
+ ]}
270
+ />,
271
+ )
272
+ fireEvent.click(screen.getByRole('button', { name: 'Download' }))
273
+ fireEvent.click(screen.getByText('CSV'))
274
+ const errBtn = await screen.findByRole('button', {
275
+ name: 'Download failed',
276
+ })
277
+ fireEvent.click(errBtn)
278
+ // After re-open the trigger flips back to the idle label and data-error
279
+ // is gone. The menu is now open so we look up the trigger by its
280
+ // aria-haspopup attribute rather than its role+name (other body nodes
281
+ // get aria-hidden while the menu is open).
282
+ await waitFor(() => {
283
+ const btn = document.querySelector('button[aria-haspopup="true"]')
284
+ expect(btn).not.toBeNull()
285
+ expect(btn?.getAttribute('data-error')).toBeNull()
286
+ expect(btn?.getAttribute('aria-label')).toBe('Download')
287
+ })
288
+ })
289
+
290
+ it('defers revoke past the current task so the browser can dispatch the download', async () => {
291
+ const trigger = vi
292
+ .spyOn(exports, 'triggerLinkDownload')
293
+ .mockImplementation(() => undefined)
294
+ const setTimeoutSpy = vi.spyOn(window, 'setTimeout')
295
+ const revoke = vi.fn()
296
+ try {
297
+ render(
298
+ <Download
299
+ items={[
300
+ fakeItem({
301
+ id: 'csv',
302
+ label: 'CSV',
303
+ resolve: () =>
304
+ Promise.resolve({
305
+ url: 'blob:abc',
306
+ filename: 'data.csv',
307
+ revoke,
308
+ }),
309
+ }),
310
+ ]}
311
+ />,
312
+ )
313
+ fireEvent.click(screen.getByRole('button', { name: 'Download' }))
314
+ fireEvent.click(screen.getByText('CSV'))
315
+ // Trigger fires inside the resolve callback; revoke is scheduled via
316
+ // setTimeout(_, 0) rather than invoked inline. Wait until the trigger
317
+ // call has happened — at that point revoke must be scheduled, not run.
318
+ await waitFor(() => expect(trigger).toHaveBeenCalledTimes(1))
319
+ expect(setTimeoutSpy).toHaveBeenCalledWith(revoke, 0)
320
+ // After the queued timeout drains, revoke actually runs.
321
+ await waitFor(() => expect(revoke).toHaveBeenCalledTimes(1))
322
+ } finally {
323
+ trigger.mockRestore()
324
+ setTimeoutSpy.mockRestore()
325
+ }
326
+ })
327
+ })
@@ -0,0 +1,144 @@
1
+ import { useCallback, useState, type ComponentType } from 'react'
2
+ import {
3
+ CircularProgress,
4
+ IconButton,
5
+ ListItemIcon,
6
+ ListItemText,
7
+ Menu,
8
+ MenuItem,
9
+ type SvgIconProps,
10
+ } from '@mui/material'
11
+ import DownloadIcon from '@mui/icons-material/FileDownload'
12
+ import { Tooltip } from '../../../components'
13
+ import { triggerLinkDownload } from './exports'
14
+ import type { DownloadItem } from './types'
15
+ import { DEFAULT_DOWNLOAD_LABELS, type DownloadLabels } from './labels'
16
+
17
+ export interface DownloadProps {
18
+ items: readonly DownloadItem[]
19
+ labels?: Partial<DownloadLabels>
20
+ icon?: ComponentType<SvgIconProps>
21
+ iconProps?: SvgIconProps
22
+ /**
23
+ * Fires when an item's `resolve()` rejects (or any step in the click-through
24
+ * path throws). The error is normalised to a real `Error` first. Surface
25
+ * to your telemetry / toast layer here; the trigger already shows a visual
26
+ * error indicator independently.
27
+ */
28
+ onError?: (err: Error) => void
29
+ }
30
+
31
+ export function Download({
32
+ items,
33
+ labels,
34
+ icon: Icon = DownloadIcon,
35
+ iconProps,
36
+ onError,
37
+ }: DownloadProps) {
38
+ const _labels = { ...DEFAULT_DOWNLOAD_LABELS, ...labels }
39
+ const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null)
40
+ const [isDownloading, setIsDownloading] = useState(false)
41
+ const [error, setError] = useState<Error | null>(null)
42
+
43
+ const open = useCallback((e: React.MouseEvent<HTMLButtonElement>) => {
44
+ // Clear any prior error when the user re-engages the menu — the next
45
+ // attempt is a fresh interaction, not a continuation of the failed one.
46
+ setError(null)
47
+ setAnchorEl(e.currentTarget)
48
+ }, [])
49
+ const close = useCallback(() => setAnchorEl(null), [])
50
+
51
+ const onSelect = useCallback(
52
+ (item: DownloadItem) => {
53
+ // Single-flight: ignore re-entry while a download is already in flight.
54
+ // The trigger is `disabled` and the menu is closed during that window,
55
+ // so this guard mostly defends against future call sites that bypass
56
+ // the menu (e.g. keyboard shortcuts).
57
+ if (item.disabled || isDownloading) return
58
+ close()
59
+ setIsDownloading(true)
60
+ setError(null)
61
+ item
62
+ .resolve()
63
+ .then(({ url, filename, revoke }) => {
64
+ // `item.filename` overrides what `resolve()` returns — lets consumers
65
+ // template the filename with widget metadata declaratively.
66
+ triggerLinkDownload({ url, filename: item.filename ?? filename })
67
+ // Defer revoke past the current task so Safari / Firefox-on-slow-disk
68
+ // can dispatch the download before the blob URL is invalidated.
69
+ // Synchronous revoke here can silently cancel the download.
70
+ if (revoke) setTimeout(revoke, 0)
71
+ })
72
+ .catch((err: unknown) => {
73
+ const e = err instanceof Error ? err : new Error(String(err))
74
+ setError(e)
75
+ onError?.(e)
76
+ })
77
+ .finally(() => {
78
+ setIsDownloading(false)
79
+ })
80
+ },
81
+ [close, isDownloading, onError],
82
+ )
83
+
84
+ const triggerLabel = isDownloading
85
+ ? _labels.loading
86
+ : error
87
+ ? _labels.error
88
+ : _labels.trigger
89
+ const triggerActive = Boolean(anchorEl) || isDownloading
90
+
91
+ return (
92
+ <>
93
+ <Tooltip title={triggerLabel}>
94
+ <IconButton
95
+ size='small'
96
+ aria-label={triggerLabel}
97
+ aria-haspopup='true'
98
+ aria-expanded={anchorEl ? 'true' : undefined}
99
+ aria-busy={isDownloading || undefined}
100
+ data-error={error ? 'true' : undefined}
101
+ disabled={isDownloading || items.length === 0}
102
+ onClick={open}
103
+ className={triggerActive ? 'active' : undefined}
104
+ >
105
+ {isDownloading ? (
106
+ <CircularProgress size={18} color='inherit' />
107
+ ) : (
108
+ <Icon fontSize='small' {...iconProps} />
109
+ )}
110
+ </IconButton>
111
+ </Tooltip>
112
+ <Menu
113
+ anchorEl={anchorEl}
114
+ open={Boolean(anchorEl)}
115
+ onClose={close}
116
+ variant='menu'
117
+ elevation={8}
118
+ anchorOrigin={{
119
+ vertical: 'bottom',
120
+ horizontal: 'right',
121
+ }}
122
+ transformOrigin={{
123
+ vertical: 'top',
124
+ horizontal: 'right',
125
+ }}
126
+ >
127
+ {items.map((item) => (
128
+ <MenuItem
129
+ key={item.id}
130
+ disabled={item.disabled}
131
+ onClick={() => void onSelect(item)}
132
+ >
133
+ {item.icon && (
134
+ <ListItemIcon sx={{ color: 'text.primary' }}>
135
+ {item.icon}
136
+ </ListItemIcon>
137
+ )}
138
+ <ListItemText>{item.label}</ListItemText>
139
+ </MenuItem>
140
+ ))}
141
+ </Menu>
142
+ </>
143
+ )
144
+ }
@@ -0,0 +1,198 @@
1
+ import { describe, it, expect, beforeEach, vi } from 'vitest'
2
+ import { render } from '@testing-library/react'
3
+
4
+ // Hoisted mock for html2canvas — tests reach into `mockToBlob` to switch
5
+ // between "blob produced" and "toBlob returned null" without re-mocking.
6
+ type ToBlobCb = (blob: Blob | null) => void
7
+ const mockToBlob = vi.fn((cb: ToBlobCb) => cb(new Blob(['png-bytes'])))
8
+
9
+ vi.mock('html2canvas', () => ({
10
+ default: () => Promise.resolve({ toBlob: mockToBlob }),
11
+ }))
12
+
13
+ import {
14
+ toCsvString,
15
+ downloadToCSV,
16
+ triggerLinkDownload,
17
+ downloadDOMToPNG,
18
+ sanitizeFilename,
19
+ } from './exports'
20
+ import { CSVIcon, PNGIcon } from './icons'
21
+
22
+ let csvText = ''
23
+
24
+ let revokeSpy: ReturnType<typeof vi.spyOn>
25
+
26
+ beforeEach(() => {
27
+ csvText = ''
28
+ mockToBlob.mockReset()
29
+ mockToBlob.mockImplementation((cb: ToBlobCb) => cb(new Blob(['png-bytes'])))
30
+ vi.spyOn(URL, 'createObjectURL').mockReturnValue('blob:mock')
31
+ revokeSpy = vi
32
+ .spyOn(URL, 'revokeObjectURL')
33
+ .mockImplementation(() => undefined)
34
+ const RealBlob = global.Blob
35
+ vi.stubGlobal(
36
+ 'Blob',
37
+ class extends RealBlob {
38
+ constructor(parts: BlobPart[], opts?: BlobPropertyBag) {
39
+ csvText = typeof parts[0] === 'string' ? parts[0] : ''
40
+ super(parts, opts)
41
+ }
42
+ },
43
+ )
44
+ })
45
+
46
+ describe('toCsvString', () => {
47
+ it('joins rows with newlines and cells with commas', () => {
48
+ expect(
49
+ toCsvString([
50
+ ['name', 'value'],
51
+ ['a', 1],
52
+ ['b', 2],
53
+ ]),
54
+ ).toBe('name,value\na,1\nb,2')
55
+ })
56
+
57
+ it('emits blank cells for null and undefined', () => {
58
+ expect(toCsvString([[null, undefined, 'x']])).toBe(',,x')
59
+ })
60
+
61
+ it('stringifies numbers and booleans', () => {
62
+ expect(toCsvString([[1, false, true]])).toBe('1,false,true')
63
+ })
64
+
65
+ it('JSON.stringifies objects and arrays', () => {
66
+ expect(toCsvString([[{ a: 1 }, [1, 2]]])).toBe('"{""a"":1}","[1,2]"')
67
+ })
68
+
69
+ it('quotes cells containing comma / quote / newline', () => {
70
+ expect(toCsvString([['a,b', 'he said "hi"', 'line1\nline2']])).toBe(
71
+ '"a,b","he said ""hi""","line1\nline2"',
72
+ )
73
+ })
74
+
75
+ describe('formula injection guard', () => {
76
+ it.each([
77
+ [
78
+ '=',
79
+ '=HYPERLINK("https://attacker.example")',
80
+ '\'=HYPERLINK("https://attacker.example")',
81
+ ],
82
+ ['+', '+1+1', "'+1+1"],
83
+ ['-', '-2+1', "'-2+1"],
84
+ ['@', '@SUM(A1:A2)', "'@SUM(A1:A2)"],
85
+ ['\\t', '\tleading-tab', "'\tleading-tab"],
86
+ ['\\r', '\rleading-cr', "'\rleading-cr"],
87
+ ])(
88
+ 'prefixes cells starting with %s with a single quote',
89
+ (_label, raw, expectedRaw) => {
90
+ // The escape step also quotes if the value contains comma/quote/newline,
91
+ // so we re-derive the exact wire representation from the raw expected.
92
+ const expected = /[",\n\r]/.test(expectedRaw)
93
+ ? `"${expectedRaw.replace(/"/g, '""')}"`
94
+ : expectedRaw
95
+ expect(toCsvString([[raw]])).toBe(expected)
96
+ },
97
+ )
98
+
99
+ it('does not touch values where the dangerous char is not leading', () => {
100
+ expect(toCsvString([['a=b']])).toBe('a=b')
101
+ expect(toCsvString([['1+2']])).toBe('1+2')
102
+ })
103
+ })
104
+ })
105
+
106
+ describe('downloadToCSV', () => {
107
+ it('serialises rows then wraps them in a URL handle with a working revoke', () => {
108
+ const handle = downloadToCSV([['x', 1]])
109
+ expect(handle.url).toBe('blob:mock')
110
+ expect(csvText).toBe('x,1')
111
+ handle.revoke?.()
112
+ expect(revokeSpy).toHaveBeenCalledWith('blob:mock')
113
+ })
114
+ })
115
+
116
+ describe('triggerLinkDownload', () => {
117
+ it('synthesises an anchor, clicks it, then removes it', () => {
118
+ const appendSpy = vi.spyOn(document.body, 'appendChild')
119
+ triggerLinkDownload({ url: 'blob:x', filename: 'demo.csv' })
120
+ expect(appendSpy).toHaveBeenCalled()
121
+ const anchor = appendSpy.mock.calls[0]?.[0] as HTMLAnchorElement
122
+ expect(anchor.href).toBe('blob:x')
123
+ expect(anchor.download).toBe('demo.csv')
124
+ expect(anchor.isConnected).toBe(false)
125
+ })
126
+
127
+ it('applies sanitizeFilename to the anchor download attribute', () => {
128
+ const appendSpy = vi.spyOn(document.body, 'appendChild')
129
+ const before = appendSpy.mock.calls.length
130
+ triggerLinkDownload({
131
+ url: 'blob:y',
132
+ filename: '..\\evil\r\nname.csv',
133
+ })
134
+ const anchor = appendSpy.mock.calls[before]?.[0] as HTMLAnchorElement
135
+ expect(anchor.download).toBe('.._evil__name.csv')
136
+ })
137
+ })
138
+
139
+ describe('sanitizeFilename', () => {
140
+ it('replaces path separators, NUL, and ASCII control chars with underscore', () => {
141
+ expect(sanitizeFilename('a/b\\c\x00\x07d\r\ne.csv')).toBe('a_b_c__d__e.csv')
142
+ })
143
+
144
+ it('clamps long filenames to 200 chars', () => {
145
+ const long = 'x'.repeat(500) + '.csv'
146
+ const out = sanitizeFilename(long)
147
+ expect(out.length).toBe(200)
148
+ expect(out.startsWith('x')).toBe(true)
149
+ })
150
+
151
+ it('falls back to "download" for empty or whitespace-only input', () => {
152
+ expect(sanitizeFilename('')).toBe('download')
153
+ // Whitespace stays — sanitizer only handles control bytes — but a leading
154
+ // NUL run that empties the string still falls back.
155
+ expect(sanitizeFilename('\x00\x00\x00')).toBe('___')
156
+ })
157
+ })
158
+
159
+ describe('downloadDOMToPNG', () => {
160
+ it('rasterises the element via html2canvas and resolves with a handle', async () => {
161
+ const handle = await downloadDOMToPNG({
162
+ element: document.createElement('div'),
163
+ })
164
+ expect(handle.url).toBe('blob:mock')
165
+ handle.revoke?.()
166
+ expect(revokeSpy).toHaveBeenCalledWith('blob:mock')
167
+ })
168
+
169
+ it('rejects when canvas.toBlob returns null', async () => {
170
+ mockToBlob.mockImplementation((cb: ToBlobCb) => cb(null))
171
+ await expect(
172
+ downloadDOMToPNG({ element: document.createElement('div') }),
173
+ ).rejects.toThrow(/toBlob/)
174
+ })
175
+
176
+ it('forwards pixelRatio and backgroundColor overrides', async () => {
177
+ // We can't easily inspect the call args without re-hoisting the mock,
178
+ // so just guard the happy path still works with overrides supplied.
179
+ const handle = await downloadDOMToPNG({
180
+ element: document.createElement('div'),
181
+ pixelRatio: 4,
182
+ backgroundColor: '#fff',
183
+ })
184
+ expect(handle.url).toBe('blob:mock')
185
+ })
186
+ })
187
+
188
+ describe('download icons', () => {
189
+ it('CSVIcon renders an SVG element', () => {
190
+ const { container } = render(<CSVIcon fontSize='small' />)
191
+ expect(container.querySelector('svg')).not.toBeNull()
192
+ })
193
+
194
+ it('PNGIcon renders an SVG element', () => {
195
+ const { container } = render(<PNGIcon fontSize='small' />)
196
+ expect(container.querySelector('svg')).not.toBeNull()
197
+ })
198
+ })