@misoto22/design 0.6.1 → 0.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 (402) hide show
  1. package/AGENTS.md +87 -0
  2. package/CLAUDE.md +1 -0
  3. package/README.md +62 -5
  4. package/bin/misoto22-design.mjs +59 -13
  5. package/dist/agent/Accordion.md +24 -1
  6. package/dist/agent/Alert.md +23 -1
  7. package/dist/agent/AppShell.md +30 -2
  8. package/dist/agent/ArchitectureFigure.md +71 -0
  9. package/dist/agent/AreaChart.md +27 -2
  10. package/dist/agent/Article.md +26 -3
  11. package/dist/agent/AspectRatio.md +57 -0
  12. package/dist/agent/Avatar.md +25 -5
  13. package/dist/agent/Badge.md +22 -1
  14. package/dist/agent/BarChart.md +29 -2
  15. package/dist/agent/BarList.md +27 -1
  16. package/dist/agent/BigNumber.md +26 -2
  17. package/dist/agent/BoxPlot.md +24 -1
  18. package/dist/agent/Breadcrumb.md +24 -2
  19. package/dist/agent/BulletChart.md +23 -1
  20. package/dist/agent/Button.md +25 -1
  21. package/dist/agent/Calendar.md +37 -3
  22. package/dist/agent/Card.md +24 -2
  23. package/dist/agent/Checkbox.md +23 -2
  24. package/dist/agent/Code.md +50 -0
  25. package/dist/agent/CodeBlock.md +74 -0
  26. package/dist/agent/Collapsible.md +22 -1
  27. package/dist/agent/Combobox.md +29 -3
  28. package/dist/agent/Command.md +25 -2
  29. package/dist/agent/ComposedChart.md +26 -2
  30. package/dist/agent/ContextMenu.md +37 -4
  31. package/dist/agent/DataflowFigure.md +48 -0
  32. package/dist/agent/DatePicker.md +34 -6
  33. package/dist/agent/DescriptionList.md +68 -0
  34. package/dist/agent/Diagram.md +27 -3
  35. package/dist/agent/DiagramCanvas.md +70 -0
  36. package/dist/agent/DiagramExportMenu.md +64 -0
  37. package/dist/agent/DiagramInspector.md +72 -0
  38. package/dist/agent/DiagramLegend.md +55 -0
  39. package/dist/agent/DiagramMinimap.md +68 -0
  40. package/dist/agent/DiagramToolbar.md +69 -0
  41. package/dist/agent/Dialog.md +26 -3
  42. package/dist/agent/DropdownMenu.md +45 -6
  43. package/dist/agent/EmptyState.md +26 -1
  44. package/dist/agent/ErrorState.md +27 -2
  45. package/dist/agent/Facet.md +25 -1
  46. package/dist/agent/Field.md +45 -4
  47. package/dist/agent/FigureBand.md +24 -1
  48. package/dist/agent/FloatingIconButton.md +23 -1
  49. package/dist/agent/FunnelChart.md +23 -1
  50. package/dist/agent/Heading.md +64 -0
  51. package/dist/agent/Heatmap.md +25 -1
  52. package/dist/agent/Histogram.md +26 -2
  53. package/dist/agent/Input.md +22 -1
  54. package/dist/agent/Kbd.md +19 -1
  55. package/dist/agent/LifecycleFigure.md +49 -0
  56. package/dist/agent/LineChart.md +26 -2
  57. package/dist/agent/LinkArrow.md +20 -1
  58. package/dist/agent/Markdown.md +68 -0
  59. package/dist/agent/NativeSelect.md +23 -2
  60. package/dist/agent/NavItem.md +21 -1
  61. package/dist/agent/Pagination.md +28 -2
  62. package/dist/agent/PieChart.md +26 -2
  63. package/dist/agent/Popover.md +23 -1
  64. package/dist/agent/Progress.md +27 -3
  65. package/dist/agent/RadarChart.md +26 -2
  66. package/dist/agent/RadialChart.md +26 -3
  67. package/dist/agent/RadioGroup.md +23 -2
  68. package/dist/agent/SankeyChart.md +25 -2
  69. package/dist/agent/ScatterChart.md +27 -2
  70. package/dist/agent/ScrollArea.md +24 -3
  71. package/dist/agent/SearchableMenu.md +23 -1
  72. package/dist/agent/Select.md +29 -3
  73. package/dist/agent/Separator.md +28 -4
  74. package/dist/agent/SequenceFigure.md +50 -0
  75. package/dist/agent/Sheet.md +25 -2
  76. package/dist/agent/Skeleton.md +24 -2
  77. package/dist/agent/Slider.md +27 -4
  78. package/dist/agent/Sparkline.md +23 -1
  79. package/dist/agent/Spinner.md +24 -2
  80. package/dist/agent/StatusDot.md +20 -1
  81. package/dist/agent/StatusPill.md +28 -2
  82. package/dist/agent/Steps.md +24 -1
  83. package/dist/agent/Switch.md +21 -1
  84. package/dist/agent/Table.md +26 -3
  85. package/dist/agent/Tabs.md +24 -2
  86. package/dist/agent/Tag.md +40 -9
  87. package/dist/agent/Text.md +65 -0
  88. package/dist/agent/Textarea.md +20 -1
  89. package/dist/agent/Timestamp.md +64 -0
  90. package/dist/agent/Toast.md +26 -2
  91. package/dist/agent/ToggleGroup.md +24 -2
  92. package/dist/agent/Toolbar.md +71 -0
  93. package/dist/agent/Tooltip.md +23 -1
  94. package/dist/agent/TreemapChart.md +24 -1
  95. package/dist/agent/WaterfallChart.md +24 -1
  96. package/dist/agent/WorkflowFigure.md +50 -0
  97. package/dist/agent/catalog.json +6086 -1009
  98. package/dist/agent/index.md +27 -4
  99. package/dist/article.css +16 -4
  100. package/dist/charts/AreaChart/AreaChart.d.ts +9 -2
  101. package/dist/charts/AreaChart/AreaChart.js +12 -13
  102. package/dist/charts/AreaChart/AreaChart.js.map +1 -1
  103. package/dist/charts/BarChart/BarChart.d.ts +14 -3
  104. package/dist/charts/BarChart/BarChart.js +20 -12
  105. package/dist/charts/BarChart/BarChart.js.map +1 -1
  106. package/dist/charts/BarList/BarList.d.ts +9 -1
  107. package/dist/charts/BarList/BarList.js +9 -1
  108. package/dist/charts/BarList/BarList.js.map +1 -1
  109. package/dist/charts/BigNumber/BigNumber.d.ts +19 -3
  110. package/dist/charts/BigNumber/BigNumber.js +25 -5
  111. package/dist/charts/BigNumber/BigNumber.js.map +1 -1
  112. package/dist/charts/BoxPlot/BoxPlot.js +4 -2
  113. package/dist/charts/BoxPlot/BoxPlot.js.map +1 -1
  114. package/dist/charts/BulletChart/BulletChart.js +28 -7
  115. package/dist/charts/BulletChart/BulletChart.js.map +1 -1
  116. package/dist/charts/ComposedChart/ComposedChart.d.ts +9 -2
  117. package/dist/charts/ComposedChart/ComposedChart.js +10 -10
  118. package/dist/charts/ComposedChart/ComposedChart.js.map +1 -1
  119. package/dist/charts/FunnelChart/FunnelChart.d.ts +7 -2
  120. package/dist/charts/FunnelChart/FunnelChart.js +6 -16
  121. package/dist/charts/FunnelChart/FunnelChart.js.map +1 -1
  122. package/dist/charts/Heatmap/Heatmap.d.ts +17 -3
  123. package/dist/charts/Heatmap/Heatmap.js +20 -11
  124. package/dist/charts/Heatmap/Heatmap.js.map +1 -1
  125. package/dist/charts/Histogram/Histogram.d.ts +24 -1
  126. package/dist/charts/Histogram/Histogram.js +43 -11
  127. package/dist/charts/Histogram/Histogram.js.map +1 -1
  128. package/dist/charts/LineChart/LineChart.d.ts +9 -2
  129. package/dist/charts/LineChart/LineChart.js +11 -12
  130. package/dist/charts/LineChart/LineChart.js.map +1 -1
  131. package/dist/charts/PieChart/PieChart.d.ts +15 -3
  132. package/dist/charts/PieChart/PieChart.js +14 -6
  133. package/dist/charts/PieChart/PieChart.js.map +1 -1
  134. package/dist/charts/RadarChart/RadarChart.d.ts +15 -3
  135. package/dist/charts/RadarChart/RadarChart.js +11 -9
  136. package/dist/charts/RadarChart/RadarChart.js.map +1 -1
  137. package/dist/charts/RadialChart/RadialChart.d.ts +22 -4
  138. package/dist/charts/RadialChart/RadialChart.js +28 -6
  139. package/dist/charts/RadialChart/RadialChart.js.map +1 -1
  140. package/dist/charts/SankeyChart/SankeyChart.d.ts +16 -4
  141. package/dist/charts/SankeyChart/SankeyChart.js +14 -6
  142. package/dist/charts/SankeyChart/SankeyChart.js.map +1 -1
  143. package/dist/charts/ScatterChart/ScatterChart.d.ts +24 -3
  144. package/dist/charts/ScatterChart/ScatterChart.js +21 -15
  145. package/dist/charts/ScatterChart/ScatterChart.js.map +1 -1
  146. package/dist/charts/Sparkline/Sparkline.js +5 -3
  147. package/dist/charts/Sparkline/Sparkline.js.map +1 -1
  148. package/dist/charts/TreemapChart/TreemapChart.d.ts +7 -2
  149. package/dist/charts/TreemapChart/TreemapChart.js +20 -11
  150. package/dist/charts/TreemapChart/TreemapChart.js.map +1 -1
  151. package/dist/charts/WaterfallChart/WaterfallChart.js +4 -2
  152. package/dist/charts/WaterfallChart/WaterfallChart.js.map +1 -1
  153. package/dist/charts/index.d.ts +2 -1
  154. package/dist/charts/lib/export.d.ts +34 -43
  155. package/dist/charts/lib/export.js +16 -158
  156. package/dist/charts/lib/export.js.map +1 -1
  157. package/dist/charts/lib/figure.d.ts +21 -1
  158. package/dist/charts/lib/figure.js +5 -2
  159. package/dist/charts/lib/figure.js.map +1 -1
  160. package/dist/charts/lib/scale.d.ts +23 -0
  161. package/dist/charts/lib/scale.js +13 -0
  162. package/dist/charts/lib/scale.js.map +1 -0
  163. package/dist/charts/lib/selection.d.ts +20 -0
  164. package/dist/charts/lib/selection.js +18 -0
  165. package/dist/charts/lib/selection.js.map +1 -0
  166. package/dist/charts/lib/toolbar.d.ts +1 -0
  167. package/dist/components/Accordion/Accordion.js +8 -1
  168. package/dist/components/Accordion/Accordion.js.map +1 -1
  169. package/dist/components/AppShell/AppShell.d.ts +6 -0
  170. package/dist/components/AppShell/AppShell.js +34 -4
  171. package/dist/components/AppShell/AppShell.js.map +1 -1
  172. package/dist/components/Article/Article.d.ts +11 -4
  173. package/dist/components/Article/Article.js +13 -0
  174. package/dist/components/Article/Article.js.map +1 -1
  175. package/dist/components/AspectRatio/AspectRatio.d.ts +46 -0
  176. package/dist/components/AspectRatio/AspectRatio.js +33 -0
  177. package/dist/components/AspectRatio/AspectRatio.js.map +1 -0
  178. package/dist/components/Avatar/Avatar.d.ts +12 -3
  179. package/dist/components/Avatar/Avatar.js +6 -1
  180. package/dist/components/Avatar/Avatar.js.map +1 -1
  181. package/dist/components/Breadcrumb/Breadcrumb.d.ts +6 -0
  182. package/dist/components/Breadcrumb/Breadcrumb.js +15 -0
  183. package/dist/components/Breadcrumb/Breadcrumb.js.map +1 -1
  184. package/dist/components/Button/Button.js +14 -0
  185. package/dist/components/Button/Button.js.map +1 -1
  186. package/dist/components/Calendar/Calendar.d.ts +29 -3
  187. package/dist/components/Calendar/Calendar.js +60 -12
  188. package/dist/components/Calendar/Calendar.js.map +1 -1
  189. package/dist/components/Card/Card.d.ts +6 -0
  190. package/dist/components/Card/Card.js +1 -1
  191. package/dist/components/Card/Card.js.map +1 -1
  192. package/dist/components/Checkbox/Checkbox.d.ts +5 -2
  193. package/dist/components/Checkbox/Checkbox.js +12 -2
  194. package/dist/components/Checkbox/Checkbox.js.map +1 -1
  195. package/dist/components/Code/Code.d.ts +30 -0
  196. package/dist/components/Code/Code.js +21 -0
  197. package/dist/components/Code/Code.js.map +1 -0
  198. package/dist/components/CodeBlock/CodeBlock.d.ts +141 -0
  199. package/dist/components/CodeBlock/CodeBlock.js +123 -0
  200. package/dist/components/CodeBlock/CodeBlock.js.map +1 -0
  201. package/dist/components/Collapsible/Collapsible.js +10 -2
  202. package/dist/components/Collapsible/Collapsible.js.map +1 -1
  203. package/dist/components/Combobox/Combobox.d.ts +15 -1
  204. package/dist/components/Combobox/Combobox.js +25 -5
  205. package/dist/components/Combobox/Combobox.js.map +1 -1
  206. package/dist/components/Command/Command.d.ts +4 -2
  207. package/dist/components/Command/Command.js +13 -1
  208. package/dist/components/Command/Command.js.map +1 -1
  209. package/dist/components/ContextMenu/ContextMenu.d.ts +30 -5
  210. package/dist/components/ContextMenu/ContextMenu.js +32 -2
  211. package/dist/components/ContextMenu/ContextMenu.js.map +1 -1
  212. package/dist/components/DatePicker/DatePicker.d.ts +36 -4
  213. package/dist/components/DatePicker/DatePicker.js +55 -12
  214. package/dist/components/DatePicker/DatePicker.js.map +1 -1
  215. package/dist/components/DescriptionList/DescriptionList.d.ts +73 -0
  216. package/dist/components/DescriptionList/DescriptionList.js +35 -0
  217. package/dist/components/DescriptionList/DescriptionList.js.map +1 -0
  218. package/dist/components/Diagram/Diagram.d.ts +8 -3
  219. package/dist/components/Diagram/Diagram.js +82 -10
  220. package/dist/components/Diagram/Diagram.js.map +1 -1
  221. package/dist/components/Dialog/Dialog.d.ts +18 -3
  222. package/dist/components/Dialog/Dialog.js +20 -2
  223. package/dist/components/Dialog/Dialog.js.map +1 -1
  224. package/dist/components/DropdownMenu/DropdownMenu.d.ts +49 -11
  225. package/dist/components/DropdownMenu/DropdownMenu.js +32 -2
  226. package/dist/components/DropdownMenu/DropdownMenu.js.map +1 -1
  227. package/dist/components/EmptyState/EmptyState.d.ts +25 -1
  228. package/dist/components/EmptyState/EmptyState.js +3 -1
  229. package/dist/components/EmptyState/EmptyState.js.map +1 -1
  230. package/dist/components/ErrorState/ErrorState.d.ts +22 -1
  231. package/dist/components/ErrorState/ErrorState.js +11 -2
  232. package/dist/components/ErrorState/ErrorState.js.map +1 -1
  233. package/dist/components/Field/Field.d.ts +70 -3
  234. package/dist/components/Field/Field.js +53 -12
  235. package/dist/components/Field/Field.js.map +1 -1
  236. package/dist/components/Field/field-control.d.ts +31 -0
  237. package/dist/components/Field/field-control.js +12 -0
  238. package/dist/components/Field/field-control.js.map +1 -0
  239. package/dist/components/FloatingIconButton/FloatingIconButton.js +2 -0
  240. package/dist/components/FloatingIconButton/FloatingIconButton.js.map +1 -1
  241. package/dist/components/Heading/Heading.d.ts +62 -0
  242. package/dist/components/Heading/Heading.js +44 -0
  243. package/dist/components/Heading/Heading.js.map +1 -0
  244. package/dist/components/Markdown/Markdown.d.ts +139 -0
  245. package/dist/components/Markdown/Markdown.js +135 -0
  246. package/dist/components/Markdown/Markdown.js.map +1 -0
  247. package/dist/components/Markdown/inline.d.ts +29 -0
  248. package/dist/components/Markdown/inline.js +74 -0
  249. package/dist/components/Markdown/inline.js.map +1 -0
  250. package/dist/components/Markdown/nodes.d.ts +65 -0
  251. package/dist/components/Markdown/nodes.js +1 -0
  252. package/dist/components/Markdown/nodes.js.map +1 -0
  253. package/dist/components/Markdown/parse.d.ts +24 -0
  254. package/dist/components/Markdown/parse.js +110 -0
  255. package/dist/components/Markdown/parse.js.map +1 -0
  256. package/dist/components/Markdown/slug.d.ts +15 -0
  257. package/dist/components/Markdown/slug.js +7 -0
  258. package/dist/components/Markdown/slug.js.map +1 -0
  259. package/dist/components/NativeSelect/NativeSelect.d.ts +6 -0
  260. package/dist/components/NativeSelect/NativeSelect.js +2 -3
  261. package/dist/components/NativeSelect/NativeSelect.js.map +1 -1
  262. package/dist/components/Pagination/Pagination.d.ts +24 -1
  263. package/dist/components/Pagination/Pagination.js +6 -3
  264. package/dist/components/Pagination/Pagination.js.map +1 -1
  265. package/dist/components/Progress/Progress.d.ts +17 -5
  266. package/dist/components/Progress/Progress.js +18 -6
  267. package/dist/components/Progress/Progress.js.map +1 -1
  268. package/dist/components/RadioGroup/RadioGroup.d.ts +6 -0
  269. package/dist/components/RadioGroup/RadioGroup.js +11 -1
  270. package/dist/components/RadioGroup/RadioGroup.js.map +1 -1
  271. package/dist/components/ScrollArea/ScrollArea.d.ts +15 -0
  272. package/dist/components/ScrollArea/ScrollArea.js +2 -2
  273. package/dist/components/ScrollArea/ScrollArea.js.map +1 -1
  274. package/dist/components/SearchableMenu/SearchableMenu.d.ts +11 -2
  275. package/dist/components/SearchableMenu/SearchableMenu.js +24 -2
  276. package/dist/components/SearchableMenu/SearchableMenu.js.map +1 -1
  277. package/dist/components/Select/Select.d.ts +18 -2
  278. package/dist/components/Select/Select.js +20 -3
  279. package/dist/components/Select/Select.js.map +1 -1
  280. package/dist/components/Separator/Separator.d.ts +21 -3
  281. package/dist/components/Separator/Separator.js +9 -1
  282. package/dist/components/Separator/Separator.js.map +1 -1
  283. package/dist/components/Sheet/Sheet.d.ts +3 -0
  284. package/dist/components/Sheet/Sheet.js +11 -2
  285. package/dist/components/Sheet/Sheet.js.map +1 -1
  286. package/dist/components/Skeleton/Skeleton.d.ts +8 -1
  287. package/dist/components/Skeleton/Skeleton.js +1 -1
  288. package/dist/components/Skeleton/Skeleton.js.map +1 -1
  289. package/dist/components/Slider/Slider.d.ts +21 -3
  290. package/dist/components/Slider/Slider.js +54 -27
  291. package/dist/components/Slider/Slider.js.map +1 -1
  292. package/dist/components/Spinner/Spinner.d.ts +8 -0
  293. package/dist/components/Spinner/Spinner.js +3 -2
  294. package/dist/components/Spinner/Spinner.js.map +1 -1
  295. package/dist/components/StatusPill/StatusPill.d.ts +5 -0
  296. package/dist/components/StatusPill/StatusPill.js +6 -0
  297. package/dist/components/StatusPill/StatusPill.js.map +1 -1
  298. package/dist/components/Table/Table.d.ts +6 -0
  299. package/dist/components/Table/Table.js +3 -1
  300. package/dist/components/Table/Table.js.map +1 -1
  301. package/dist/components/Tag/Tag.d.ts +68 -9
  302. package/dist/components/Tag/Tag.js +50 -10
  303. package/dist/components/Tag/Tag.js.map +1 -1
  304. package/dist/components/Text/Text.d.ts +62 -0
  305. package/dist/components/Text/Text.js +30 -0
  306. package/dist/components/Text/Text.js.map +1 -0
  307. package/dist/components/Timestamp/Timestamp.d.ts +59 -0
  308. package/dist/components/Timestamp/Timestamp.js +56 -0
  309. package/dist/components/Timestamp/Timestamp.js.map +1 -0
  310. package/dist/components/Toast/Toast.d.ts +9 -1
  311. package/dist/components/Toast/Toast.js +27 -4
  312. package/dist/components/Toast/Toast.js.map +1 -1
  313. package/dist/components/ToggleGroup/ToggleGroup.d.ts +6 -1
  314. package/dist/components/ToggleGroup/ToggleGroup.js +11 -1
  315. package/dist/components/ToggleGroup/ToggleGroup.js.map +1 -1
  316. package/dist/components/Toolbar/Toolbar.d.ts +55 -0
  317. package/dist/components/Toolbar/Toolbar.js +43 -0
  318. package/dist/components/Toolbar/Toolbar.js.map +1 -0
  319. package/dist/diagrams/ArchitectureFigure/ArchitectureFigure.d.ts +55 -0
  320. package/dist/diagrams/ArchitectureFigure/ArchitectureFigure.js +175 -0
  321. package/dist/diagrams/ArchitectureFigure/ArchitectureFigure.js.map +1 -0
  322. package/dist/diagrams/DataflowFigure/DataflowFigure.d.ts +28 -0
  323. package/dist/diagrams/DataflowFigure/DataflowFigure.js +148 -0
  324. package/dist/diagrams/DataflowFigure/DataflowFigure.js.map +1 -0
  325. package/dist/diagrams/DiagramCanvas/DiagramCanvas.d.ts +78 -0
  326. package/dist/diagrams/DiagramCanvas/DiagramCanvas.js +229 -0
  327. package/dist/diagrams/DiagramCanvas/DiagramCanvas.js.map +1 -0
  328. package/dist/diagrams/DiagramExportMenu/DiagramExportMenu.d.ts +106 -0
  329. package/dist/diagrams/DiagramExportMenu/DiagramExportMenu.js +123 -0
  330. package/dist/diagrams/DiagramExportMenu/DiagramExportMenu.js.map +1 -0
  331. package/dist/diagrams/DiagramInspector/DiagramInspector.d.ts +70 -0
  332. package/dist/diagrams/DiagramInspector/DiagramInspector.js +94 -0
  333. package/dist/diagrams/DiagramInspector/DiagramInspector.js.map +1 -0
  334. package/dist/diagrams/DiagramLegend/DiagramLegend.d.ts +42 -0
  335. package/dist/diagrams/DiagramLegend/DiagramLegend.js +35 -0
  336. package/dist/diagrams/DiagramLegend/DiagramLegend.js.map +1 -0
  337. package/dist/diagrams/DiagramMinimap/DiagramMinimap.d.ts +77 -0
  338. package/dist/diagrams/DiagramMinimap/DiagramMinimap.js +106 -0
  339. package/dist/diagrams/DiagramMinimap/DiagramMinimap.js.map +1 -0
  340. package/dist/diagrams/DiagramToolbar/DiagramToolbar.d.ts +48 -0
  341. package/dist/diagrams/DiagramToolbar/DiagramToolbar.js +53 -0
  342. package/dist/diagrams/DiagramToolbar/DiagramToolbar.js.map +1 -0
  343. package/dist/diagrams/LifecycleFigure/LifecycleFigure.d.ts +31 -0
  344. package/dist/diagrams/LifecycleFigure/LifecycleFigure.js +234 -0
  345. package/dist/diagrams/LifecycleFigure/LifecycleFigure.js.map +1 -0
  346. package/dist/diagrams/SequenceFigure/SequenceFigure.d.ts +38 -0
  347. package/dist/diagrams/SequenceFigure/SequenceFigure.js +261 -0
  348. package/dist/diagrams/SequenceFigure/SequenceFigure.js.map +1 -0
  349. package/dist/diagrams/WorkflowFigure/WorkflowFigure.d.ts +36 -0
  350. package/dist/diagrams/WorkflowFigure/WorkflowFigure.js +259 -0
  351. package/dist/diagrams/WorkflowFigure/WorkflowFigure.js.map +1 -0
  352. package/dist/diagrams/index.d.ts +17 -0
  353. package/dist/diagrams/index.js +29 -0
  354. package/dist/diagrams/index.js.map +1 -0
  355. package/dist/diagrams/lib/dev.d.ts +61 -0
  356. package/dist/diagrams/lib/dev.js +96 -0
  357. package/dist/diagrams/lib/dev.js.map +1 -0
  358. package/dist/diagrams/lib/frame.d.ts +138 -0
  359. package/dist/diagrams/lib/frame.js +131 -0
  360. package/dist/diagrams/lib/frame.js.map +1 -0
  361. package/dist/diagrams/lib/geometry.d.ts +114 -0
  362. package/dist/diagrams/lib/geometry.js +113 -0
  363. package/dist/diagrams/lib/geometry.js.map +1 -0
  364. package/dist/diagrams/lib/legend.d.ts +32 -0
  365. package/dist/diagrams/lib/legend.js +84 -0
  366. package/dist/diagrams/lib/legend.js.map +1 -0
  367. package/dist/diagrams/lib/marks.d.ts +207 -0
  368. package/dist/diagrams/lib/marks.js +480 -0
  369. package/dist/diagrams/lib/marks.js.map +1 -0
  370. package/dist/diagrams/lib/route.d.ts +85 -0
  371. package/dist/diagrams/lib/route.js +178 -0
  372. package/dist/diagrams/lib/route.js.map +1 -0
  373. package/dist/diagrams/lib/wires.d.ts +59 -0
  374. package/dist/diagrams/lib/wires.js +90 -0
  375. package/dist/diagrams/lib/wires.js.map +1 -0
  376. package/dist/diagrams/spec.d.ts +405 -0
  377. package/dist/diagrams/spec.js +1 -0
  378. package/dist/diagrams/spec.js.map +1 -0
  379. package/dist/index.d.ts +16 -4
  380. package/dist/index.js +9 -0
  381. package/dist/index.js.map +1 -1
  382. package/dist/keyframes.css +57 -3
  383. package/dist/lib/overlay-container.d.ts +14 -4
  384. package/dist/lib/overlay-container.js.map +1 -1
  385. package/dist/lib/svg-export.d.ts +138 -0
  386. package/dist/lib/svg-export.js +198 -0
  387. package/dist/lib/svg-export.js.map +1 -0
  388. package/dist/lib/warn.d.ts +73 -0
  389. package/dist/lib/warn.js +38 -0
  390. package/dist/lib/warn.js.map +1 -0
  391. package/dist/semantic.css +51 -6
  392. package/dist/styles.css +1 -1
  393. package/dist/tokens.css +20 -2
  394. package/dist/tokens.d.ts +17 -1
  395. package/dist/tokens.js +121 -36
  396. package/dist/tokens.json +121 -36
  397. package/llms.txt +35 -0
  398. package/package.json +9 -2
  399. package/skills/misoto22-design/SKILL.md +21 -7
  400. package/skills/misoto22-design/evals/evals.json +84 -0
  401. package/skills/misoto22-design/rules/a11y.md +55 -3
  402. package/skills/misoto22-design/rules/forms.md +5 -0
@@ -1,9 +1,10 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
- import { useId } from "react";
3
+ import {} from "react";
4
4
  import { Treemap as RechartsTreemap } from "recharts";
5
5
  import { ChartContainer, cssName } from "../lib/chart.js";
6
6
  import { ChartFigure } from "../lib/figure.js";
7
+ import {} from "../lib/empty.js";
7
8
  import { ChartTooltip, ChartTooltipContent } from "../lib/tooltip.js";
8
9
  function TreemapChart({
9
10
  config = {},
@@ -18,10 +19,10 @@ function TreemapChart({
18
19
  showLabels = true,
19
20
  children,
20
21
  chartProps,
21
- hideDataTable = false
22
+ hideDataTable = false,
23
+ empty
22
24
  }) {
23
- const chartId = useId().replace(/:/g, "");
24
- const leaves = flatten(data);
25
+ const leaves = flatten(data, dataKey);
25
26
  return /* @__PURE__ */ jsx(
26
27
  ChartFigure,
27
28
  {
@@ -34,6 +35,8 @@ function TreemapChart({
34
35
  rowKey: "path",
35
36
  columns: [{ key: dataKey, label: "Value" }]
36
37
  },
38
+ isEmpty: data.length === 0,
39
+ empty,
37
40
  children: /* @__PURE__ */ jsx(ChartContainer, { config, children: /* @__PURE__ */ jsx(
38
41
  RechartsTreemap,
39
42
  {
@@ -42,7 +45,7 @@ function TreemapChart({
42
45
  nameKey: "name",
43
46
  aspectRatio,
44
47
  isAnimationActive: false,
45
- content: /* @__PURE__ */ jsx(TreemapTile, { chartId, variant, config, showLabels }),
48
+ content: /* @__PURE__ */ jsx(TreemapTile, { variant, config, showLabels }),
46
49
  ...chartProps,
47
50
  children
48
51
  }
@@ -50,15 +53,22 @@ function TreemapChart({
50
53
  }
51
54
  );
52
55
  }
53
- function flatten(nodes, prefix = "") {
56
+ function flatten(nodes, dataKey, prefix = "") {
54
57
  return nodes.flatMap((node) => {
55
58
  const path = prefix ? `${prefix} \u203A ${node.name}` : node.name;
56
- if (node.children?.length) return flatten(node.children, path);
57
- return [{ ...node, path }];
59
+ if (node.children?.length) return flatten(node.children, dataKey, path);
60
+ const value = Number(node[dataKey]);
61
+ if (Number.isFinite(value) && value > 0) return [{ ...node, path }];
62
+ return [
63
+ {
64
+ ...node,
65
+ path,
66
+ [dataKey]: Number.isFinite(value) ? `${value.toLocaleString()} \u2014 not drawn` : "not drawn"
67
+ }
68
+ ];
58
69
  });
59
70
  }
60
71
  function TreemapTile({
61
- chartId,
62
72
  variant,
63
73
  config,
64
74
  showLabels,
@@ -96,8 +106,7 @@ function TreemapTile({
96
106
  children: name
97
107
  }
98
108
  ),
99
- /* @__PURE__ */ jsx("title", { children: name }),
100
- /* @__PURE__ */ jsx("desc", { children: chartId })
109
+ /* @__PURE__ */ jsx("title", { children: name })
101
110
  ] });
102
111
  }
103
112
  function Tooltip({ variant, roundness }) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/charts/TreemapChart/TreemapChart.tsx"],"sourcesContent":["'use client'\n\nimport { useId, type ComponentProps, type ReactNode } from 'react'\nimport { Treemap as RechartsTreemap } from 'recharts'\nimport { ChartContainer, cssName, type ChartConfig } from '../lib/chart'\nimport { ChartFigure } from '../lib/figure'\nimport { ChartTooltip, ChartTooltipContent } from '../lib/tooltip'\nimport type { ChartTooltipSlotProps } from '../AreaChart/AreaChart'\n\n/**\n * How a tile is painted.\n *\n * `ramp` walks the series ramp so siblings separate by lightness; `nested`\n * darkens by DEPTH instead, which is the right encoding when the tree has more\n * than one level and the question is \"what is inside what\".\n */\nexport type TreemapVariant = 'ramp' | 'nested'\n\n/** A node in the tree. Children make it a branch. */\nexport interface TreemapNode {\n name: string\n /** Leaf value. Omit on a branch — a branch is the sum of its children. */\n size?: number\n children?: TreemapNode[]\n [key: string]: unknown\n}\n\n/** The raw Recharts Treemap props a consumer may still reach for. */\nexport type TreemapExtras = Omit<ComponentProps<typeof RechartsTreemap>, 'data' | 'dataKey'>\n\nexport interface TreemapChartProps {\n /** Tile names → their label and paint. Optional: the ramp covers unnamed tiles. */\n config?: ChartConfig\n /** The tree. One level is a flat set of tiles; two is a nested treemap. */\n data: TreemapNode[]\n /**\n * What the chart shows, in a sentence a reader could act on. Required, and\n * announced to a screen reader even when it is not printed.\n */\n title: string\n /** Prints the title above the plot instead of hiding it from sight. */\n showTitle?: boolean\n /** A line under the title — the unit, the window, the caveat. */\n description?: ReactNode\n /** Merged onto the figure, last, so a call site can size or space it. */\n className?: string\n variant?: TreemapVariant\n /** The leaf field holding each tile's number. */\n dataKey?: string\n /** How square the tiles are pushed to be. Recharts' own squarify parameter. */\n aspectRatio?: number\n /** Prints each tile's name on it, where the tile is big enough to hold it. */\n showLabels?: boolean\n /** The hover panel. */\n children?: ReactNode\n chartProps?: TreemapExtras\n /** Drops the hidden table view. Only correct when the page prints the data itself. */\n hideDataTable?: boolean\n}\n\n/**\n * Part of a whole, when the whole has too many parts for a pie — and the parts\n * nest.\n *\n * A treemap encodes value as AREA, which the eye reads worse than length but\n * far better than angle, and it is the only form here that stays readable at\n * fifty items. Two rules make it honest: no negative values (an area cannot be\n * negative), and the tiles have to sum to something a reader recognises as the\n * whole.\n *\n * Reach for `<BarChart>` when there are under a dozen items and the ranking\n * matters — a bar's length is the more precise encoding, and a treemap's\n * layout deliberately does not order its tiles by value alone.\n *\n * @example\n * <TreemapChart title=\"Bundle size by package\" data={packages} showLabels>\n * <TreemapChart.Tooltip />\n * </TreemapChart>\n */\nexport function TreemapChart({\n config = {},\n data,\n title,\n showTitle,\n description,\n className,\n variant = 'ramp',\n dataKey = 'size',\n aspectRatio = 4 / 3,\n showLabels = true,\n children,\n chartProps,\n hideDataTable = false,\n}: TreemapChartProps) {\n const chartId = useId().replace(/:/g, '')\n\n // Flattened for the table view, because a nested tree read aloud row by row\n // is not something anyone can follow — the leaves are the facts.\n const leaves = flatten(data)\n\n return (\n <ChartFigure\n title={title}\n showTitle={showTitle}\n description={description}\n className={className}\n table={\n hideDataTable\n ? false\n : {\n rows: leaves,\n rowKey: 'path',\n columns: [{ key: dataKey, label: 'Value' }],\n }\n }\n >\n <ChartContainer config={config}>\n <RechartsTreemap\n data={data}\n dataKey={dataKey}\n nameKey=\"name\"\n aspectRatio={aspectRatio}\n isAnimationActive={false}\n content={\n <TreemapTile chartId={chartId} variant={variant} config={config} showLabels={showLabels} />\n }\n {...chartProps}\n >\n {children}\n </RechartsTreemap>\n </ChartContainer>\n </ChartFigure>\n )\n}\n\n/** Every leaf, with the path that names it. */\nfunction flatten(nodes: TreemapNode[], prefix = ''): Record<string, unknown>[] {\n return nodes.flatMap((node) => {\n const path = prefix ? `${prefix} › ${node.name}` : node.name\n if (node.children?.length) return flatten(node.children, path)\n return [{ ...node, path }]\n })\n}\n\ninterface TileProps {\n chartId: string\n variant: TreemapVariant\n config: ChartConfig\n showLabels: boolean\n // Recharts hands the geometry in as loose props on the cloned element.\n x?: number\n y?: number\n width?: number\n height?: number\n depth?: number\n index?: number\n name?: string\n}\n\n/**\n * One tile.\n *\n * The gap between tiles is a surface-coloured stroke rather than a smaller\n * rect, so the tiles still tile — a treemap whose parts do not touch stops\n * reading as a partition of one whole.\n */\nfunction TreemapTile({\n chartId,\n variant,\n config,\n showLabels,\n x = 0,\n y = 0,\n width = 0,\n height = 0,\n depth = 1,\n index = 0,\n name = '',\n}: TileProps) {\n if (width <= 0 || height <= 0) return null\n\n const slot =\n variant === 'nested'\n ? `var(--series-${Math.min(8, depth * 2)})`\n : name in config\n ? `var(--color-${cssName(name)}-0)`\n : `var(--series-${(index % 8) + 1})`\n\n // Only label a tile with room for one. A clipped word is worse than none.\n const roomForLabel = showLabels && width > 56 && height > 26\n\n return (\n <g>\n <rect\n x={x}\n y={y}\n width={width}\n height={height}\n fill={slot}\n stroke=\"var(--chart-surface)\"\n strokeWidth={2}\n />\n {roomForLabel && (\n <text\n x={x + 8}\n y={y + 18}\n className=\"fill-(--chart-surface) text-[11px] font-medium\"\n style={{ pointerEvents: 'none' }}\n >\n {name}\n </text>\n )}\n {/* A hidden id so the tooltip can key off the same name the tile paints. */}\n <title>{name}</title>\n <desc>{chartId}</desc>\n </g>\n )\n}\n\n/** The hover panel. No heading: the tile's own name is the row label. */\nfunction Tooltip({ variant, roundness }: ChartTooltipSlotProps) {\n return (\n <ChartTooltip\n cursor={false}\n content={<ChartTooltipContent nameKey=\"name\" hideLabel roundness={roundness} variant={variant} />}\n />\n )\n}\n\nTreemapChart.Tooltip = Tooltip\n\nexport default TreemapChart\n"],"mappings":";AA4HY,cAoER,YApEQ;AA1HZ,SAAS,aAAkD;AAC3D,SAAS,WAAW,uBAAuB;AAC3C,SAAS,gBAAgB,eAAiC;AAC1D,SAAS,mBAAmB;AAC5B,SAAS,cAAc,2BAA2B;AAyE3C,SAAS,aAAa;AAAA,EAC3B,SAAS,CAAC;AAAA,EACV;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV,UAAU;AAAA,EACV,cAAc,IAAI;AAAA,EAClB,aAAa;AAAA,EACb;AAAA,EACA;AAAA,EACA,gBAAgB;AAClB,GAAsB;AACpB,QAAM,UAAU,MAAM,EAAE,QAAQ,MAAM,EAAE;AAIxC,QAAM,SAAS,QAAQ,IAAI;AAE3B,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,OACE,gBACI,QACA;AAAA,QACE,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,SAAS,CAAC,EAAE,KAAK,SAAS,OAAO,QAAQ,CAAC;AAAA,MAC5C;AAAA,MAGN,8BAAC,kBAAe,QACd;AAAA,QAAC;AAAA;AAAA,UACC;AAAA,UACA;AAAA,UACA,SAAQ;AAAA,UACR;AAAA,UACA,mBAAmB;AAAA,UACnB,SACE,oBAAC,eAAY,SAAkB,SAAkB,QAAgB,YAAwB;AAAA,UAE1F,GAAG;AAAA,UAEH;AAAA;AAAA,MACH,GACF;AAAA;AAAA,EACF;AAEJ;AAGA,SAAS,QAAQ,OAAsB,SAAS,IAA+B;AAC7E,SAAO,MAAM,QAAQ,CAAC,SAAS;AAC7B,UAAM,OAAO,SAAS,GAAG,MAAM,WAAM,KAAK,IAAI,KAAK,KAAK;AACxD,QAAI,KAAK,UAAU,OAAQ,QAAO,QAAQ,KAAK,UAAU,IAAI;AAC7D,WAAO,CAAC,EAAE,GAAG,MAAM,KAAK,CAAC;AAAA,EAC3B,CAAC;AACH;AAwBA,SAAS,YAAY;AAAA,EACnB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,OAAO;AACT,GAAc;AACZ,MAAI,SAAS,KAAK,UAAU,EAAG,QAAO;AAEtC,QAAM,OACJ,YAAY,WACR,gBAAgB,KAAK,IAAI,GAAG,QAAQ,CAAC,CAAC,MACtC,QAAQ,SACN,eAAe,QAAQ,IAAI,CAAC,QAC5B,gBAAiB,QAAQ,IAAK,CAAC;AAGvC,QAAM,eAAe,cAAc,QAAQ,MAAM,SAAS;AAE1D,SACE,qBAAC,OACC;AAAA;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,MAAM;AAAA,QACN,QAAO;AAAA,QACP,aAAa;AAAA;AAAA,IACf;AAAA,IACC,gBACC;AAAA,MAAC;AAAA;AAAA,QACC,GAAG,IAAI;AAAA,QACP,GAAG,IAAI;AAAA,QACP,WAAU;AAAA,QACV,OAAO,EAAE,eAAe,OAAO;AAAA,QAE9B;AAAA;AAAA,IACH;AAAA,IAGF,oBAAC,WAAO,gBAAK;AAAA,IACb,oBAAC,UAAM,mBAAQ;AAAA,KACjB;AAEJ;AAGA,SAAS,QAAQ,EAAE,SAAS,UAAU,GAA0B;AAC9D,SACE;AAAA,IAAC;AAAA;AAAA,MACC,QAAQ;AAAA,MACR,SAAS,oBAAC,uBAAoB,SAAQ,QAAO,WAAS,MAAC,WAAsB,SAAkB;AAAA;AAAA,EACjG;AAEJ;AAEA,aAAa,UAAU;AAEvB,IAAO,uBAAQ;","names":[]}
1
+ {"version":3,"sources":["../../../src/charts/TreemapChart/TreemapChart.tsx"],"sourcesContent":["'use client'\n\nimport { type ComponentProps, type ReactNode } from 'react'\nimport { Treemap as RechartsTreemap } from 'recharts'\nimport { ChartContainer, cssName, type ChartConfig } from '../lib/chart'\nimport { ChartFigure } from '../lib/figure'\nimport { type ChartEmptyProps } from '../lib/empty'\nimport { ChartTooltip, ChartTooltipContent } from '../lib/tooltip'\nimport type { ChartTooltipSlotProps } from '../AreaChart/AreaChart'\n\n/**\n * How a tile is painted.\n *\n * `ramp` walks the series ramp so siblings separate by lightness; `nested`\n * darkens by DEPTH instead, which is the right encoding when the tree has more\n * than one level and the question is \"what is inside what\".\n */\nexport type TreemapVariant = 'ramp' | 'nested'\n\n/** A node in the tree. Children make it a branch. */\nexport interface TreemapNode {\n name: string\n /** Leaf value. Omit on a branch — a branch is the sum of its children. */\n size?: number\n children?: TreemapNode[]\n [key: string]: unknown\n}\n\n/** The raw Recharts Treemap props a consumer may still reach for. */\nexport type TreemapExtras = Omit<ComponentProps<typeof RechartsTreemap>, 'data' | 'dataKey'>\n\nexport interface TreemapChartProps {\n /** Tile names → their label and paint. Optional: the ramp covers unnamed tiles. */\n config?: ChartConfig\n /** The tree. One level is a flat set of tiles; two is a nested treemap. */\n data: TreemapNode[]\n /**\n * What the chart shows, in a sentence a reader could act on. Required, and\n * announced to a screen reader even when it is not printed.\n */\n title: string\n /** Prints the title above the plot instead of hiding it from sight. */\n showTitle?: boolean\n /** A line under the title — the unit, the window, the caveat. */\n description?: ReactNode\n /** Merged onto the figure, last, so a call site can size or space it. */\n className?: string\n variant?: TreemapVariant\n /** The leaf field holding each tile's number. */\n dataKey?: string\n /** How square the tiles are pushed to be. Recharts' own squarify parameter. */\n aspectRatio?: number\n /** Prints each tile's name on it, where the tile is big enough to hold it. */\n showLabels?: boolean\n /** The hover panel. */\n children?: ReactNode\n chartProps?: TreemapExtras\n /** Drops the hidden table view. Only correct when the page prints the data itself. */\n hideDataTable?: boolean\n /**\n * What the chart shows when it has nothing to draw. `false` keeps the empty\n * plot, for a chart whose emptiness is itself the reading.\n */\n empty?: ChartEmptyProps | false\n}\n\n/**\n * Part of a whole, when the whole has too many parts for a pie — and the parts\n * nest.\n *\n * A treemap encodes value as AREA, which the eye reads worse than length but\n * far better than angle, and it is the only form here that stays readable at\n * fifty items. Two rules make it honest: no negative values (an area cannot be\n * negative), and the tiles have to sum to something a reader recognises as the\n * whole.\n *\n * Reach for `<BarChart>` when there are under a dozen items and the ranking\n * matters — a bar's length is the more precise encoding, and a treemap's\n * layout deliberately does not order its tiles by value alone.\n *\n * @example\n * <TreemapChart title=\"Bundle size by package\" data={packages} showLabels>\n * <TreemapChart.Tooltip />\n * </TreemapChart>\n */\nexport function TreemapChart({\n config = {},\n data,\n title,\n showTitle,\n description,\n className,\n variant = 'ramp',\n dataKey = 'size',\n aspectRatio = 4 / 3,\n showLabels = true,\n children,\n chartProps,\n hideDataTable = false,\n empty,\n}: TreemapChartProps) {\n // Flattened for the table view, because a nested tree read aloud row by row\n // is not something anyone can follow — the leaves are the facts.\n const leaves = flatten(data, dataKey)\n\n return (\n <ChartFigure\n title={title}\n showTitle={showTitle}\n description={description}\n className={className}\n table={\n hideDataTable\n ? false\n : {\n rows: leaves,\n rowKey: 'path',\n columns: [{ key: dataKey, label: 'Value' }],\n }\n }\n isEmpty={data.length === 0}\n empty={empty}\n >\n <ChartContainer config={config}>\n <RechartsTreemap\n data={data}\n dataKey={dataKey}\n nameKey=\"name\"\n aspectRatio={aspectRatio}\n isAnimationActive={false}\n content={\n <TreemapTile variant={variant} config={config} showLabels={showLabels} />\n }\n {...chartProps}\n >\n {children}\n </RechartsTreemap>\n </ChartContainer>\n </ChartFigure>\n )\n}\n\n/**\n * Every leaf, with the path that names it and whether the picture can hold it.\n *\n * A tile's AREA is its value, so a leaf at zero or below has no area to be\n * drawn at: the layout gives it no width, the tile is dropped, and the leaf\n * disappears from the picture while staying in this table. The table is the\n * only place left that can say so, and saying nothing would leave the two views\n * of one tree disagreeing about how many leaves there are.\n */\nfunction flatten(\n nodes: TreemapNode[],\n dataKey: string,\n prefix = '',\n): Record<string, unknown>[] {\n return nodes.flatMap((node) => {\n const path = prefix ? `${prefix} › ${node.name}` : node.name\n if (node.children?.length) return flatten(node.children, dataKey, path)\n\n const value = Number(node[dataKey])\n if (Number.isFinite(value) && value > 0) return [{ ...node, path }]\n return [\n {\n ...node,\n path,\n [dataKey]: Number.isFinite(value)\n ? `${value.toLocaleString()} — not drawn`\n : 'not drawn',\n },\n ]\n })\n}\n\ninterface TileProps {\n variant: TreemapVariant\n config: ChartConfig\n showLabels: boolean\n // Recharts hands the geometry in as loose props on the cloned element.\n x?: number\n y?: number\n width?: number\n height?: number\n depth?: number\n index?: number\n name?: string\n}\n\n/**\n * One tile.\n *\n * The gap between tiles is a surface-coloured stroke rather than a smaller\n * rect, so the tiles still tile — a treemap whose parts do not touch stops\n * reading as a partition of one whole.\n */\nfunction TreemapTile({\n variant,\n config,\n showLabels,\n x = 0,\n y = 0,\n width = 0,\n height = 0,\n depth = 1,\n index = 0,\n name = '',\n}: TileProps) {\n if (width <= 0 || height <= 0) return null\n\n const slot =\n variant === 'nested'\n ? `var(--series-${Math.min(8, depth * 2)})`\n : name in config\n ? `var(--color-${cssName(name)}-0)`\n : `var(--series-${(index % 8) + 1})`\n\n // Only label a tile with room for one. A clipped word is worse than none.\n const roomForLabel = showLabels && width > 56 && height > 26\n\n return (\n <g>\n <rect\n x={x}\n y={y}\n width={width}\n height={height}\n fill={slot}\n stroke=\"var(--chart-surface)\"\n strokeWidth={2}\n />\n {roomForLabel && (\n <text\n x={x + 8}\n y={y + 18}\n className=\"fill-(--chart-surface) text-[11px] font-medium\"\n style={{ pointerEvents: 'none' }}\n >\n {name}\n </text>\n )}\n {/* The tile's whole accessible reading. `<desc>` used to carry the chart's\n generated id beside it — and `<desc>` IS the accessible description,\n so every tile announced its name followed by an opaque string nothing\n on the page could explain. */}\n <title>{name}</title>\n </g>\n )\n}\n\n/** The hover panel. No heading: the tile's own name is the row label. */\nfunction Tooltip({ variant, roundness }: ChartTooltipSlotProps) {\n return (\n <ChartTooltip\n cursor={false}\n content={<ChartTooltipContent nameKey=\"name\" hideLabel roundness={roundness} variant={variant} />}\n />\n )\n}\n\nTreemapChart.Tooltip = Tooltip\n\nexport default TreemapChart\n"],"mappings":"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","names":[]}
@@ -17,7 +17,7 @@ import {
17
17
  } from "recharts";
18
18
  import { ChartContainer } from "../lib/chart.js";
19
19
  import { ChartFigure } from "../lib/figure.js";
20
- import { ChartEmpty } from "../lib/empty.js";
20
+ import {} from "../lib/empty.js";
21
21
  import { ChartBackground } from "../lib/background.js";
22
22
  import { ChartTooltip } from "../lib/tooltip.js";
23
23
  import { BarHatchedFill, SeriesGradient } from "../lib/paint.js";
@@ -103,7 +103,9 @@ function WaterfallChart({
103
103
  { key: "total", label: "Running total" }
104
104
  ]
105
105
  },
106
- children: isEmpty ? /* @__PURE__ */ jsx(ChartEmpty, { ...empty || {} }) : /* @__PURE__ */ jsx(ChartContainer, { config, children: /* @__PURE__ */ jsx(
106
+ isEmpty,
107
+ empty,
108
+ children: /* @__PURE__ */ jsx(ChartContainer, { config, children: /* @__PURE__ */ jsx(
107
109
  RechartsBarChart,
108
110
  {
109
111
  id: chartId,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/charts/WaterfallChart/WaterfallChart.tsx"],"sourcesContent":["'use client'\n\nimport {\n createContext,\n useContext,\n useId,\n useMemo,\n type ComponentProps,\n type ReactNode,\n} from 'react'\nimport {\n Bar as RechartsBar,\n BarChart as RechartsBarChart,\n CartesianGrid,\n ReferenceLine as RechartsReferenceLine,\n XAxis as RechartsXAxis,\n YAxis as RechartsYAxis,\n usePlotArea,\n} from 'recharts'\nimport { ChartContainer, type ChartConfig } from '../lib/chart'\nimport { ChartFigure } from '../lib/figure'\nimport { ChartEmpty, type ChartEmptyProps } from '../lib/empty'\nimport { ChartBackground, type ChartBackgroundVariant } from '../lib/background'\nimport { ChartTooltip } from '../lib/tooltip'\nimport { BarHatchedFill, SeriesGradient } from '../lib/paint'\nimport { Annotation, ReferenceBand, ReferenceLine } from '../lib/annotations'\nimport { axisLabel } from '../lib/axis'\nimport { defaultTick } from '../lib/format'\nimport { cn } from '../../lib/cn'\nimport type { ChartXAxisProps, ChartYAxisProps } from '../BarChart/BarChart'\nimport type { ChartTooltipSlotProps } from '../AreaChart/AreaChart'\n\n/** The series a chart with only one of them falls back to. */\nconst DEFAULT_CONFIG: ChartConfig = { value: { label: 'Change' } }\n\nconst CHART_MARGIN = { top: 20, right: 12, bottom: 0, left: 12 }\n\n/** The corner every bar rounds to. */\nconst BAR_RADIUS = 2\n\n/** How a step moves the running total. */\nexport type WaterfallStepType = 'delta' | 'total'\n\n/** Which way a bar goes, which is what its texture encodes. */\nexport type WaterfallDirection = 'increase' | 'decrease' | 'total'\n\nexport interface WaterfallStep {\n /** The step's name on the category axis. Doubles as its key. */\n name: string\n /**\n * The signed change this step makes, or — on a `total` step — the absolute\n * figure the running total is set to.\n *\n * Omit it on a `total` and the running total is used, which is the right\n * default for the closing bar: an end total typed by hand can disagree with\n * the deltas above it, and the chart would draw the disagreement without\n * saying anything.\n */\n value?: number\n /**\n * `delta` (the default) adds `value` to the running total and floats the bar\n * where that lands. `total` plants a bar on the baseline — an opening\n * balance, a subtotal, a closing figure.\n */\n type?: WaterfallStepType\n}\n\n/** A step with its span, its direction and its running total worked out. */\ninterface ResolvedStep extends WaterfallStep {\n type: WaterfallStepType\n /**\n * What this step moved the total by. On a `total` step that is the gap it\n * closes — zero whenever the deltas above it already add up to it.\n */\n change: number\n /** The running total once this step has been applied. */\n total: number\n /**\n * The bar's two ends on the value axis, LOW first.\n *\n * Ascending because Recharts reads a floating bar's range that way and\n * collapses a descending one to nothing — which is every decrease in the\n * cascade. Which end the running total is at is recovered from `total`\n * rather than from the order here.\n */\n span: [number, number]\n direction: WaterfallDirection\n}\n\n/** A bar's two ends, low first — see `ResolvedStep.span`. */\nfunction ascending(a: number, b: number): [number, number] {\n return a <= b ? [a, b] : [b, a]\n}\n\n/** The steps, walked in order, each carrying the total it leaves behind. */\nfunction resolve(data: WaterfallStep[]): ResolvedStep[] {\n let running = 0\n\n return data.map((step) => {\n const type = step.type ?? 'delta'\n\n if (type === 'total') {\n const total = step.value ?? running\n const resolved: ResolvedStep = {\n ...step,\n type,\n change: total - running,\n total,\n span: ascending(0, total),\n direction: 'total',\n }\n running = total\n return resolved\n }\n\n const change = step.value ?? 0\n const from = running\n running += change\n\n return {\n ...step,\n type,\n change,\n total: running,\n span: ascending(from, running),\n direction: change < 0 ? 'decrease' : 'increase',\n }\n })\n}\n\ninterface WaterfallContextValue {\n config: ChartConfig\n seriesKey: string\n steps: ResolvedStep[]\n formatValue: (value: number) => string\n}\n\nconst WaterfallContext = createContext<WaterfallContextValue | null>(null)\n\nfunction useWaterfall(): WaterfallContextValue {\n const context = useContext(WaterfallContext)\n if (!context) throw new Error('A waterfall part must be rendered inside <WaterfallChart>')\n return context\n}\n\nexport interface WaterfallChartProps {\n /**\n * The single series — its label and its paint. Only the FIRST entry is read;\n * a waterfall has one quantity and as many bars as it has steps.\n */\n config?: ChartConfig\n /**\n * The steps, in the order they are applied. Order is the arithmetic: the\n * chart does not sort, because a different order is a different total at\n * every intermediate bar.\n */\n data: WaterfallStep[]\n /**\n * What the chart shows, in a sentence a reader could act on. Required, and\n * announced to a screen reader even when it is not printed.\n */\n title: string\n /** Prints the title above the plot instead of hiding it from sight. */\n showTitle?: boolean\n /** A line under the title — the unit, the window, the caveat. */\n description?: ReactNode\n /** The composed parts — axes, grid, tooltip, and `<WaterfallChart.Bars>`. */\n children: ReactNode\n /** Merged onto the figure, last, so a call site can size or space it. */\n className?: string\n /** Escape hatch onto the raw Recharts chart element. */\n chartProps?: ComponentProps<typeof RechartsBarChart>\n /** Formats every number the chart prints — ticks, labels, tooltip, table. */\n formatValue?: (value: number) => string\n /** Drops the hidden table view. Only correct when the page prints the data itself. */\n hideDataTable?: boolean\n /**\n * What the chart shows when it has nothing to draw. `false` keeps the axes,\n * for a chart whose emptiness is itself the reading.\n */\n empty?: ChartEmptyProps | false\n}\n\n/**\n * How a total got from one figure to another — an opening balance, the signed\n * contributions that moved it, and where it closed.\n *\n * The form for \"why did this change\", which a pair of bars cannot answer and a\n * pie chart answers wrongly, because contributions can be NEGATIVE and a slice\n * cannot. Reach for `<BarChart>` when the parts do not have to add up to the\n * gap between two totals, and for `<FunnelChart>` when the quantity only ever\n * shrinks.\n *\n * **The connectors are the claim to be careful about.** They draw the steps as\n * a sequence — this happened, then this — and most breakdowns are not\n * sequential at all: churn and expansion in the same month are simultaneous,\n * and the order they are listed in is an editorial choice. The arithmetic\n * survives any order; the STORY does not, and a reader will take the leftmost\n * bar as the first cause. Two related traps: the intermediate bars are floating\n * lengths read against no baseline, so a small step high up the cascade is hard\n * to compare with a large one near zero; and any step that is itself a net of\n * two larger opposing movements is invisible as such. Where the order is\n * arbitrary, say so in the `description`.\n *\n * Recharts earns its place for the axes, grid and tooltip. Each bar is a\n * floating range — from the running total to the new one — with a custom shape\n * over it, because Recharts has no waterfall mark and the connectors have to be\n * drawn from the same geometry as the bars they join.\n *\n * @example\n * <WaterfallChart title=\"Revenue bridge, FY25\" data={steps}>\n * <WaterfallChart.Grid />\n * <WaterfallChart.XAxis />\n * <WaterfallChart.YAxis label=\"AUD\" />\n * <WaterfallChart.Tooltip />\n * <WaterfallChart.Bars showValues />\n * </WaterfallChart>\n *\n * @example\n * const steps = [\n * { name: 'FY24', value: 1200, type: 'total' as const },\n * { name: 'New', value: 340 },\n * { name: 'Churn', value: -180 },\n * // No value: the closing bar is whatever the deltas add up to.\n * { name: 'FY25', type: 'total' as const },\n * ]\n */\nexport function WaterfallChart({\n config = DEFAULT_CONFIG,\n data,\n title,\n showTitle,\n description,\n children,\n className,\n chartProps,\n formatValue = defaultTick,\n hideDataTable = false,\n empty,\n}: WaterfallChartProps) {\n const chartId = useId().replace(/:/g, '')\n const seriesKey = Object.keys(config)[0] ?? 'value'\n const steps = useMemo(() => resolve(data), [data])\n\n const context = useMemo<WaterfallContextValue>(\n () => ({ config, seriesKey, steps, formatValue }),\n [config, seriesKey, steps, formatValue],\n )\n\n const isEmpty = empty !== false && steps.length === 0\n\n return (\n <WaterfallContext.Provider value={context}>\n <ChartFigure\n title={title}\n showTitle={showTitle}\n description={description}\n className={className}\n table={\n hideDataTable\n ? false\n : {\n rows: steps as unknown as Record<string, unknown>[],\n rowKey: 'name',\n columns: [\n { key: 'change', label: 'Change' },\n { key: 'total', label: 'Running total' },\n ],\n }\n }\n >\n {isEmpty ? (\n <ChartEmpty {...(empty || {})} />\n ) : (\n <ChartContainer config={config}>\n <RechartsBarChart\n id={chartId}\n accessibilityLayer\n margin={CHART_MARGIN}\n data={steps}\n barCategoryGap=\"25%\"\n {...chartProps}\n >\n {children}\n </RechartsBarChart>\n </ChartContainer>\n )}\n </ChartFigure>\n </WaterfallContext.Provider>\n )\n}\n\nexport interface WaterfallBarsProps {\n /** The bars' corner. */\n radius?: number\n /**\n * Joins each bar's closing edge to where the next one starts.\n *\n * On by default: without them a waterfall is a row of bars floating at\n * unrelated heights, and the cascade — which is the entire point of the form\n * — has to be reconstructed by the reader.\n */\n connectors?: boolean\n /**\n * Prints each step's signed change above or below its bar.\n *\n * Worth more here than on most charts: an intermediate bar has no baseline\n * under it, so its length is the one thing a reader cannot get off the axis.\n */\n showValues?: boolean\n /** Escape hatch onto the underlying Recharts bar. */\n barProps?: ComponentProps<typeof RechartsBar>\n}\n\n/**\n * The bars and the connectors between them.\n *\n * Increases and totals take the solid series fill; decreases take the 45°\n * hatch. Texture rather than a second colour, because the whole set has to stay\n * legible in greyscale and under forced colours — and a total is told from an\n * increase by geometry instead, since it is the only kind of bar that stands on\n * the baseline.\n */\nfunction Bars({\n radius = BAR_RADIUS,\n connectors = true,\n showValues = false,\n barProps,\n}: WaterfallBarsProps) {\n const { config, seriesKey, steps, formatValue } = useWaterfall()\n const id = useId().replace(/:/g, '')\n\n const shared = {\n id,\n seriesKey,\n radius,\n connectors,\n showValues,\n stepCount: steps.length,\n formatValue,\n }\n\n return (\n <>\n {/* The baseline the totals stand on. A waterfall without a visible zero\n asks the reader to take the floating bars on trust. */}\n <RechartsReferenceLine y={0} stroke=\"var(--chart-grid)\" strokeWidth={1} />\n <RechartsBar\n dataKey={(row: unknown) => (row as ResolvedStep).span}\n name={seriesKey}\n isAnimationActive={false}\n fill=\"transparent\"\n shape={(props: unknown) => <StepShape {...(props as StepGeometry)} {...shared} />}\n activeBar={(props: unknown) => <StepShape {...(props as StepGeometry)} {...shared} />}\n {...barProps}\n />\n <defs>\n <SeriesGradient id={id} dataKey={seriesKey} config={config} direction=\"vertical\" />\n <BarHatchedFill id={id} dataKey={seriesKey} />\n </defs>\n </>\n )\n}\n\n/** The geometry Recharts hands a custom bar shape, plus the step behind it. */\ninterface StepGeometry {\n x?: number\n y?: number\n width?: number\n height?: number\n index?: number\n payload?: ResolvedStep\n}\n\ninterface StepShapeProps extends StepGeometry {\n id: string\n seriesKey: string\n radius: number\n connectors: boolean\n showValues: boolean\n stepCount: number\n formatValue: (value: number) => string\n}\n\n/** One step: its bar, its connector to the next step, and its label. */\nfunction StepShape({\n x = 0,\n y = 0,\n width = 0,\n height = 0,\n index = -1,\n payload,\n id,\n seriesKey,\n radius,\n connectors,\n showValues,\n stepCount,\n formatValue,\n}: StepShapeProps) {\n const plot = usePlotArea()\n\n if (!payload) return null\n\n const step = payload\n const fill =\n step.direction === 'decrease'\n ? `url(#${id}-hatched-${seriesKey})`\n : `url(#${id}-colors-${seriesKey})`\n\n // Which edge of the rect the running total lands on. Everything downstream —\n // the connector's height, where the label sits — hangs off this one fact,\n // and it is a comparison rather than a re-derived scale so it stays right\n // whatever domain the axis ends up with.\n const closesAtTop = step.total >= step.span[1]\n const closingY = closesAtTop ? y : y + height\n\n // A zero-change step has no rect to see. It still has to appear, or the\n // cascade silently loses a category and the axis stops matching the bars.\n const drawnHeight = Math.max(height, 1)\n\n // Recharts leaves a gap between category bands, and the connector has to\n // cross it. One band is the plot's width over the number of steps, so the\n // next bar's near edge is exactly one band along from this one's.\n const band = plot && stepCount > 0 ? plot.width / stepCount : width\n const hasNext = index >= 0 && index < stepCount - 1\n\n return (\n <g>\n {connectors && hasNext && (\n <line\n x1={x + width}\n y1={closingY}\n x2={x + band}\n y2={closingY}\n stroke=\"var(--chart-cursor)\"\n strokeWidth={1}\n strokeDasharray=\"3 3\"\n />\n )}\n <rect x={x} y={y} width={width} height={drawnHeight} rx={radius || undefined} fill={fill} />\n {showValues && (\n <text\n x={x + width / 2}\n // Above a rise, below a fall — so the label never sits inside the bar\n // it is describing and never collides with the connector leaving it.\n y={closesAtTop ? y - 6 : y + drawnHeight + 13}\n textAnchor=\"middle\"\n className=\"fill-(--ink) font-mono text-[11px] font-medium tabular-nums\"\n style={{ pointerEvents: 'none' }}\n >\n {step.direction === 'total'\n ? formatValue(step.total)\n : `${step.change > 0 ? '+' : ''}${formatValue(step.change)}`}\n </text>\n )}\n </g>\n )\n}\n\n/** The step axis. */\nfunction XAxis({\n dataKey = 'name',\n tickLine = false,\n axisLine = false,\n tickMargin = 8,\n minTickGap = 8,\n label,\n ...rest\n}: ChartXAxisProps) {\n return (\n <RechartsXAxis\n dataKey={dataKey}\n tickLine={tickLine}\n axisLine={axisLine}\n tickMargin={tickMargin}\n minTickGap={minTickGap}\n {...rest}\n >\n {axisLabel(label, 'x')}\n </RechartsXAxis>\n )\n}\n\n/** The value axis. */\nfunction YAxis({\n tickLine = false,\n axisLine = false,\n tickMargin = 8,\n width = 'auto',\n label,\n tickFormatter,\n ...rest\n}: ChartYAxisProps) {\n const { formatValue } = useWaterfall()\n\n return (\n <RechartsYAxis\n tickLine={tickLine}\n axisLine={axisLine}\n tickMargin={tickMargin}\n width={width}\n tickFormatter={tickFormatter ?? formatValue}\n {...rest}\n >\n {axisLabel(label, 'y')}\n </RechartsYAxis>\n )\n}\n\n/** Rules across the value axis. */\nfunction Grid({\n strokeDasharray = '3 3',\n vertical = false,\n ...rest\n}: ComponentProps<typeof CartesianGrid>) {\n return <CartesianGrid strokeDasharray={strokeDasharray} vertical={vertical} {...rest} />\n}\n\n/**\n * The hover panel, printing the change AND the total it leaves behind.\n *\n * The generic tooltip would print the bar's raw span, which is a pair of\n * running totals and reads as two unrelated numbers. Both facts a waterfall\n * carries — what this step did, and where the total stood afterwards — are\n * named here instead.\n */\nfunction Tooltip({ variant = 'solid', roundness = 'lg', defaultIndex }: ChartTooltipSlotProps) {\n const { formatValue } = useWaterfall()\n\n return (\n <ChartTooltip\n cursor={false}\n defaultIndex={defaultIndex}\n content={(props) => {\n const { active, payload } = props as unknown as {\n active?: boolean\n payload?: { payload?: ResolvedStep }[]\n }\n const step = payload?.[0]?.payload\n if (!active || !step) return <span className=\"p-4\" />\n\n return (\n <div\n className={cn(\n 'grid min-w-40 gap-1.5 border border-(--rule-2) px-2.5 py-1.5 text-xs',\n roundness === 'sm' && 'rounded-(--radius-sm)',\n roundness === 'md' && 'rounded-(--radius)',\n roundness === 'lg' && 'rounded-(--radius-lg)',\n variant === 'frosted'\n ? 'bg-(--chart-surface)/75 backdrop-blur-sm'\n : 'bg-(--chart-surface)',\n )}\n >\n <div className=\"font-medium text-(--ink)\">{step.name}</div>\n {step.direction !== 'total' && (\n <div className=\"flex justify-between gap-4 leading-none\">\n <span className=\"text-(--ink-3-aa)\">Change</span>\n <span className=\"font-mono font-medium tabular-nums text-(--ink)\">\n {step.change > 0 ? '+' : ''}\n {formatValue(step.change)}\n </span>\n </div>\n )}\n <div className=\"flex justify-between gap-4 leading-none\">\n <span className=\"text-(--ink-3-aa)\">\n {step.direction === 'total' ? 'Total' : 'Running total'}\n </span>\n <span className=\"font-mono font-medium tabular-nums text-(--ink)\">\n {formatValue(step.total)}\n </span>\n </div>\n </div>\n )\n }}\n />\n )\n}\n\n/** The decorative plate behind the plot. */\nfunction Background({ variant }: { variant: ChartBackgroundVariant }) {\n return <ChartBackground variant={variant} />\n}\n\nWaterfallChart.Bars = Bars\nWaterfallChart.XAxis = XAxis\nWaterfallChart.YAxis = YAxis\nWaterfallChart.Grid = Grid\nWaterfallChart.Tooltip = Tooltip\nWaterfallChart.Background = Background\nWaterfallChart.ReferenceLine = ReferenceLine\nWaterfallChart.ReferenceBand = ReferenceBand\nWaterfallChart.Annotation = Annotation\n\nexport default WaterfallChart\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/charts/WaterfallChart/WaterfallChart.tsx"],"sourcesContent":["'use client'\n\nimport {\n createContext,\n useContext,\n useId,\n useMemo,\n type ComponentProps,\n type ReactNode,\n} from 'react'\nimport {\n Bar as RechartsBar,\n BarChart as RechartsBarChart,\n CartesianGrid,\n ReferenceLine as RechartsReferenceLine,\n XAxis as RechartsXAxis,\n YAxis as RechartsYAxis,\n usePlotArea,\n} from 'recharts'\nimport { ChartContainer, type ChartConfig } from '../lib/chart'\nimport { ChartFigure } from '../lib/figure'\nimport { type ChartEmptyProps } from '../lib/empty'\nimport { ChartBackground, type ChartBackgroundVariant } from '../lib/background'\nimport { ChartTooltip } from '../lib/tooltip'\nimport { BarHatchedFill, SeriesGradient } from '../lib/paint'\nimport { Annotation, ReferenceBand, ReferenceLine } from '../lib/annotations'\nimport { axisLabel } from '../lib/axis'\nimport { defaultTick } from '../lib/format'\nimport { cn } from '../../lib/cn'\nimport type { ChartXAxisProps, ChartYAxisProps } from '../BarChart/BarChart'\nimport type { ChartTooltipSlotProps } from '../AreaChart/AreaChart'\n\n/** The series a chart with only one of them falls back to. */\nconst DEFAULT_CONFIG: ChartConfig = { value: { label: 'Change' } }\n\nconst CHART_MARGIN = { top: 20, right: 12, bottom: 0, left: 12 }\n\n/** The corner every bar rounds to. */\nconst BAR_RADIUS = 2\n\n/** How a step moves the running total. */\nexport type WaterfallStepType = 'delta' | 'total'\n\n/** Which way a bar goes, which is what its texture encodes. */\nexport type WaterfallDirection = 'increase' | 'decrease' | 'total'\n\nexport interface WaterfallStep {\n /** The step's name on the category axis. Doubles as its key. */\n name: string\n /**\n * The signed change this step makes, or — on a `total` step — the absolute\n * figure the running total is set to.\n *\n * Omit it on a `total` and the running total is used, which is the right\n * default for the closing bar: an end total typed by hand can disagree with\n * the deltas above it, and the chart would draw the disagreement without\n * saying anything.\n */\n value?: number\n /**\n * `delta` (the default) adds `value` to the running total and floats the bar\n * where that lands. `total` plants a bar on the baseline — an opening\n * balance, a subtotal, a closing figure.\n */\n type?: WaterfallStepType\n}\n\n/** A step with its span, its direction and its running total worked out. */\ninterface ResolvedStep extends WaterfallStep {\n type: WaterfallStepType\n /**\n * What this step moved the total by. On a `total` step that is the gap it\n * closes — zero whenever the deltas above it already add up to it.\n */\n change: number\n /** The running total once this step has been applied. */\n total: number\n /**\n * The bar's two ends on the value axis, LOW first.\n *\n * Ascending because Recharts reads a floating bar's range that way and\n * collapses a descending one to nothing — which is every decrease in the\n * cascade. Which end the running total is at is recovered from `total`\n * rather than from the order here.\n */\n span: [number, number]\n direction: WaterfallDirection\n}\n\n/** A bar's two ends, low first — see `ResolvedStep.span`. */\nfunction ascending(a: number, b: number): [number, number] {\n return a <= b ? [a, b] : [b, a]\n}\n\n/** The steps, walked in order, each carrying the total it leaves behind. */\nfunction resolve(data: WaterfallStep[]): ResolvedStep[] {\n let running = 0\n\n return data.map((step) => {\n const type = step.type ?? 'delta'\n\n if (type === 'total') {\n const total = step.value ?? running\n const resolved: ResolvedStep = {\n ...step,\n type,\n change: total - running,\n total,\n span: ascending(0, total),\n direction: 'total',\n }\n running = total\n return resolved\n }\n\n const change = step.value ?? 0\n const from = running\n running += change\n\n return {\n ...step,\n type,\n change,\n total: running,\n span: ascending(from, running),\n direction: change < 0 ? 'decrease' : 'increase',\n }\n })\n}\n\ninterface WaterfallContextValue {\n config: ChartConfig\n seriesKey: string\n steps: ResolvedStep[]\n formatValue: (value: number) => string\n}\n\nconst WaterfallContext = createContext<WaterfallContextValue | null>(null)\n\nfunction useWaterfall(): WaterfallContextValue {\n const context = useContext(WaterfallContext)\n if (!context) throw new Error('A waterfall part must be rendered inside <WaterfallChart>')\n return context\n}\n\nexport interface WaterfallChartProps {\n /**\n * The single series — its label and its paint. Only the FIRST entry is read;\n * a waterfall has one quantity and as many bars as it has steps.\n */\n config?: ChartConfig\n /**\n * The steps, in the order they are applied. Order is the arithmetic: the\n * chart does not sort, because a different order is a different total at\n * every intermediate bar.\n */\n data: WaterfallStep[]\n /**\n * What the chart shows, in a sentence a reader could act on. Required, and\n * announced to a screen reader even when it is not printed.\n */\n title: string\n /** Prints the title above the plot instead of hiding it from sight. */\n showTitle?: boolean\n /** A line under the title — the unit, the window, the caveat. */\n description?: ReactNode\n /** The composed parts — axes, grid, tooltip, and `<WaterfallChart.Bars>`. */\n children: ReactNode\n /** Merged onto the figure, last, so a call site can size or space it. */\n className?: string\n /** Escape hatch onto the raw Recharts chart element. */\n chartProps?: ComponentProps<typeof RechartsBarChart>\n /** Formats every number the chart prints — ticks, labels, tooltip, table. */\n formatValue?: (value: number) => string\n /** Drops the hidden table view. Only correct when the page prints the data itself. */\n hideDataTable?: boolean\n /**\n * What the chart shows when it has nothing to draw. `false` keeps the axes,\n * for a chart whose emptiness is itself the reading.\n */\n empty?: ChartEmptyProps | false\n}\n\n/**\n * How a total got from one figure to another — an opening balance, the signed\n * contributions that moved it, and where it closed.\n *\n * The form for \"why did this change\", which a pair of bars cannot answer and a\n * pie chart answers wrongly, because contributions can be NEGATIVE and a slice\n * cannot. Reach for `<BarChart>` when the parts do not have to add up to the\n * gap between two totals, and for `<FunnelChart>` when the quantity only ever\n * shrinks.\n *\n * **The connectors are the claim to be careful about.** They draw the steps as\n * a sequence — this happened, then this — and most breakdowns are not\n * sequential at all: churn and expansion in the same month are simultaneous,\n * and the order they are listed in is an editorial choice. The arithmetic\n * survives any order; the STORY does not, and a reader will take the leftmost\n * bar as the first cause. Two related traps: the intermediate bars are floating\n * lengths read against no baseline, so a small step high up the cascade is hard\n * to compare with a large one near zero; and any step that is itself a net of\n * two larger opposing movements is invisible as such. Where the order is\n * arbitrary, say so in the `description`.\n *\n * Recharts earns its place for the axes, grid and tooltip. Each bar is a\n * floating range — from the running total to the new one — with a custom shape\n * over it, because Recharts has no waterfall mark and the connectors have to be\n * drawn from the same geometry as the bars they join.\n *\n * @example\n * <WaterfallChart title=\"Revenue bridge, FY25\" data={steps}>\n * <WaterfallChart.Grid />\n * <WaterfallChart.XAxis />\n * <WaterfallChart.YAxis label=\"AUD\" />\n * <WaterfallChart.Tooltip />\n * <WaterfallChart.Bars showValues />\n * </WaterfallChart>\n *\n * @example\n * const steps = [\n * { name: 'FY24', value: 1200, type: 'total' as const },\n * { name: 'New', value: 340 },\n * { name: 'Churn', value: -180 },\n * // No value: the closing bar is whatever the deltas add up to.\n * { name: 'FY25', type: 'total' as const },\n * ]\n */\nexport function WaterfallChart({\n config = DEFAULT_CONFIG,\n data,\n title,\n showTitle,\n description,\n children,\n className,\n chartProps,\n formatValue = defaultTick,\n hideDataTable = false,\n empty,\n}: WaterfallChartProps) {\n const chartId = useId().replace(/:/g, '')\n const seriesKey = Object.keys(config)[0] ?? 'value'\n const steps = useMemo(() => resolve(data), [data])\n\n const context = useMemo<WaterfallContextValue>(\n () => ({ config, seriesKey, steps, formatValue }),\n [config, seriesKey, steps, formatValue],\n )\n\n const isEmpty = empty !== false && steps.length === 0\n\n return (\n <WaterfallContext.Provider value={context}>\n <ChartFigure\n title={title}\n showTitle={showTitle}\n description={description}\n className={className}\n table={\n hideDataTable\n ? false\n : {\n rows: steps as unknown as Record<string, unknown>[],\n rowKey: 'name',\n columns: [\n { key: 'change', label: 'Change' },\n { key: 'total', label: 'Running total' },\n ],\n }\n }\n isEmpty={isEmpty}\n empty={empty}\n >\n <ChartContainer config={config}>\n <RechartsBarChart\n id={chartId}\n accessibilityLayer\n margin={CHART_MARGIN}\n data={steps}\n barCategoryGap=\"25%\"\n {...chartProps}\n >\n {children}\n </RechartsBarChart>\n </ChartContainer>\n </ChartFigure>\n </WaterfallContext.Provider>\n )\n}\n\nexport interface WaterfallBarsProps {\n /** The bars' corner. */\n radius?: number\n /**\n * Joins each bar's closing edge to where the next one starts.\n *\n * On by default: without them a waterfall is a row of bars floating at\n * unrelated heights, and the cascade — which is the entire point of the form\n * — has to be reconstructed by the reader.\n */\n connectors?: boolean\n /**\n * Prints each step's signed change above or below its bar.\n *\n * Worth more here than on most charts: an intermediate bar has no baseline\n * under it, so its length is the one thing a reader cannot get off the axis.\n */\n showValues?: boolean\n /** Escape hatch onto the underlying Recharts bar. */\n barProps?: ComponentProps<typeof RechartsBar>\n}\n\n/**\n * The bars and the connectors between them.\n *\n * Increases and totals take the solid series fill; decreases take the 45°\n * hatch. Texture rather than a second colour, because the whole set has to stay\n * legible in greyscale and under forced colours — and a total is told from an\n * increase by geometry instead, since it is the only kind of bar that stands on\n * the baseline.\n */\nfunction Bars({\n radius = BAR_RADIUS,\n connectors = true,\n showValues = false,\n barProps,\n}: WaterfallBarsProps) {\n const { config, seriesKey, steps, formatValue } = useWaterfall()\n const id = useId().replace(/:/g, '')\n\n const shared = {\n id,\n seriesKey,\n radius,\n connectors,\n showValues,\n stepCount: steps.length,\n formatValue,\n }\n\n return (\n <>\n {/* The baseline the totals stand on. A waterfall without a visible zero\n asks the reader to take the floating bars on trust. */}\n <RechartsReferenceLine y={0} stroke=\"var(--chart-grid)\" strokeWidth={1} />\n <RechartsBar\n dataKey={(row: unknown) => (row as ResolvedStep).span}\n name={seriesKey}\n isAnimationActive={false}\n fill=\"transparent\"\n shape={(props: unknown) => <StepShape {...(props as StepGeometry)} {...shared} />}\n activeBar={(props: unknown) => <StepShape {...(props as StepGeometry)} {...shared} />}\n {...barProps}\n />\n <defs>\n <SeriesGradient id={id} dataKey={seriesKey} config={config} direction=\"vertical\" />\n <BarHatchedFill id={id} dataKey={seriesKey} />\n </defs>\n </>\n )\n}\n\n/** The geometry Recharts hands a custom bar shape, plus the step behind it. */\ninterface StepGeometry {\n x?: number\n y?: number\n width?: number\n height?: number\n index?: number\n payload?: ResolvedStep\n}\n\ninterface StepShapeProps extends StepGeometry {\n id: string\n seriesKey: string\n radius: number\n connectors: boolean\n showValues: boolean\n stepCount: number\n formatValue: (value: number) => string\n}\n\n/** One step: its bar, its connector to the next step, and its label. */\nfunction StepShape({\n x = 0,\n y = 0,\n width = 0,\n height = 0,\n index = -1,\n payload,\n id,\n seriesKey,\n radius,\n connectors,\n showValues,\n stepCount,\n formatValue,\n}: StepShapeProps) {\n const plot = usePlotArea()\n\n if (!payload) return null\n\n const step = payload\n const fill =\n step.direction === 'decrease'\n ? `url(#${id}-hatched-${seriesKey})`\n : `url(#${id}-colors-${seriesKey})`\n\n // Which edge of the rect the running total lands on. Everything downstream —\n // the connector's height, where the label sits — hangs off this one fact,\n // and it is a comparison rather than a re-derived scale so it stays right\n // whatever domain the axis ends up with.\n const closesAtTop = step.total >= step.span[1]\n const closingY = closesAtTop ? y : y + height\n\n // A zero-change step has no rect to see. It still has to appear, or the\n // cascade silently loses a category and the axis stops matching the bars.\n const drawnHeight = Math.max(height, 1)\n\n // Recharts leaves a gap between category bands, and the connector has to\n // cross it. One band is the plot's width over the number of steps, so the\n // next bar's near edge is exactly one band along from this one's.\n const band = plot && stepCount > 0 ? plot.width / stepCount : width\n const hasNext = index >= 0 && index < stepCount - 1\n\n return (\n <g>\n {connectors && hasNext && (\n <line\n x1={x + width}\n y1={closingY}\n x2={x + band}\n y2={closingY}\n stroke=\"var(--chart-cursor)\"\n strokeWidth={1}\n strokeDasharray=\"3 3\"\n />\n )}\n <rect x={x} y={y} width={width} height={drawnHeight} rx={radius || undefined} fill={fill} />\n {showValues && (\n <text\n x={x + width / 2}\n // Above a rise, below a fall — so the label never sits inside the bar\n // it is describing and never collides with the connector leaving it.\n y={closesAtTop ? y - 6 : y + drawnHeight + 13}\n textAnchor=\"middle\"\n className=\"fill-(--ink) font-mono text-[11px] font-medium tabular-nums\"\n style={{ pointerEvents: 'none' }}\n >\n {step.direction === 'total'\n ? formatValue(step.total)\n : `${step.change > 0 ? '+' : ''}${formatValue(step.change)}`}\n </text>\n )}\n </g>\n )\n}\n\n/** The step axis. */\nfunction XAxis({\n dataKey = 'name',\n tickLine = false,\n axisLine = false,\n tickMargin = 8,\n minTickGap = 8,\n label,\n ...rest\n}: ChartXAxisProps) {\n return (\n <RechartsXAxis\n dataKey={dataKey}\n tickLine={tickLine}\n axisLine={axisLine}\n tickMargin={tickMargin}\n minTickGap={minTickGap}\n {...rest}\n >\n {axisLabel(label, 'x')}\n </RechartsXAxis>\n )\n}\n\n/** The value axis. */\nfunction YAxis({\n tickLine = false,\n axisLine = false,\n tickMargin = 8,\n width = 'auto',\n label,\n tickFormatter,\n ...rest\n}: ChartYAxisProps) {\n const { formatValue } = useWaterfall()\n\n return (\n <RechartsYAxis\n tickLine={tickLine}\n axisLine={axisLine}\n tickMargin={tickMargin}\n width={width}\n tickFormatter={tickFormatter ?? formatValue}\n {...rest}\n >\n {axisLabel(label, 'y')}\n </RechartsYAxis>\n )\n}\n\n/** Rules across the value axis. */\nfunction Grid({\n strokeDasharray = '3 3',\n vertical = false,\n ...rest\n}: ComponentProps<typeof CartesianGrid>) {\n return <CartesianGrid strokeDasharray={strokeDasharray} vertical={vertical} {...rest} />\n}\n\n/**\n * The hover panel, printing the change AND the total it leaves behind.\n *\n * The generic tooltip would print the bar's raw span, which is a pair of\n * running totals and reads as two unrelated numbers. Both facts a waterfall\n * carries — what this step did, and where the total stood afterwards — are\n * named here instead.\n */\nfunction Tooltip({ variant = 'solid', roundness = 'lg', defaultIndex }: ChartTooltipSlotProps) {\n const { formatValue } = useWaterfall()\n\n return (\n <ChartTooltip\n cursor={false}\n defaultIndex={defaultIndex}\n content={(props) => {\n const { active, payload } = props as unknown as {\n active?: boolean\n payload?: { payload?: ResolvedStep }[]\n }\n const step = payload?.[0]?.payload\n if (!active || !step) return <span className=\"p-4\" />\n\n return (\n <div\n className={cn(\n 'grid min-w-40 gap-1.5 border border-(--rule-2) px-2.5 py-1.5 text-xs',\n roundness === 'sm' && 'rounded-(--radius-sm)',\n roundness === 'md' && 'rounded-(--radius)',\n roundness === 'lg' && 'rounded-(--radius-lg)',\n variant === 'frosted'\n ? 'bg-(--chart-surface)/75 backdrop-blur-sm'\n : 'bg-(--chart-surface)',\n )}\n >\n <div className=\"font-medium text-(--ink)\">{step.name}</div>\n {step.direction !== 'total' && (\n <div className=\"flex justify-between gap-4 leading-none\">\n <span className=\"text-(--ink-3-aa)\">Change</span>\n <span className=\"font-mono font-medium tabular-nums text-(--ink)\">\n {step.change > 0 ? '+' : ''}\n {formatValue(step.change)}\n </span>\n </div>\n )}\n <div className=\"flex justify-between gap-4 leading-none\">\n <span className=\"text-(--ink-3-aa)\">\n {step.direction === 'total' ? 'Total' : 'Running total'}\n </span>\n <span className=\"font-mono font-medium tabular-nums text-(--ink)\">\n {formatValue(step.total)}\n </span>\n </div>\n </div>\n )\n }}\n />\n )\n}\n\n/** The decorative plate behind the plot. */\nfunction Background({ variant }: { variant: ChartBackgroundVariant }) {\n return <ChartBackground variant={variant} />\n}\n\nWaterfallChart.Bars = Bars\nWaterfallChart.XAxis = XAxis\nWaterfallChart.YAxis = YAxis\nWaterfallChart.Grid = Grid\nWaterfallChart.Tooltip = Tooltip\nWaterfallChart.Background = Background\nWaterfallChart.ReferenceLine = ReferenceLine\nWaterfallChart.ReferenceBand = ReferenceBand\nWaterfallChart.Annotation = Annotation\n\nexport default WaterfallChart\n"],"mappings":"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","names":[]}
@@ -31,11 +31,12 @@ export { SONIFY_DEFAULTS, SonifyController, SonifyOptions, SonifyPoint, SonifySe
31
31
  export { ChartSonifyButton, ChartSonifyButtonProps, Sonify, SonifyAlign, SonifyProps, UseSonifyOptions, UseSonifyResult, chartSonifySeries, useChartSonifySeries, useSonify } from './lib/sonify-control.js';
32
32
  export { ChartControls, ChartControlsProps, ChartExportFormat, ChartToolbar, ChartToolbarProps, Toolbar, ToolbarProps } from './lib/toolbar.js';
33
33
  export { ChartZoom, ChartZoomOptions, ChartZoomSurface, ChartZoomSurfaceProps, clampWindow, panWindow, useChartZoom, zoomWindow } from './lib/zoom.js';
34
- export { ChartPngOptions, chartToCsv, chartToPng, downloadBlob, exportFilename } from './lib/export.js';
34
+ export { ChartPngOptions, chartToCsv, chartToPng, exportFilename } from './lib/export.js';
35
35
  export { ANNOTATION_LAYER, Annotation, AnnotationProps, AnnotationWeight, ReferenceBand, ReferenceBandProps, ReferenceLine, ReferenceLineProps } from './lib/annotations.js';
36
36
  export { ValueLabelMode, Values, ValuesProps } from './lib/values.js';
37
37
  export { ChartEmpty, ChartEmptyProps } from './lib/empty.js';
38
38
  export { NumberFormatOptions, NumberStyle, defaultTick, formatNumber } from './lib/format.js';
39
+ export { downloadBlob } from '../lib/svg-export.js';
39
40
  import 'react';
40
41
  import 'recharts';
41
42
  import 'recharts/types/component/DefaultTooltipContent';
@@ -1,6 +1,23 @@
1
1
  import { ChartColumn } from './figure.js';
2
+ export { downloadBlob } from '../../lib/svg-export.js';
2
3
  import 'react';
4
+ import './empty.js';
3
5
 
6
+ /**
7
+ * Taking a chart off the page: a PNG of the plot, or a CSV of its rows.
8
+ *
9
+ * The general half — walking a live tree to inline its resolved paint,
10
+ * serialising a standalone SVG document, rasterising one through a canvas — is
11
+ * not chart-shaped at all, and lives in `src/lib/svg-export.ts` shared with the
12
+ * diagrams entry. What is left here is what only a Recharts chart knows: which
13
+ * `<svg>` in the subtree is the plot, what a row of chart data looks like as a
14
+ * CSV record, and that the ground behind an exported plot is `--chart-surface`.
15
+ *
16
+ * Nothing here touches the DOM at module scope. Every browser API is reached
17
+ * for inside a function body, so this module is safe to import from a server
18
+ * component and only fails when a consumer actually asks for a file on a
19
+ * server, which is a call that cannot succeed anywhere.
20
+ */
4
21
  interface ChartPngOptions {
5
22
  /**
6
23
  * Printed above the plot in the exported image. Pass the figure's title: a
@@ -21,46 +38,27 @@ interface ChartPngOptions {
21
38
  /**
22
39
  * The chart's `<svg>`, rasterised to a PNG blob.
23
40
  *
24
- * ## The problem this function exists to solve
25
- *
26
- * Serialising the `<svg>` and handing it to an `<img>` puts it in a document
27
- * of its own. That document has none of the page's stylesheets, so every
28
- * `var(--color-desktop-0)`, `var(--chart-fill)` and `var(--ink)` in the markup
29
- * resolves to nothing — and a paint that resolves to nothing is not a fallback
30
- * colour, it is an invisible mark. Serialise a chart naively and you get an
31
- * empty rectangle with axes on it.
32
- *
33
- * ## The solution
41
+ * Three chart-specific decisions and nothing else. `serializeSvg` does the
42
+ * work, and its comment carries the reason a naive `XMLSerializer` export of a
43
+ * themed chart comes out as an empty rectangle with axes on it.
34
44
  *
35
- * Walk the live tree and the clone in step, and on every node write
36
- * `getComputedStyle`'s answer for the painted properties into an inline
37
- * `style`. Computed values have already had `var()` substituted by the engine,
38
- * so `fill: var(--color-desktop-0)` arrives as `fill: rgb(16, 16, 16)` — and,
39
- * because the walk reads the LIVE element, it reads it under whichever theme
40
- * is currently applied. Light and dark come out right for free, with no theme
41
- * argument anywhere in this file.
42
- *
43
- * `<stop>` elements are walked too, which is the half that is easy to miss: a
44
- * series is painted `fill="url(#…-colors-desktop)"`, the url survives
45
- * serialisation because the gradient is inside the same `<svg>`, and it is the
46
- * gradient's STOPS that carry the custom properties.
45
+ * 1. WHICH `<svg>` the plot, not the first one in the subtree. See
46
+ * `findPlotSvg`.
47
+ * 2. WHAT GROUND `--chart-surface`, resolved against the live element, which
48
+ * is what makes a dark-mode export come out dark rather than transparent.
49
+ * 3. WHAT INK AND FACE for the caption `--ink` and the element's own
50
+ * resolved family, so the exported title matches the page it came from.
47
51
  *
48
52
  * ## What this cannot capture, honestly
49
53
  *
50
54
  * - **Anything drawn in HTML.** The legend, the tooltip, the brush strip and
51
55
  * the hidden data table are DOM, not SVG. The PNG is the plot.
52
- * - **Web fonts.** The isolated document cannot fetch the page's `@font-face`
53
- * sources, so text is rasterised in whatever the resolved family stack finds
54
- * locally. Type metrics will differ from the screen.
55
- * - **Cross-origin images.** A chart with an external `<image>` in it taints
56
- * the canvas and `toBlob` throws; there is no way around it from script.
57
- * - **The exact rendering.** This is the browser's SVG rasteriser re-drawing
58
- * the markup, not a screenshot. Antialiasing, `backdrop-filter` and any
59
- * effect a page stylesheet applied from outside the `<svg>` are not in it.
56
+ * - **Web fonts, cross-origin images, and the exact rendering.** See
57
+ * `serializeSvg` and `rasterize`.
60
58
  *
61
59
  * @example
62
60
  * const blob = await chartToPng(plotRef.current, { title: 'Visitors per month' })
63
- * downloadBlob(blob, 'visitors-per-month.png')
61
+ * downloadBlob(blob, exportFilename('Visitors per month', 'png'))
64
62
  */
65
63
  declare function chartToPng(element: HTMLElement | SVGSVGElement, options?: ChartPngOptions): Promise<Blob>;
66
64
  /**
@@ -83,21 +81,14 @@ declare function chartToPng(element: HTMLElement | SVGSVGElement, options?: Char
83
81
  * downloadBlob(new Blob([csv], { type: 'text/csv;charset=utf-8' }), 'visitors.csv')
84
82
  */
85
83
  declare function chartToCsv(rows: Record<string, unknown>[], columns: ChartColumn[]): string;
86
- /**
87
- * Hands a blob to the browser as a download.
88
- *
89
- * Throws rather than no-oping without a `document`: a download is something a
90
- * person just asked for, and a silent return would look to them like a click
91
- * that did nothing.
92
- */
93
- declare function downloadBlob(blob: Blob, filename: string): void;
84
+
94
85
  /**
95
86
  * A figure's title as a filename.
96
87
  *
97
- * Lowercase, ASCII-safe and hyphenated, because a downloaded file crosses into
98
- * shells, zip archives and Windows paths where a title's spaces and slashes
99
- * are someone else's problem.
88
+ * The shared slugger, with `chart` rather than `figure` as the fallback for a
89
+ * title that slugs to nothing an export from this entry is a chart, and the
90
+ * name a reader finds in their downloads folder should say so.
100
91
  */
101
92
  declare function exportFilename(title: string, extension: string): string;
102
93
 
103
- export { type ChartPngOptions, chartToCsv, chartToPng, downloadBlob, exportFilename };
94
+ export { type ChartPngOptions, chartToCsv, chartToPng, exportFilename };
@@ -1,154 +1,28 @@
1
- const SVG_NS = "http://www.w3.org/2000/svg";
2
- const PAINTED_PROPERTIES = [
3
- "display",
4
- "visibility",
5
- "opacity",
6
- "color",
7
- "fill",
8
- "fill-opacity",
9
- "fill-rule",
10
- "stroke",
11
- "stroke-opacity",
12
- "stroke-width",
13
- "stroke-linecap",
14
- "stroke-linejoin",
15
- "stroke-dasharray",
16
- "stroke-dashoffset",
17
- "stop-color",
18
- "stop-opacity",
19
- "flood-color",
20
- "flood-opacity",
21
- "mask",
22
- "filter",
23
- "clip-path",
24
- "clip-rule",
25
- "paint-order",
26
- "mix-blend-mode",
27
- "shape-rendering",
28
- "text-anchor",
29
- "dominant-baseline",
30
- "font-family",
31
- "font-size",
32
- "font-style",
33
- "font-weight",
34
- "letter-spacing",
35
- "text-transform"
36
- ];
1
+ import {
2
+ downloadBlob,
3
+ exportFilename as figureFilename,
4
+ rasterize,
5
+ readToken,
6
+ serializeSvg
7
+ } from "../../lib/svg-export.js";
37
8
  async function chartToPng(element, options = {}) {
38
9
  const { title, scale = 2, background, padding = 16 } = options;
39
10
  const svg = findPlotSvg(element);
40
11
  if (!svg) throw new Error("chartToPng: found no <svg> to export inside the element it was given");
41
- const box = svg.getBoundingClientRect();
42
- const width = Math.ceil(box.width);
43
- const height = Math.ceil(box.height);
44
- if (width === 0 || height === 0) {
45
- throw new Error("chartToPng: the chart has no measured size yet \u2014 export it after it renders");
46
- }
47
- const clone = svg.cloneNode(true);
48
- inlinePaint(svg, clone);
49
- clone.setAttribute("width", String(width));
50
- clone.setAttribute("height", String(height));
51
- const titleBand = title ? 30 : 0;
52
- const totalWidth = width + padding * 2;
53
- const totalHeight = height + padding * 2 + titleBand;
54
- const page = document.createElementNS(SVG_NS, "svg");
55
- page.setAttribute("xmlns", SVG_NS);
56
- page.setAttribute("width", String(totalWidth));
57
- page.setAttribute("height", String(totalHeight));
58
- page.setAttribute("viewBox", `0 0 ${totalWidth} ${totalHeight}`);
59
- const plate = document.createElementNS(SVG_NS, "rect");
60
- plate.setAttribute("width", String(totalWidth));
61
- plate.setAttribute("height", String(totalHeight));
62
- plate.setAttribute("fill", background ?? readToken(element, "--chart-surface", "#ffffff"));
63
- page.append(plate);
64
- if (title) {
65
- const caption = document.createElementNS(SVG_NS, "text");
66
- caption.setAttribute("x", String(padding));
67
- caption.setAttribute("y", String(padding + 14));
68
- caption.setAttribute("fill", readToken(element, "--ink", "#101010"));
69
- caption.setAttribute("font-size", "13");
70
- caption.setAttribute("font-weight", "500");
71
- caption.setAttribute("font-family", getComputedStyle(element).fontFamily || "sans-serif");
72
- caption.textContent = title;
73
- page.append(caption);
74
- }
75
- const group = document.createElementNS(SVG_NS, "g");
76
- group.setAttribute("transform", `translate(${padding}, ${padding + titleBand})`);
77
- group.append(clone);
78
- page.append(group);
79
- const markup = new XMLSerializer().serializeToString(page);
80
- const url = URL.createObjectURL(
81
- new Blob([`<?xml version="1.0" encoding="UTF-8"?>
82
- ${markup}`], {
83
- type: "image/svg+xml;charset=utf-8"
84
- })
85
- );
86
- try {
87
- const image = await loadImage(url);
88
- const canvas = document.createElement("canvas");
89
- canvas.width = Math.ceil(totalWidth * scale);
90
- canvas.height = Math.ceil(totalHeight * scale);
91
- const context = canvas.getContext("2d");
92
- if (!context) throw new Error("chartToPng: this browser gave back no 2D canvas context");
93
- context.drawImage(image, 0, 0, canvas.width, canvas.height);
94
- return await canvasToBlob(canvas);
95
- } finally {
96
- URL.revokeObjectURL(url);
97
- }
12
+ const markup = serializeSvg(svg, {
13
+ padding,
14
+ background: background ?? readToken(element, "--chart-surface", "#ffffff"),
15
+ title,
16
+ titleColor: readToken(element, "--ink", "#101010"),
17
+ titleFont: getComputedStyle(element).fontFamily || "sans-serif"
18
+ });
19
+ return rasterize(markup, "png", { scale });
98
20
  }
99
21
  function findPlotSvg(element) {
100
22
  if (element.tagName.toLowerCase() === "svg") return element;
101
23
  const scope = element.querySelector('[data-slot="chart"]') ?? element;
102
24
  return scope.querySelector("svg.recharts-surface") ?? scope.querySelector("svg");
103
25
  }
104
- function inlinePaint(live, clone) {
105
- const computed = getComputedStyle(live);
106
- let css = "";
107
- for (const property of PAINTED_PROPERTIES) {
108
- const value = computed.getPropertyValue(property);
109
- if (value) css += `${property}:${value};`;
110
- }
111
- if (css) clone.setAttribute("style", css);
112
- const children = live.children;
113
- const clones = clone.children;
114
- for (let index = 0; index < children.length; index += 1) {
115
- const liveChild = children[index];
116
- const cloneChild = clones[index];
117
- if (liveChild && cloneChild) inlinePaint(liveChild, cloneChild);
118
- }
119
- }
120
- function readToken(element, name, fallback) {
121
- const value = getComputedStyle(element).getPropertyValue(name).trim();
122
- return value && !value.includes("var(") ? value : fallback;
123
- }
124
- function loadImage(url) {
125
- return new Promise((resolve, reject) => {
126
- const image = new Image();
127
- image.addEventListener("load", () => resolve(image));
128
- image.addEventListener(
129
- "error",
130
- () => reject(new Error("chartToPng: the browser refused to decode the serialised chart"))
131
- );
132
- image.src = url;
133
- });
134
- }
135
- function canvasToBlob(canvas) {
136
- return new Promise((resolve, reject) => {
137
- try {
138
- canvas.toBlob((blob) => {
139
- if (blob) resolve(blob);
140
- else reject(new Error("chartToPng: the canvas produced no PNG data"));
141
- }, "image/png");
142
- } catch (cause) {
143
- reject(
144
- new Error(
145
- "chartToPng: the canvas is tainted, which means the chart contains a cross-origin image",
146
- { cause }
147
- )
148
- );
149
- }
150
- });
151
- }
152
26
  const UTF8_BOM = "\uFEFF";
153
27
  function chartToCsv(rows, columns) {
154
28
  if (columns.length === 0) return UTF8_BOM;
@@ -176,24 +50,8 @@ function cellText(value) {
176
50
  if (value instanceof Date) return value.toISOString();
177
51
  return "";
178
52
  }
179
- function downloadBlob(blob, filename) {
180
- if (typeof document === "undefined") {
181
- throw new Error("downloadBlob: needs a browser document \u2014 call it from an event handler");
182
- }
183
- const url = URL.createObjectURL(blob);
184
- const anchor = document.createElement("a");
185
- anchor.href = url;
186
- anchor.download = filename;
187
- anchor.rel = "noopener";
188
- anchor.style.display = "none";
189
- document.body.append(anchor);
190
- anchor.click();
191
- anchor.remove();
192
- setTimeout(() => URL.revokeObjectURL(url), 0);
193
- }
194
53
  function exportFilename(title, extension) {
195
- const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
196
- return `${slug || "chart"}.${extension}`;
54
+ return figureFilename(title, extension, "chart");
197
55
  }
198
56
  export {
199
57
  chartToCsv,