@carto/ps-react-ui 4.7.0 → 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 (578) 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/actions.js +688 -668
  247. package/dist/widgets/actions.js.map +1 -1
  248. package/dist/widgets/bar.js +14 -13
  249. package/dist/widgets/bar.js.map +1 -1
  250. package/dist/widgets/histogram.js +38 -37
  251. package/dist/widgets/histogram.js.map +1 -1
  252. package/dist/widgets/pie.js +19 -18
  253. package/dist/widgets/pie.js.map +1 -1
  254. package/dist/widgets/scatterplot.js +8 -7
  255. package/dist/widgets/scatterplot.js.map +1 -1
  256. package/dist/widgets/timeseries.js +11 -10
  257. package/dist/widgets/timeseries.js.map +1 -1
  258. package/dist/widgets/utils.js +8 -7
  259. package/dist/widgets/utils.js.map +1 -1
  260. package/dist/widgets-v2/actions.js +43 -0
  261. package/dist/widgets-v2/actions.js.map +1 -0
  262. package/dist/widgets-v2/bar.js +327 -0
  263. package/dist/widgets-v2/bar.js.map +1 -0
  264. package/dist/widgets-v2/category.js +104 -0
  265. package/dist/widgets-v2/category.js.map +1 -0
  266. package/dist/widgets-v2/echart.js +57 -0
  267. package/dist/widgets-v2/echart.js.map +1 -0
  268. package/dist/widgets-v2/formula.js +74 -0
  269. package/dist/widgets-v2/formula.js.map +1 -0
  270. package/dist/widgets-v2/histogram.js +350 -0
  271. package/dist/widgets-v2/histogram.js.map +1 -0
  272. package/dist/widgets-v2/markdown.js +68 -0
  273. package/dist/widgets-v2/markdown.js.map +1 -0
  274. package/dist/widgets-v2/pie.js +381 -0
  275. package/dist/widgets-v2/pie.js.map +1 -0
  276. package/dist/widgets-v2/range.js +52 -0
  277. package/dist/widgets-v2/range.js.map +1 -0
  278. package/dist/widgets-v2/scatterplot.js +405 -0
  279. package/dist/widgets-v2/scatterplot.js.map +1 -0
  280. package/dist/widgets-v2/spread.js +72 -0
  281. package/dist/widgets-v2/spread.js.map +1 -0
  282. package/dist/widgets-v2/stores.js +42 -0
  283. package/dist/widgets-v2/stores.js.map +1 -0
  284. package/dist/widgets-v2/table.js +78 -0
  285. package/dist/widgets-v2/table.js.map +1 -0
  286. package/dist/widgets-v2/timeseries.js +352 -0
  287. package/dist/widgets-v2/timeseries.js.map +1 -0
  288. package/dist/widgets-v2/utils.js +7 -0
  289. package/dist/widgets-v2/utils.js.map +1 -0
  290. package/dist/widgets-v2.js +953 -0
  291. package/dist/widgets-v2.js.map +1 -0
  292. package/package.json +73 -5
  293. package/src/components/lasso-tool/chip.test.tsx +176 -0
  294. package/src/components/lasso-tool/lasso-tool-inline.test.tsx +171 -0
  295. package/src/components/lasso-tool/lasso-tool.test.tsx +198 -0
  296. package/src/components/list-data/list-data.test.tsx +73 -0
  297. package/src/components/no-data-alert/no-data-alert.test.tsx +38 -0
  298. package/src/components/responsive-drawer/responsive-drawer.test.tsx +68 -0
  299. package/src/widgets/actions/brush-toggle/brush-overlay.test.tsx +465 -0
  300. package/src/widgets/actions/brush-toggle/brush-overlay.tsx +24 -2
  301. package/src/widgets/actions/brush-toggle/brush-toggle.test.tsx +208 -0
  302. package/src/widgets/actions/change-column/change-column-dnd.test.tsx +193 -0
  303. package/src/widgets/actions/change-column/sortable-column-item.test.tsx +124 -0
  304. package/src/widgets/actions/zoom-toggle/zoom-toggle.test.tsx +322 -0
  305. package/src/widgets/category/components/category-rows.test.tsx +213 -0
  306. package/src/widgets/echart/utils.test.ts +277 -0
  307. package/src/widgets/formula/config.test.ts +37 -0
  308. package/src/widgets/histogram/config.ts +1 -3
  309. package/src/widgets/range/components/range-item.test.tsx +243 -0
  310. package/src/widgets/stores/widget-store-branches.test.ts +275 -0
  311. package/src/widgets/table/config.test.ts +65 -0
  312. package/src/widgets/utils/chart-config/option-builders.test.ts +188 -0
  313. package/src/widgets-v2/PERFORMANCE.md +189 -0
  314. package/src/widgets-v2/actions/brush-toggle/brush-toggle.test.tsx +180 -0
  315. package/src/widgets-v2/actions/brush-toggle/brush-toggle.tsx +154 -0
  316. package/src/widgets-v2/actions/brush-toggle/index.ts +3 -0
  317. package/src/widgets-v2/actions/brush-toggle/labels.ts +9 -0
  318. package/src/widgets-v2/actions/brush-toggle/style.ts +11 -0
  319. package/src/widgets-v2/actions/brush-toggle/transforms.test.ts +47 -0
  320. package/src/widgets-v2/actions/brush-toggle/transforms.ts +31 -0
  321. package/src/widgets-v2/actions/change-column/change-column-icon.tsx +14 -0
  322. package/src/widgets-v2/actions/change-column/change-column.test.tsx +59 -0
  323. package/src/widgets-v2/actions/change-column/change-column.tsx +180 -0
  324. package/src/widgets-v2/actions/change-column/index.ts +7 -0
  325. package/src/widgets-v2/actions/change-column/labels.ts +9 -0
  326. package/src/widgets-v2/actions/change-column/sortable-column-item.tsx +56 -0
  327. package/src/widgets-v2/actions/change-column/style.ts +32 -0
  328. package/src/widgets-v2/actions/change-column/types.ts +11 -0
  329. package/src/widgets-v2/actions/download/download.test.tsx +327 -0
  330. package/src/widgets-v2/actions/download/download.tsx +144 -0
  331. package/src/widgets-v2/actions/download/exports.test.tsx +198 -0
  332. package/src/widgets-v2/actions/download/exports.ts +115 -0
  333. package/src/widgets-v2/actions/download/icons.tsx +26 -0
  334. package/src/widgets-v2/actions/download/index.ts +13 -0
  335. package/src/widgets-v2/actions/download/labels.ts +16 -0
  336. package/src/widgets-v2/actions/download/png-item.test.tsx +72 -0
  337. package/src/widgets-v2/actions/download/png-item.tsx +52 -0
  338. package/src/widgets-v2/actions/download/style.ts +3 -0
  339. package/src/widgets-v2/actions/download/types.ts +32 -0
  340. package/src/widgets-v2/actions/fullscreen/fullscreen.test.tsx +150 -0
  341. package/src/widgets-v2/actions/fullscreen/fullscreen.tsx +230 -0
  342. package/src/widgets-v2/actions/fullscreen/index.ts +7 -0
  343. package/src/widgets-v2/actions/fullscreen/labels.ts +9 -0
  344. package/src/widgets-v2/actions/fullscreen/style.ts +59 -0
  345. package/src/widgets-v2/actions/fullscreen/types.ts +15 -0
  346. package/src/widgets-v2/actions/index.ts +82 -0
  347. package/src/widgets-v2/actions/lock-selection/index.ts +10 -0
  348. package/src/widgets-v2/actions/lock-selection/labels.ts +11 -0
  349. package/src/widgets-v2/actions/lock-selection/lock-selection.test.tsx +187 -0
  350. package/src/widgets-v2/actions/lock-selection/lock-selection.tsx +130 -0
  351. package/src/widgets-v2/actions/lock-selection/style.ts +11 -0
  352. package/src/widgets-v2/actions/lock-selection/transforms.ts +27 -0
  353. package/src/widgets-v2/actions/relative-data/index.ts +3 -0
  354. package/src/widgets-v2/actions/relative-data/labels.ts +9 -0
  355. package/src/widgets-v2/actions/relative-data/relative-data.test.tsx +71 -0
  356. package/src/widgets-v2/actions/relative-data/relative-data.tsx +107 -0
  357. package/src/widgets-v2/actions/relative-data/style.ts +11 -0
  358. package/src/widgets-v2/actions/relative-data/transforms.test.ts +151 -0
  359. package/src/widgets-v2/actions/relative-data/transforms.ts +70 -0
  360. package/src/widgets-v2/actions/searcher/filter.ts +28 -0
  361. package/src/widgets-v2/actions/searcher/index.ts +8 -0
  362. package/src/widgets-v2/actions/searcher/labels.ts +13 -0
  363. package/src/widgets-v2/actions/searcher/searcher-toggle.tsx +91 -0
  364. package/src/widgets-v2/actions/searcher/searcher.test.tsx +92 -0
  365. package/src/widgets-v2/actions/searcher/searcher.tsx +112 -0
  366. package/src/widgets-v2/actions/searcher/style.ts +15 -0
  367. package/src/widgets-v2/actions/stack-toggle/index.ts +3 -0
  368. package/src/widgets-v2/actions/stack-toggle/labels.ts +9 -0
  369. package/src/widgets-v2/actions/stack-toggle/stack-toggle.test.tsx +61 -0
  370. package/src/widgets-v2/actions/stack-toggle/stack-toggle.tsx +54 -0
  371. package/src/widgets-v2/actions/stack-toggle/style.ts +11 -0
  372. package/src/widgets-v2/actions/stack-toggle/transforms.test.ts +43 -0
  373. package/src/widgets-v2/actions/stack-toggle/transforms.ts +25 -0
  374. package/src/widgets-v2/actions/zoom-toggle/index.ts +9 -0
  375. package/src/widgets-v2/actions/zoom-toggle/labels.ts +9 -0
  376. package/src/widgets-v2/actions/zoom-toggle/style.ts +11 -0
  377. package/src/widgets-v2/actions/zoom-toggle/transforms.test.ts +148 -0
  378. package/src/widgets-v2/actions/zoom-toggle/transforms.ts +171 -0
  379. package/src/widgets-v2/actions/zoom-toggle/zoom-toggle.test.tsx +107 -0
  380. package/src/widgets-v2/actions/zoom-toggle/zoom-toggle.tsx +106 -0
  381. package/src/widgets-v2/bar/download.test.tsx +91 -0
  382. package/src/widgets-v2/bar/download.tsx +66 -0
  383. package/src/widgets-v2/bar/index.ts +10 -0
  384. package/src/widgets-v2/bar/options.test.ts +317 -0
  385. package/src/widgets-v2/bar/options.ts +326 -0
  386. package/src/widgets-v2/bar/skeleton.test.tsx +19 -0
  387. package/src/widgets-v2/bar/skeleton.tsx +69 -0
  388. package/src/widgets-v2/bar/types.ts +46 -0
  389. package/src/widgets-v2/category/category-ui.test.tsx +746 -0
  390. package/src/widgets-v2/category/category-ui.tsx +389 -0
  391. package/src/widgets-v2/category/category.relative-data.test.tsx +107 -0
  392. package/src/widgets-v2/category/category.stack-toggle.test.tsx +85 -0
  393. package/src/widgets-v2/category/category.test.tsx +305 -0
  394. package/src/widgets-v2/category/category.tsx +121 -0
  395. package/src/widgets-v2/category/components/category-bar-stacked.test.tsx +121 -0
  396. package/src/widgets-v2/category/components/category-bar-stacked.tsx +73 -0
  397. package/src/widgets-v2/category/components/category-bar.test.tsx +64 -0
  398. package/src/widgets-v2/category/components/category-bar.tsx +49 -0
  399. package/src/widgets-v2/category/components/category-legend.test.tsx +51 -0
  400. package/src/widgets-v2/category/components/category-legend.tsx +39 -0
  401. package/src/widgets-v2/category/components/category-row-multi.tsx +86 -0
  402. package/src/widgets-v2/category/components/category-row-other.test.tsx +28 -0
  403. package/src/widgets-v2/category/components/category-row-other.tsx +33 -0
  404. package/src/widgets-v2/category/components/category-row-single.tsx +76 -0
  405. package/src/widgets-v2/category/components/category-row-stacked.test.tsx +244 -0
  406. package/src/widgets-v2/category/components/category-row-stacked.tsx +99 -0
  407. package/src/widgets-v2/category/download.test.ts +71 -0
  408. package/src/widgets-v2/category/download.ts +54 -0
  409. package/src/widgets-v2/category/index.ts +32 -0
  410. package/src/widgets-v2/category/skeleton.test.tsx +26 -0
  411. package/src/widgets-v2/category/skeleton.tsx +74 -0
  412. package/src/widgets-v2/category/style.ts +290 -0
  413. package/src/widgets-v2/category/types.ts +54 -0
  414. package/src/widgets-v2/echart/echart-ui.test.tsx +232 -0
  415. package/src/widgets-v2/echart/echart-ui.tsx +184 -0
  416. package/src/widgets-v2/echart/echart.test.tsx +229 -0
  417. package/src/widgets-v2/echart/echart.tsx +199 -0
  418. package/src/widgets-v2/echart/index.ts +22 -0
  419. package/src/widgets-v2/echart/shared-resize-observer.test.ts +91 -0
  420. package/src/widgets-v2/echart/shared-resize-observer.ts +56 -0
  421. package/src/widgets-v2/echart/style.ts +8 -0
  422. package/src/widgets-v2/echart/use-chart-selection.test.tsx +118 -0
  423. package/src/widgets-v2/echart/use-chart-selection.ts +115 -0
  424. package/src/widgets-v2/formula/delta.tsx +61 -0
  425. package/src/widgets-v2/formula/download.test.tsx +65 -0
  426. package/src/widgets-v2/formula/download.tsx +69 -0
  427. package/src/widgets-v2/formula/formula-ui.test.tsx +91 -0
  428. package/src/widgets-v2/formula/formula-ui.tsx +66 -0
  429. package/src/widgets-v2/formula/formula.test.tsx +50 -0
  430. package/src/widgets-v2/formula/formula.tsx +34 -0
  431. package/src/widgets-v2/formula/index.ts +17 -0
  432. package/src/widgets-v2/formula/note.tsx +25 -0
  433. package/src/widgets-v2/formula/prefix.tsx +25 -0
  434. package/src/widgets-v2/formula/series.tsx +67 -0
  435. package/src/widgets-v2/formula/skeleton.test.tsx +21 -0
  436. package/src/widgets-v2/formula/skeleton.tsx +27 -0
  437. package/src/widgets-v2/formula/style.ts +31 -0
  438. package/src/widgets-v2/formula/subcomponents.test.tsx +107 -0
  439. package/src/widgets-v2/formula/suffix.tsx +25 -0
  440. package/src/widgets-v2/formula/types.ts +44 -0
  441. package/src/widgets-v2/formula/value.tsx +31 -0
  442. package/src/widgets-v2/histogram/download.test.ts +94 -0
  443. package/src/widgets-v2/histogram/download.ts +60 -0
  444. package/src/widgets-v2/histogram/index.ts +10 -0
  445. package/src/widgets-v2/histogram/options.test.ts +304 -0
  446. package/src/widgets-v2/histogram/options.ts +337 -0
  447. package/src/widgets-v2/histogram/skeleton.test.tsx +16 -0
  448. package/src/widgets-v2/histogram/skeleton.tsx +70 -0
  449. package/src/widgets-v2/histogram/transforms.test.ts +46 -0
  450. package/src/widgets-v2/histogram/transforms.ts +30 -0
  451. package/src/widgets-v2/histogram/types.ts +51 -0
  452. package/src/widgets-v2/index.ts +201 -0
  453. package/src/widgets-v2/markdown/download.test.ts +66 -0
  454. package/src/widgets-v2/markdown/download.ts +53 -0
  455. package/src/widgets-v2/markdown/index.ts +6 -0
  456. package/src/widgets-v2/markdown/markdown-content.test.tsx +155 -0
  457. package/src/widgets-v2/markdown/markdown-content.tsx +72 -0
  458. package/src/widgets-v2/markdown/markdown-ui.test.tsx +75 -0
  459. package/src/widgets-v2/markdown/markdown-ui.tsx +55 -0
  460. package/src/widgets-v2/markdown/markdown.test.tsx +39 -0
  461. package/src/widgets-v2/markdown/markdown.tsx +17 -0
  462. package/src/widgets-v2/markdown/skeleton.test.tsx +15 -0
  463. package/src/widgets-v2/markdown/skeleton.tsx +32 -0
  464. package/src/widgets-v2/markdown/style.ts +53 -0
  465. package/src/widgets-v2/markdown/types.ts +4 -0
  466. package/src/widgets-v2/note/labels.ts +9 -0
  467. package/src/widgets-v2/note/style.ts +26 -0
  468. package/src/widgets-v2/note/widget-note.test.tsx +158 -0
  469. package/src/widgets-v2/note/widget-note.tsx +172 -0
  470. package/src/widgets-v2/pie/download.test.ts +78 -0
  471. package/src/widgets-v2/pie/download.ts +55 -0
  472. package/src/widgets-v2/pie/index.ts +10 -0
  473. package/src/widgets-v2/pie/options.test.ts +585 -0
  474. package/src/widgets-v2/pie/options.ts +509 -0
  475. package/src/widgets-v2/pie/skeleton.test.tsx +17 -0
  476. package/src/widgets-v2/pie/skeleton.tsx +32 -0
  477. package/src/widgets-v2/pie/types.ts +55 -0
  478. package/src/widgets-v2/provider/widget-provider.test.tsx +119 -0
  479. package/src/widgets-v2/provider/widget-provider.tsx +111 -0
  480. package/src/widgets-v2/range/index.ts +4 -0
  481. package/src/widgets-v2/range/range-ui.test.tsx +130 -0
  482. package/src/widgets-v2/range/range-ui.tsx +211 -0
  483. package/src/widgets-v2/range/range.test.tsx +68 -0
  484. package/src/widgets-v2/range/range.tsx +46 -0
  485. package/src/widgets-v2/range/skeleton.test.tsx +17 -0
  486. package/src/widgets-v2/range/skeleton.tsx +47 -0
  487. package/src/widgets-v2/range/style.ts +41 -0
  488. package/src/widgets-v2/range/types.ts +37 -0
  489. package/src/widgets-v2/scatterplot/download.test.ts +71 -0
  490. package/src/widgets-v2/scatterplot/download.ts +54 -0
  491. package/src/widgets-v2/scatterplot/index.ts +11 -0
  492. package/src/widgets-v2/scatterplot/options.test.ts +399 -0
  493. package/src/widgets-v2/scatterplot/options.ts +421 -0
  494. package/src/widgets-v2/scatterplot/skeleton.test.tsx +17 -0
  495. package/src/widgets-v2/scatterplot/skeleton.tsx +84 -0
  496. package/src/widgets-v2/scatterplot/transforms.test.ts +97 -0
  497. package/src/widgets-v2/scatterplot/transforms.ts +38 -0
  498. package/src/widgets-v2/scatterplot/types.ts +55 -0
  499. package/src/widgets-v2/selection-summary/labels.ts +11 -0
  500. package/src/widgets-v2/selection-summary/selection-summary.test.tsx +53 -0
  501. package/src/widgets-v2/selection-summary/selection-summary.tsx +62 -0
  502. package/src/widgets-v2/selection-summary/style.ts +23 -0
  503. package/src/widgets-v2/spread/download.test.ts +64 -0
  504. package/src/widgets-v2/spread/download.ts +59 -0
  505. package/src/widgets-v2/spread/index.ts +6 -0
  506. package/src/widgets-v2/spread/separator.tsx +11 -0
  507. package/src/widgets-v2/spread/skeleton.test.tsx +17 -0
  508. package/src/widgets-v2/spread/skeleton.tsx +38 -0
  509. package/src/widgets-v2/spread/spread-ui.test.tsx +108 -0
  510. package/src/widgets-v2/spread/spread-ui.tsx +52 -0
  511. package/src/widgets-v2/spread/spread.test.tsx +50 -0
  512. package/src/widgets-v2/spread/spread.tsx +31 -0
  513. package/src/widgets-v2/spread/types.ts +27 -0
  514. package/src/widgets-v2/state/labels.test.ts +33 -0
  515. package/src/widgets-v2/state/labels.ts +20 -0
  516. package/src/widgets-v2/state/style.ts +25 -0
  517. package/src/widgets-v2/state/widget-state.test.tsx +294 -0
  518. package/src/widgets-v2/state/widget-state.tsx +184 -0
  519. package/src/widgets-v2/stores/index.ts +49 -0
  520. package/src/widgets-v2/stores/pipeline-middleware.test.ts +187 -0
  521. package/src/widgets-v2/stores/pipeline-middleware.ts +91 -0
  522. package/src/widgets-v2/stores/transforms.test.ts +162 -0
  523. package/src/widgets-v2/stores/transforms.ts +70 -0
  524. package/src/widgets-v2/stores/types.ts +64 -0
  525. package/src/widgets-v2/stores/use-echart-instance.test.tsx +91 -0
  526. package/src/widgets-v2/stores/use-echart-instance.ts +29 -0
  527. package/src/widgets-v2/stores/use-transform-enabled.test.tsx +127 -0
  528. package/src/widgets-v2/stores/use-transform-enabled.ts +25 -0
  529. package/src/widgets-v2/stores/use-transform.test.tsx +262 -0
  530. package/src/widgets-v2/stores/use-transform.ts +158 -0
  531. package/src/widgets-v2/stores/widget-context.test.tsx +58 -0
  532. package/src/widgets-v2/stores/widget-context.ts +15 -0
  533. package/src/widgets-v2/stores/widget-store-registry.test.ts +292 -0
  534. package/src/widgets-v2/stores/widget-store-registry.ts +248 -0
  535. package/src/widgets-v2/subheader/style.ts +12 -0
  536. package/src/widgets-v2/subheader/subheader.test.tsx +30 -0
  537. package/src/widgets-v2/subheader/subheader.tsx +16 -0
  538. package/src/widgets-v2/table/download.test.ts +75 -0
  539. package/src/widgets-v2/table/download.ts +47 -0
  540. package/src/widgets-v2/table/helpers.test.ts +214 -0
  541. package/src/widgets-v2/table/helpers.ts +136 -0
  542. package/src/widgets-v2/table/index.ts +23 -0
  543. package/src/widgets-v2/table/labels.tsx +41 -0
  544. package/src/widgets-v2/table/skeleton.test.tsx +26 -0
  545. package/src/widgets-v2/table/skeleton.tsx +65 -0
  546. package/src/widgets-v2/table/style.ts +46 -0
  547. package/src/widgets-v2/table/table-ui.test.tsx +200 -0
  548. package/src/widgets-v2/table/table-ui.tsx +331 -0
  549. package/src/widgets-v2/table/table.test.tsx +119 -0
  550. package/src/widgets-v2/table/table.tsx +174 -0
  551. package/src/widgets-v2/table/types.ts +44 -0
  552. package/src/widgets-v2/test-utils.ts +107 -0
  553. package/src/widgets-v2/timeseries/download.test.ts +95 -0
  554. package/src/widgets-v2/timeseries/download.ts +86 -0
  555. package/src/widgets-v2/timeseries/index.ts +10 -0
  556. package/src/widgets-v2/timeseries/options.test.ts +379 -0
  557. package/src/widgets-v2/timeseries/options.ts +341 -0
  558. package/src/widgets-v2/timeseries/skeleton.test.tsx +13 -0
  559. package/src/widgets-v2/timeseries/skeleton.tsx +76 -0
  560. package/src/widgets-v2/timeseries/types.ts +61 -0
  561. package/src/widgets-v2/toolbox/labels.ts +9 -0
  562. package/src/widgets-v2/toolbox/style.ts +33 -0
  563. package/src/widgets-v2/toolbox/toolbox.test.tsx +200 -0
  564. package/src/widgets-v2/toolbox/toolbox.tsx +309 -0
  565. package/src/widgets-v2/utils/data-zoom-layout.ts +26 -0
  566. package/src/widgets-v2/utils/index.ts +2 -0
  567. package/src/widgets-v2/utils/merge-options.test.ts +52 -0
  568. package/src/widgets-v2/utils/merge-options.ts +50 -0
  569. package/src/widgets-v2/wrapper/index.ts +14 -0
  570. package/src/widgets-v2/wrapper/labels.ts +11 -0
  571. package/src/widgets-v2/wrapper/style.ts +134 -0
  572. package/src/widgets-v2/wrapper/widget-actions.test.tsx +52 -0
  573. package/src/widgets-v2/wrapper/widget-actions.tsx +43 -0
  574. package/src/widgets-v2/wrapper/widget-content.test.tsx +27 -0
  575. package/src/widgets-v2/wrapper/widget-content.tsx +29 -0
  576. package/src/widgets-v2/wrapper/widget-wrapper.test.tsx +159 -0
  577. package/src/widgets-v2/wrapper/widget-wrapper.tsx +178 -0
  578. package/dist/styles-BYTyKQFP.js.map +0 -1
@@ -0,0 +1,746 @@
1
+ import { describe, it, expect, vi } from 'vitest'
2
+ import { fireEvent, render, screen, within } from '@testing-library/react'
3
+ import { CategoryUI } from './category-ui'
4
+
5
+ describe('<CategoryUI>', () => {
6
+ // ── Rendering basics ────────────────────────────────────────────────
7
+ describe('rendering', () => {
8
+ it('renders one row per item with name + value', () => {
9
+ render(
10
+ <CategoryUI
11
+ data={[
12
+ [
13
+ { name: 'A', value: 10 },
14
+ { name: 'B', value: 20 },
15
+ ],
16
+ ]}
17
+ />,
18
+ )
19
+ expect(screen.getByText('A')).toBeTruthy()
20
+ expect(screen.getByText('B')).toBeTruthy()
21
+ expect(screen.getByText('10')).toBeTruthy()
22
+ expect(screen.getByText('20')).toBeTruthy()
23
+ expect(screen.getAllByRole('button')).toHaveLength(2)
24
+ })
25
+
26
+ it('returns null when data is empty (no rows, no container)', () => {
27
+ const { container } = render(<CategoryUI data={[]} />)
28
+ expect(container.firstChild).toBeNull()
29
+ })
30
+
31
+ it('returns null when all series are empty', () => {
32
+ const { container } = render(<CategoryUI data={[[], [], []]} />)
33
+ expect(container.firstChild).toBeNull()
34
+ })
35
+ })
36
+
37
+ // ── maxItems + "Other" overflow ────────────────────────────────────
38
+ describe('maxItems + Other row', () => {
39
+ const oneToTwelve = () =>
40
+ Array.from({ length: 12 }, (_, i) => ({
41
+ name: `n${i}`,
42
+ value: 12 - i,
43
+ }))
44
+
45
+ it('defaults to 20 → no Other row when data fits', () => {
46
+ render(<CategoryUI data={[oneToTwelve()]} />)
47
+ expect(screen.queryByText(/more/)).toBeNull()
48
+ expect(screen.getAllByRole('button')).toHaveLength(12)
49
+ })
50
+
51
+ it('default caps at 20 when data exceeds it → "Other (X more)" footer', () => {
52
+ const twentyFive = Array.from({ length: 25 }, (_, i) => ({
53
+ name: `n${i}`,
54
+ value: 25 - i,
55
+ }))
56
+ render(<CategoryUI data={[twentyFive]} />)
57
+ expect(screen.getAllByRole('button')).toHaveLength(20)
58
+ expect(screen.getByText('Other')).toBeTruthy()
59
+ expect(screen.getByText('(5 more)')).toBeTruthy()
60
+ })
61
+
62
+ it('maxItems=null → no cap, no scroll (canonical form)', () => {
63
+ // Explicit opt-out from both the default cap AND the searcher's
64
+ // scroll viewport. `null` is required because `undefined` resolves
65
+ // to the 20-row default-parameter value.
66
+ const twentyFive = Array.from({ length: 25 }, (_, i) => ({
67
+ name: `n${i}`,
68
+ value: 25 - i,
69
+ }))
70
+ const { container } = render(
71
+ <CategoryUI data={[twentyFive]} maxItems={null} />,
72
+ )
73
+ expect(screen.queryByText('Other')).toBeNull()
74
+ expect(screen.getAllByRole('button')).toHaveLength(25)
75
+ const root = container.firstChild as HTMLElement
76
+ const list = root.firstChild as HTMLElement
77
+ expect(list.style.maxHeight).toBe('')
78
+ expect(list.style.overflowY).toBe('')
79
+ })
80
+
81
+ it('maxItems=Infinity also yields no cap, no scroll (alias)', () => {
82
+ const twentyFive = Array.from({ length: 25 }, (_, i) => ({
83
+ name: `n${i}`,
84
+ value: 25 - i,
85
+ }))
86
+ render(
87
+ <CategoryUI data={[twentyFive]} maxItems={Number.POSITIVE_INFINITY} />,
88
+ )
89
+ expect(screen.queryByText('Other')).toBeNull()
90
+ expect(screen.getAllByRole('button')).toHaveLength(25)
91
+ })
92
+
93
+ it('slices to maxItems and shows "Other (X more)" footer', () => {
94
+ render(<CategoryUI data={[oneToTwelve()]} maxItems={5} />)
95
+ expect(screen.getAllByRole('button')).toHaveLength(5)
96
+ expect(screen.getByText('Other')).toBeTruthy()
97
+ expect(screen.getByText('(7 more)')).toBeTruthy()
98
+ })
99
+
100
+ it('maxItems=0 is the no-cap-with-scroll sentinel: every row renders inside a fixed scroll viewport', () => {
101
+ // `0` is the composer-driven "disable pagination" sentinel —
102
+ // used e.g. when the SearcherToggle is open so the search filter
103
+ // has the full dataset to chew through. The "Other" footer is
104
+ // suppressed because nothing is hidden, and the list locks to a
105
+ // scrollable viewport so the widget card height stays stable.
106
+ const { container } = render(
107
+ <CategoryUI data={[oneToTwelve()]} maxItems={0} />,
108
+ )
109
+ expect(screen.getAllByRole('button')).toHaveLength(12)
110
+ expect(screen.queryByText('Other')).toBeNull()
111
+ expect(screen.queryByText(/more/)).toBeNull()
112
+ // List has the scroll viewport inline style applied.
113
+ const root = container.firstChild as HTMLElement
114
+ const list = root.firstChild as HTMLElement
115
+ expect(list.style.maxHeight).not.toBe('')
116
+ expect(list.style.overflowY).toBe('auto')
117
+ })
118
+
119
+ // ── Natural-height measurement ────────────────────────────────
120
+ //
121
+ // Scroll mode (`maxItems === 0`) sizes its viewport from the
122
+ // list's `clientHeight` measured on the first render where the
123
+ // list was NOT in scroll mode (i.e., the capped view). jsdom's
124
+ // layout engine reports `clientHeight === 0`, so the tests stub
125
+ // it on `HTMLDivElement.prototype` for the renders they care
126
+ // about.
127
+ describe('scroll mode height (measure-once)', () => {
128
+ const stubClientHeight = (value: number) => {
129
+ Object.defineProperty(HTMLDivElement.prototype, 'clientHeight', {
130
+ configurable: true,
131
+ get: () => value,
132
+ })
133
+ }
134
+ const restoreClientHeight = () => {
135
+ // Delete the stub so other tests aren't affected.
136
+ delete (HTMLDivElement.prototype as { clientHeight?: unknown })
137
+ .clientHeight
138
+ }
139
+
140
+ it('locks scroll viewport to the capped view height after the user toggles into scroll mode', () => {
141
+ stubClientHeight(240)
142
+ try {
143
+ const { rerender, container } = render(
144
+ <CategoryUI data={[oneToTwelve()]} maxItems={5} />,
145
+ )
146
+ // First render: capped, no scroll lock. Effect measures 240.
147
+ const root = container.firstChild as HTMLElement
148
+ const list = root.firstChild as HTMLElement
149
+ expect(list.style.maxHeight).toBe('')
150
+ // Composer flips to scroll mode → viewport uses the measured
151
+ // 240px, NOT the hard-coded fallback.
152
+ rerender(<CategoryUI data={[oneToTwelve()]} maxItems={0} />)
153
+ expect(list.style.maxHeight).toBe('240px')
154
+ expect(list.style.overflowY).toBe('auto')
155
+ } finally {
156
+ restoreClientHeight()
157
+ }
158
+ })
159
+
160
+ it('freezes the lock anchor: subsequent re-measures while in scroll mode are ignored', () => {
161
+ stubClientHeight(180)
162
+ try {
163
+ const { rerender, container } = render(
164
+ <CategoryUI data={[oneToTwelve()]} maxItems={5} />,
165
+ )
166
+ const root = container.firstChild as HTMLElement
167
+ const list = root.firstChild as HTMLElement
168
+ rerender(<CategoryUI data={[oneToTwelve()]} maxItems={0} />)
169
+ expect(list.style.maxHeight).toBe('180px')
170
+ // Change the stub mid-flight. The lock must hold — measurement
171
+ // is one-shot, taken on the first non-scroll render.
172
+ stubClientHeight(999)
173
+ rerender(<CategoryUI data={[oneToTwelve()]} maxItems={0} />)
174
+ expect(list.style.maxHeight).toBe('180px')
175
+ } finally {
176
+ restoreClientHeight()
177
+ }
178
+ })
179
+
180
+ it('falls back to 8 × ROW_HEIGHT_PX[size] when mounted directly into scroll mode (no capped render to measure)', () => {
181
+ // No stub — clientHeight reads 0 in jsdom by default, so the
182
+ // effect's `if (h > 0)` guard skips the setState. The viewport
183
+ // must still have a sane size: 36 × 8 = 288px for small.
184
+ const { container } = render(
185
+ <CategoryUI data={[oneToTwelve()]} maxItems={0} />,
186
+ )
187
+ const root = container.firstChild as HTMLElement
188
+ const list = root.firstChild as HTMLElement
189
+ expect(list.style.maxHeight).toBe('288px')
190
+ expect(list.style.overflowY).toBe('auto')
191
+ })
192
+
193
+ it('uses the same fallback for multi-series widgets (no seriesCount multiplier)', () => {
194
+ // Regression: a 3-series widget mounted directly into scroll
195
+ // mode previously rendered at 3× the intended viewport (864px)
196
+ // because the fallback was multiplied by `seriesCount`. The
197
+ // fallback should be 288px regardless of series count.
198
+ const threeSeries = [oneToTwelve(), oneToTwelve(), oneToTwelve()]
199
+ const { container } = render(
200
+ <CategoryUI data={threeSeries} maxItems={0} />,
201
+ )
202
+ const root = container.firstChild as HTMLElement
203
+ const list = root.firstChild as HTMLElement
204
+ expect(list.style.maxHeight).toBe('288px')
205
+ })
206
+ })
207
+
208
+ it('honors custom labels.other + labels.otherCount with {count} substitution', () => {
209
+ render(
210
+ <CategoryUI
211
+ data={[oneToTwelve()]}
212
+ maxItems={3}
213
+ labels={{
214
+ other: 'Others',
215
+ otherCount: '+{count} hidden',
216
+ }}
217
+ />,
218
+ )
219
+ expect(screen.getByText('Others')).toBeTruthy()
220
+ expect(screen.getByText('(+9 hidden)')).toBeTruthy()
221
+ })
222
+
223
+ it('large maxItems = no Other row', () => {
224
+ render(<CategoryUI data={[oneToTwelve()]} maxItems={1_000} />)
225
+ expect(screen.queryByText(/more/)).toBeNull()
226
+ expect(screen.getAllByRole('button')).toHaveLength(12)
227
+ })
228
+ })
229
+
230
+ // ── Multi-series grouped rendering ─────────────────────────────────
231
+ describe('multi-series', () => {
232
+ const twoSeries = () => [
233
+ [
234
+ { name: 'A', value: 10 },
235
+ { name: 'B', value: 20 },
236
+ ],
237
+ [
238
+ { name: 'A', value: 5 },
239
+ { name: 'B', value: 8 },
240
+ ],
241
+ ]
242
+
243
+ it('renders one row per unique name across series', () => {
244
+ render(<CategoryUI data={twoSeries()} />)
245
+ // 2 unique names → 2 row buttons (not 4 like a per-series layout would give).
246
+ expect(screen.getAllByRole('button')).toHaveLength(2)
247
+ expect(screen.getByText('A')).toBeTruthy()
248
+ expect(screen.getByText('B')).toBeTruthy()
249
+ })
250
+
251
+ it('renders both values per row, formatted', () => {
252
+ render(<CategoryUI data={twoSeries()} formatter={(n) => `$${n}`} />)
253
+ const rowA = screen.getAllByRole('button')[0]!
254
+ expect(within(rowA).getByText('A')).toBeTruthy()
255
+ expect(within(rowA).getByText('$10')).toBeTruthy()
256
+ expect(within(rowA).getByText('$5')).toBeTruthy()
257
+ })
258
+
259
+ it('clicking a multi-row toggles by name with raw key', () => {
260
+ const onSelectionChange = vi.fn()
261
+ render(
262
+ <CategoryUI
263
+ data={twoSeries()}
264
+ selection={[]}
265
+ onSelectionChange={onSelectionChange}
266
+ />,
267
+ )
268
+ fireEvent.click(screen.getAllByRole('button')[1]!)
269
+ expect(onSelectionChange).toHaveBeenCalledWith(['B'])
270
+ })
271
+
272
+ it('falls back gracefully when data.length > series.length (extra series unlabeled)', () => {
273
+ render(<CategoryUI data={twoSeries()} series={[{ name: 'Only-One' }]} />)
274
+ // 2 rows still render (data.length = 2 is the truth).
275
+ expect(screen.getAllByRole('button')).toHaveLength(2)
276
+ // Legend has just one entry.
277
+ expect(screen.getByText('Only-One')).toBeTruthy()
278
+ })
279
+
280
+ it('falls back gracefully when series.length > data.length (extras ignored, no legend overflow)', () => {
281
+ render(
282
+ <CategoryUI
283
+ data={[[{ name: 'A', value: 1 }]]}
284
+ series={[{ name: 'S1' }, { name: 'S2' }, { name: 'S3' }]}
285
+ />,
286
+ )
287
+ // data.length === 1 → single-series layout, no legend.
288
+ expect(screen.queryByText('S2')).toBeNull()
289
+ })
290
+ })
291
+
292
+ // ── Legend ─────────────────────────────────────────────────────────
293
+ describe('legend', () => {
294
+ it('renders the legend when multi-series + series prop supplied', () => {
295
+ render(
296
+ <CategoryUI
297
+ data={[[{ name: 'A', value: 1 }], [{ name: 'A', value: 2 }]]}
298
+ series={[{ name: '2024' }, { name: '2025' }]}
299
+ />,
300
+ )
301
+ expect(screen.getByText('2024')).toBeTruthy()
302
+ expect(screen.getByText('2025')).toBeTruthy()
303
+ })
304
+
305
+ it('does NOT render the legend for single-series even when series is given', () => {
306
+ render(
307
+ <CategoryUI
308
+ data={[[{ name: 'A', value: 1 }]]}
309
+ series={[{ name: 'OnlySeries' }]}
310
+ />,
311
+ )
312
+ expect(screen.queryByText('OnlySeries')).toBeNull()
313
+ })
314
+
315
+ it('does NOT render the legend for multi-series when series is omitted', () => {
316
+ render(
317
+ <CategoryUI
318
+ data={[[{ name: 'A', value: 1 }], [{ name: 'A', value: 2 }]]}
319
+ />,
320
+ )
321
+ // No legend element should be in the DOM (no series names to show).
322
+ // The text "A" is still present from the row label.
323
+ expect(screen.getAllByText('A')).toHaveLength(1)
324
+ })
325
+ })
326
+
327
+ // ── Selection visual ───────────────────────────────────────────────
328
+ describe('selection visual', () => {
329
+ it('marks selected rows aria-pressed=true', () => {
330
+ render(
331
+ <CategoryUI
332
+ data={[
333
+ [
334
+ { name: 'A', value: 10 },
335
+ { name: 'B', value: 20 },
336
+ ],
337
+ ]}
338
+ selection={['A']}
339
+ />,
340
+ )
341
+ const pressed = screen.getAllByRole('button', { pressed: true })
342
+ expect(pressed).toHaveLength(1)
343
+ expect(pressed[0]!.textContent).toContain('A')
344
+ })
345
+
346
+ it('fires onSelectionChange with the toggled set on click', () => {
347
+ const onSelectionChange = vi.fn()
348
+ render(
349
+ <CategoryUI
350
+ data={[
351
+ [
352
+ { name: 'A', value: 10 },
353
+ { name: 'B', value: 20 },
354
+ ],
355
+ ]}
356
+ selection={[]}
357
+ onSelectionChange={onSelectionChange}
358
+ />,
359
+ )
360
+ fireEvent.click(screen.getByText('B'))
361
+ expect(onSelectionChange).toHaveBeenCalledWith(['B'])
362
+ })
363
+
364
+ it('removes a selected item on second click', () => {
365
+ const onSelectionChange = vi.fn()
366
+ render(
367
+ <CategoryUI
368
+ data={[
369
+ [
370
+ { name: 'A', value: 10 },
371
+ { name: 'B', value: 20 },
372
+ ],
373
+ ]}
374
+ selection={['A', 'B']}
375
+ onSelectionChange={onSelectionChange}
376
+ />,
377
+ )
378
+ fireEvent.click(screen.getByText('A'))
379
+ expect(onSelectionChange).toHaveBeenCalledWith(['B'])
380
+ })
381
+
382
+ it('toggles via keyboard Enter and Space', () => {
383
+ const onSelectionChange = vi.fn()
384
+ render(
385
+ <CategoryUI
386
+ data={[[{ name: 'A', value: 10 }]]}
387
+ selection={[]}
388
+ onSelectionChange={onSelectionChange}
389
+ />,
390
+ )
391
+ const row = screen.getByRole('button')
392
+ fireEvent.keyDown(row, { key: 'Enter' })
393
+ expect(onSelectionChange).toHaveBeenCalledWith(['A'])
394
+ onSelectionChange.mockClear()
395
+ fireEvent.keyDown(row, { key: ' ' })
396
+ expect(onSelectionChange).toHaveBeenCalledWith(['A'])
397
+ })
398
+ })
399
+
400
+ // ── Selection dim color (bar-fill-only signal) ─────────────────────
401
+ //
402
+ // When a selection exists, non-selected rows' bar fills swap to
403
+ // `theme.palette.action.disabled` — the ONLY visual difference
404
+ // between selected and non-selected rows. Text colors stay at full
405
+ // values; rows have no bg tint, no opacity rule.
406
+ //
407
+ // jsdom + MUI default light theme: `action.disabled` resolves to
408
+ // `rgba(0, 0, 0, 0.26)` — what the browser writes to
409
+ // `style.backgroundColor`.
410
+ describe('selection dim color', () => {
411
+ const DIM = 'rgba(0, 0, 0, 0.26)'
412
+
413
+ function getBarFill(row: HTMLElement): HTMLElement | null {
414
+ return row.querySelector('div[style*="width:"]')
415
+ }
416
+
417
+ it('non-selected rows render the bar fill in the theme dim color when a selection exists', () => {
418
+ render(
419
+ <CategoryUI
420
+ data={[
421
+ [
422
+ { name: 'A', value: 10, color: 'rgb(255, 0, 0)' },
423
+ { name: 'B', value: 20, color: 'rgb(0, 255, 0)' },
424
+ ],
425
+ ]}
426
+ selection={['A']}
427
+ />,
428
+ )
429
+ const rows = screen.getAllByRole('button')
430
+ const aRow = rows.find((r) => r.textContent?.includes('A'))!
431
+ const bRow = rows.find((r) => r.textContent?.includes('B'))!
432
+ // Selected: keeps its real color.
433
+ expect(getBarFill(aRow)!.style.backgroundColor).toBe('rgb(255, 0, 0)')
434
+ // Non-selected: swaps to the dim color.
435
+ expect(getBarFill(bRow)!.style.backgroundColor).toBe(DIM)
436
+ })
437
+
438
+ it('without a selection, no row is dimmed (every bar keeps its real color)', () => {
439
+ render(
440
+ <CategoryUI
441
+ data={[
442
+ [
443
+ { name: 'A', value: 10, color: 'rgb(255, 0, 0)' },
444
+ { name: 'B', value: 20, color: 'rgb(0, 255, 0)' },
445
+ ],
446
+ ]}
447
+ />,
448
+ )
449
+ const rows = screen.getAllByRole('button')
450
+ for (const row of rows) {
451
+ expect(getBarFill(row)!.style.backgroundColor).not.toBe(DIM)
452
+ }
453
+ })
454
+
455
+ it('multi-series: every bar in a dimmed row gets the dim color', () => {
456
+ render(
457
+ <CategoryUI
458
+ data={[
459
+ [
460
+ { name: 'A', value: 10, color: 'rgb(255, 0, 0)' },
461
+ { name: 'B', value: 20, color: 'rgb(255, 0, 0)' },
462
+ ],
463
+ [
464
+ { name: 'A', value: 5, color: 'rgb(0, 0, 255)' },
465
+ { name: 'B', value: 7, color: 'rgb(0, 0, 255)' },
466
+ ],
467
+ ]}
468
+ selection={['A']}
469
+ />,
470
+ )
471
+ const rows = screen.getAllByRole('button')
472
+ const bRow = rows.find((r) => r.textContent?.includes('B'))!
473
+ const fills = bRow.querySelectorAll<HTMLElement>('div[style*="width:"]')
474
+ expect(fills.length).toBe(2)
475
+ fills.forEach((f) => {
476
+ expect(f.style.backgroundColor).toBe(DIM)
477
+ })
478
+ })
479
+ })
480
+
481
+ // ── labelFormatter (selection-key integrity) ───────────────────────
482
+ describe('labelFormatter', () => {
483
+ it('transforms displayed names without changing selection keys', () => {
484
+ const onSelectionChange = vi.fn()
485
+ render(
486
+ <CategoryUI
487
+ data={[
488
+ [
489
+ { name: 'dog', value: 10 },
490
+ { name: 'cat', value: 20 },
491
+ ],
492
+ ]}
493
+ labelFormatter={(n) => String(n).toUpperCase()}
494
+ selection={[]}
495
+ onSelectionChange={onSelectionChange}
496
+ />,
497
+ )
498
+ // Display labels are upper-cased.
499
+ expect(screen.getByText('DOG')).toBeTruthy()
500
+ expect(screen.getByText('CAT')).toBeTruthy()
501
+ expect(screen.queryByText('dog')).toBeNull()
502
+ // Selection callback receives the RAW name.
503
+ fireEvent.click(screen.getByText('DOG'))
504
+ expect(onSelectionChange).toHaveBeenCalledWith(['dog'])
505
+ })
506
+
507
+ it('formats names in multi-series rows too, with raw selection keys', () => {
508
+ const onSelectionChange = vi.fn()
509
+ render(
510
+ <CategoryUI
511
+ data={[[{ name: 'dog', value: 1 }], [{ name: 'dog', value: 2 }]]}
512
+ labelFormatter={(n) => `[${n}]`}
513
+ selection={[]}
514
+ onSelectionChange={onSelectionChange}
515
+ />,
516
+ )
517
+ expect(screen.getByText('[dog]')).toBeTruthy()
518
+ fireEvent.click(screen.getByText('[dog]'))
519
+ expect(onSelectionChange).toHaveBeenCalledWith(['dog'])
520
+ })
521
+ })
522
+
523
+ // ── Palette + per-item / per-series color overrides ────────────────
524
+ describe('color palette', () => {
525
+ function getBarFill(row: HTMLElement): HTMLElement | null {
526
+ // The bar fill is the second <div> inside the bar track div.
527
+ return row.querySelector(
528
+ // The MUI Box renders as a div; the fill has inline style with width.
529
+ 'div[style*="width:"]',
530
+ )
531
+ }
532
+
533
+ it('per-item color (data[s][r].color) wins over series + palette', () => {
534
+ render(
535
+ <CategoryUI
536
+ data={[[{ name: 'A', value: 10, color: 'rgb(255, 0, 0)' }]]}
537
+ series={[{ name: 'S', color: 'rgb(0, 255, 0)' }]}
538
+ />,
539
+ )
540
+ const row = screen.getByRole('button')
541
+ const fill = getBarFill(row)
542
+ expect(fill).toBeTruthy()
543
+ // jsdom resolves theme tokens but inline color literals come through.
544
+ expect(fill!.style.backgroundColor).toBe('rgb(255, 0, 0)')
545
+ })
546
+
547
+ it('series-level color (series[i].color) wins over palette', () => {
548
+ render(
549
+ <CategoryUI
550
+ data={[[{ name: 'A', value: 10 }]]}
551
+ series={[{ name: 'S', color: 'rgb(123, 45, 67)' }]}
552
+ />,
553
+ )
554
+ const fill = getBarFill(screen.getByRole('button'))
555
+ expect(fill!.style.backgroundColor).toBe('rgb(123, 45, 67)')
556
+ })
557
+ })
558
+
559
+ // ── maxOverride ────────────────────────────────────────────────────
560
+ describe('maxOverride', () => {
561
+ it('changes the bar width denominator (different markup)', () => {
562
+ const { rerender, container } = render(
563
+ <CategoryUI data={[[{ name: 'A', value: 100 }]]} />,
564
+ )
565
+ const before = container.innerHTML
566
+ rerender(
567
+ <CategoryUI data={[[{ name: 'A', value: 100 }]]} maxOverride={200} />,
568
+ )
569
+ expect(container.innerHTML).not.toBe(before)
570
+ })
571
+ })
572
+
573
+ // ── formatter ──────────────────────────────────────────────────────
574
+ describe('formatter', () => {
575
+ it('uses formatter for the value display', () => {
576
+ render(
577
+ <CategoryUI
578
+ data={[[{ name: 'A', value: 1234 }]]}
579
+ formatter={(n) => n.toLocaleString('en-US')}
580
+ />,
581
+ )
582
+ expect(screen.getByText('1,234')).toBeTruthy()
583
+ })
584
+ })
585
+
586
+ // ── Size variant ───────────────────────────────────────────────────
587
+ describe('size variant', () => {
588
+ const TWO_SERIES = [
589
+ [
590
+ { name: 'A', value: 10 },
591
+ { name: 'B', value: 20 },
592
+ ],
593
+ [
594
+ { name: 'A', value: 5 },
595
+ { name: 'B', value: 15 },
596
+ ],
597
+ ]
598
+
599
+ it('defaults to size="small" — every bar carries data-size="small"', () => {
600
+ const { container } = render(
601
+ <CategoryUI
602
+ data={[
603
+ [
604
+ { name: 'A', value: 10 },
605
+ { name: 'B', value: 20 },
606
+ ],
607
+ ]}
608
+ />,
609
+ )
610
+ const tracks = container.querySelectorAll('[data-size]')
611
+ expect(tracks.length).toBe(2)
612
+ tracks.forEach((t) => {
613
+ expect(t.getAttribute('data-size')).toBe('small')
614
+ })
615
+ })
616
+
617
+ it('propagates size="medium" to every bar (single-series)', () => {
618
+ const { container } = render(
619
+ <CategoryUI
620
+ data={[
621
+ [
622
+ { name: 'A', value: 10 },
623
+ { name: 'B', value: 20 },
624
+ ],
625
+ ]}
626
+ size='medium'
627
+ />,
628
+ )
629
+ const tracks = container.querySelectorAll('[data-size]')
630
+ expect(tracks.length).toBe(2)
631
+ tracks.forEach((t) => {
632
+ expect(t.getAttribute('data-size')).toBe('medium')
633
+ })
634
+ })
635
+
636
+ it('propagates size="medium" to every bar (multi-series — N bars per row)', () => {
637
+ const { container } = render(
638
+ <CategoryUI data={TWO_SERIES} size='medium' />,
639
+ )
640
+ // 2 rows × 2 series = 4 bars
641
+ const tracks = container.querySelectorAll('[data-size]')
642
+ expect(tracks.length).toBe(4)
643
+ tracks.forEach((t) => {
644
+ expect(t.getAttribute('data-size')).toBe('medium')
645
+ })
646
+ })
647
+ })
648
+
649
+ // ── Stacked mode ────────────────────────────────────────────────────
650
+ describe('stacked', () => {
651
+ const TWO_SERIES = [
652
+ [
653
+ { name: 'A', value: 45 },
654
+ { name: 'B', value: 30 },
655
+ ],
656
+ [
657
+ { name: 'A', value: 32 },
658
+ { name: 'B', value: 18 },
659
+ ],
660
+ ]
661
+
662
+ it('multi-series + stacked → one stacked row per group with one bar track per row', () => {
663
+ const { container } = render(
664
+ <CategoryUI
665
+ data={TWO_SERIES}
666
+ series={[{ name: 'Women' }, { name: 'Men' }]}
667
+ stacked
668
+ />,
669
+ )
670
+ // 2 unique names → 2 stacked rows → 2 bar tracks (NOT 4 like CategoryRowMulti).
671
+ const tracks = container.querySelectorAll('[data-size]')
672
+ expect(tracks.length).toBe(2)
673
+ // Each track has 2 segments (data-bar-fill) for the two non-zero series.
674
+ const fills = container.querySelectorAll('[data-bar-fill="true"]')
675
+ expect(fills.length).toBe(4)
676
+ })
677
+
678
+ it('renders the formatted total in the header and the breakdown line under each row', () => {
679
+ render(
680
+ <CategoryUI
681
+ data={TWO_SERIES}
682
+ series={[{ name: 'Women' }, { name: 'Men' }]}
683
+ stacked
684
+ formatter={(n) => `${n}K`}
685
+ />,
686
+ )
687
+ // Totals: A=45+32=77, B=30+18=48
688
+ expect(screen.getByText('77K')).toBeTruthy()
689
+ expect(screen.getByText('48K')).toBeTruthy()
690
+ // Breakdown labels per row.
691
+ expect(screen.getByText('Women: 45K')).toBeTruthy()
692
+ expect(screen.getByText('Men: 32K')).toBeTruthy()
693
+ expect(screen.getByText('Women: 30K')).toBeTruthy()
694
+ expect(screen.getByText('Men: 18K')).toBeTruthy()
695
+ })
696
+
697
+ it('falls back to "Series N" in the breakdown when series prop is omitted', () => {
698
+ render(<CategoryUI data={TWO_SERIES} stacked formatter={(n) => `${n}`} />)
699
+ expect(screen.getByText('Series 1: 45')).toBeTruthy()
700
+ expect(screen.getByText('Series 2: 32')).toBeTruthy()
701
+ })
702
+
703
+ it('stacked is a no-op for single-series data (still renders CategoryRowSingle)', () => {
704
+ const { container } = render(
705
+ <CategoryUI data={[[{ name: 'A', value: 10 }]]} stacked />,
706
+ )
707
+ // Single-series row has no breakdown text — assert by the lack of
708
+ // "Series 1: ..." pattern (which would appear in stacked rendering).
709
+ expect(screen.queryByText(/Series 1:/)).toBeNull()
710
+ // 1 bar track for the one row.
711
+ expect(container.querySelectorAll('[data-size]').length).toBe(1)
712
+ })
713
+
714
+ it('clicking a stacked row toggles by raw name', () => {
715
+ const onSelectionChange = vi.fn()
716
+ render(
717
+ <CategoryUI
718
+ data={TWO_SERIES}
719
+ series={[{ name: 'Women' }, { name: 'Men' }]}
720
+ stacked
721
+ selection={[]}
722
+ onSelectionChange={onSelectionChange}
723
+ />,
724
+ )
725
+ fireEvent.click(screen.getAllByRole('button')[1]!)
726
+ expect(onSelectionChange).toHaveBeenCalledWith(['B'])
727
+ })
728
+
729
+ it('dim color applies to every segment of non-selected rows when a selection exists', () => {
730
+ const DIM = 'rgba(0, 0, 0, 0.26)'
731
+ render(
732
+ <CategoryUI
733
+ data={TWO_SERIES}
734
+ series={[{ name: 'Women' }, { name: 'Men' }]}
735
+ stacked
736
+ selection={['A']}
737
+ />,
738
+ )
739
+ const rows = screen.getAllByRole('button')
740
+ const bRow = rows.find((r) => r.textContent?.includes('B'))!
741
+ const fills = bRow.querySelectorAll<HTMLElement>('[data-bar-fill="true"]')
742
+ expect(fills.length).toBe(2)
743
+ fills.forEach((f) => expect(f.style.backgroundColor).toBe(DIM))
744
+ })
745
+ })
746
+ })