@misoto22/design 0.7.0 → 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 (369) hide show
  1. package/AGENTS.md +87 -0
  2. package/CLAUDE.md +1 -0
  3. package/README.md +50 -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 +25 -2
  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 +22 -1
  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 +24 -1
  36. package/dist/agent/DiagramExportMenu.md +29 -3
  37. package/dist/agent/DiagramInspector.md +23 -1
  38. package/dist/agent/DiagramLegend.md +22 -1
  39. package/dist/agent/DiagramMinimap.md +24 -4
  40. package/dist/agent/DiagramToolbar.md +23 -1
  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 +22 -1
  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 +23 -1
  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 +24 -2
  97. package/dist/agent/catalog.json +5602 -745
  98. package/dist/agent/index.md +13 -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/lib/export.d.ts +1 -0
  154. package/dist/charts/lib/figure.d.ts +21 -1
  155. package/dist/charts/lib/figure.js +5 -2
  156. package/dist/charts/lib/figure.js.map +1 -1
  157. package/dist/charts/lib/scale.d.ts +23 -0
  158. package/dist/charts/lib/scale.js +13 -0
  159. package/dist/charts/lib/scale.js.map +1 -0
  160. package/dist/charts/lib/selection.d.ts +20 -0
  161. package/dist/charts/lib/selection.js +18 -0
  162. package/dist/charts/lib/selection.js.map +1 -0
  163. package/dist/charts/lib/toolbar.d.ts +1 -0
  164. package/dist/components/Accordion/Accordion.js +8 -1
  165. package/dist/components/Accordion/Accordion.js.map +1 -1
  166. package/dist/components/AppShell/AppShell.d.ts +6 -0
  167. package/dist/components/AppShell/AppShell.js +34 -4
  168. package/dist/components/AppShell/AppShell.js.map +1 -1
  169. package/dist/components/Article/Article.d.ts +11 -4
  170. package/dist/components/Article/Article.js +13 -0
  171. package/dist/components/Article/Article.js.map +1 -1
  172. package/dist/components/AspectRatio/AspectRatio.d.ts +46 -0
  173. package/dist/components/AspectRatio/AspectRatio.js +33 -0
  174. package/dist/components/AspectRatio/AspectRatio.js.map +1 -0
  175. package/dist/components/Avatar/Avatar.d.ts +12 -3
  176. package/dist/components/Avatar/Avatar.js +6 -1
  177. package/dist/components/Avatar/Avatar.js.map +1 -1
  178. package/dist/components/Breadcrumb/Breadcrumb.d.ts +6 -0
  179. package/dist/components/Breadcrumb/Breadcrumb.js +15 -0
  180. package/dist/components/Breadcrumb/Breadcrumb.js.map +1 -1
  181. package/dist/components/Button/Button.js +14 -0
  182. package/dist/components/Button/Button.js.map +1 -1
  183. package/dist/components/Calendar/Calendar.d.ts +29 -3
  184. package/dist/components/Calendar/Calendar.js +60 -12
  185. package/dist/components/Calendar/Calendar.js.map +1 -1
  186. package/dist/components/Card/Card.d.ts +6 -0
  187. package/dist/components/Card/Card.js +1 -1
  188. package/dist/components/Card/Card.js.map +1 -1
  189. package/dist/components/Checkbox/Checkbox.d.ts +5 -2
  190. package/dist/components/Checkbox/Checkbox.js +12 -2
  191. package/dist/components/Checkbox/Checkbox.js.map +1 -1
  192. package/dist/components/Code/Code.d.ts +30 -0
  193. package/dist/components/Code/Code.js +21 -0
  194. package/dist/components/Code/Code.js.map +1 -0
  195. package/dist/components/CodeBlock/CodeBlock.d.ts +141 -0
  196. package/dist/components/CodeBlock/CodeBlock.js +123 -0
  197. package/dist/components/CodeBlock/CodeBlock.js.map +1 -0
  198. package/dist/components/Collapsible/Collapsible.js +10 -2
  199. package/dist/components/Collapsible/Collapsible.js.map +1 -1
  200. package/dist/components/Combobox/Combobox.d.ts +15 -1
  201. package/dist/components/Combobox/Combobox.js +25 -5
  202. package/dist/components/Combobox/Combobox.js.map +1 -1
  203. package/dist/components/Command/Command.d.ts +4 -2
  204. package/dist/components/Command/Command.js +13 -1
  205. package/dist/components/Command/Command.js.map +1 -1
  206. package/dist/components/ContextMenu/ContextMenu.d.ts +30 -5
  207. package/dist/components/ContextMenu/ContextMenu.js +32 -2
  208. package/dist/components/ContextMenu/ContextMenu.js.map +1 -1
  209. package/dist/components/DatePicker/DatePicker.d.ts +36 -4
  210. package/dist/components/DatePicker/DatePicker.js +55 -12
  211. package/dist/components/DatePicker/DatePicker.js.map +1 -1
  212. package/dist/components/DescriptionList/DescriptionList.d.ts +73 -0
  213. package/dist/components/DescriptionList/DescriptionList.js +35 -0
  214. package/dist/components/DescriptionList/DescriptionList.js.map +1 -0
  215. package/dist/components/Diagram/Diagram.d.ts +8 -3
  216. package/dist/components/Diagram/Diagram.js +82 -10
  217. package/dist/components/Diagram/Diagram.js.map +1 -1
  218. package/dist/components/Dialog/Dialog.d.ts +18 -3
  219. package/dist/components/Dialog/Dialog.js +20 -2
  220. package/dist/components/Dialog/Dialog.js.map +1 -1
  221. package/dist/components/DropdownMenu/DropdownMenu.d.ts +49 -11
  222. package/dist/components/DropdownMenu/DropdownMenu.js +32 -2
  223. package/dist/components/DropdownMenu/DropdownMenu.js.map +1 -1
  224. package/dist/components/EmptyState/EmptyState.d.ts +25 -1
  225. package/dist/components/EmptyState/EmptyState.js +3 -1
  226. package/dist/components/EmptyState/EmptyState.js.map +1 -1
  227. package/dist/components/ErrorState/ErrorState.d.ts +22 -1
  228. package/dist/components/ErrorState/ErrorState.js +11 -2
  229. package/dist/components/ErrorState/ErrorState.js.map +1 -1
  230. package/dist/components/Field/Field.d.ts +70 -3
  231. package/dist/components/Field/Field.js +53 -12
  232. package/dist/components/Field/Field.js.map +1 -1
  233. package/dist/components/Field/field-control.d.ts +31 -0
  234. package/dist/components/Field/field-control.js +12 -0
  235. package/dist/components/Field/field-control.js.map +1 -0
  236. package/dist/components/FloatingIconButton/FloatingIconButton.js +2 -0
  237. package/dist/components/FloatingIconButton/FloatingIconButton.js.map +1 -1
  238. package/dist/components/Heading/Heading.d.ts +62 -0
  239. package/dist/components/Heading/Heading.js +44 -0
  240. package/dist/components/Heading/Heading.js.map +1 -0
  241. package/dist/components/Markdown/Markdown.d.ts +139 -0
  242. package/dist/components/Markdown/Markdown.js +135 -0
  243. package/dist/components/Markdown/Markdown.js.map +1 -0
  244. package/dist/components/Markdown/inline.d.ts +29 -0
  245. package/dist/components/Markdown/inline.js +74 -0
  246. package/dist/components/Markdown/inline.js.map +1 -0
  247. package/dist/components/Markdown/nodes.d.ts +65 -0
  248. package/dist/components/Markdown/nodes.js +1 -0
  249. package/dist/components/Markdown/nodes.js.map +1 -0
  250. package/dist/components/Markdown/parse.d.ts +24 -0
  251. package/dist/components/Markdown/parse.js +110 -0
  252. package/dist/components/Markdown/parse.js.map +1 -0
  253. package/dist/components/Markdown/slug.d.ts +15 -0
  254. package/dist/components/Markdown/slug.js +7 -0
  255. package/dist/components/Markdown/slug.js.map +1 -0
  256. package/dist/components/NativeSelect/NativeSelect.d.ts +6 -0
  257. package/dist/components/NativeSelect/NativeSelect.js +2 -3
  258. package/dist/components/NativeSelect/NativeSelect.js.map +1 -1
  259. package/dist/components/Pagination/Pagination.d.ts +24 -1
  260. package/dist/components/Pagination/Pagination.js +6 -3
  261. package/dist/components/Pagination/Pagination.js.map +1 -1
  262. package/dist/components/Progress/Progress.d.ts +17 -5
  263. package/dist/components/Progress/Progress.js +18 -6
  264. package/dist/components/Progress/Progress.js.map +1 -1
  265. package/dist/components/RadioGroup/RadioGroup.d.ts +6 -0
  266. package/dist/components/RadioGroup/RadioGroup.js +11 -1
  267. package/dist/components/RadioGroup/RadioGroup.js.map +1 -1
  268. package/dist/components/ScrollArea/ScrollArea.d.ts +15 -0
  269. package/dist/components/ScrollArea/ScrollArea.js +2 -2
  270. package/dist/components/ScrollArea/ScrollArea.js.map +1 -1
  271. package/dist/components/SearchableMenu/SearchableMenu.d.ts +11 -2
  272. package/dist/components/SearchableMenu/SearchableMenu.js +24 -2
  273. package/dist/components/SearchableMenu/SearchableMenu.js.map +1 -1
  274. package/dist/components/Select/Select.d.ts +18 -2
  275. package/dist/components/Select/Select.js +20 -3
  276. package/dist/components/Select/Select.js.map +1 -1
  277. package/dist/components/Separator/Separator.d.ts +21 -3
  278. package/dist/components/Separator/Separator.js +9 -1
  279. package/dist/components/Separator/Separator.js.map +1 -1
  280. package/dist/components/Sheet/Sheet.d.ts +3 -0
  281. package/dist/components/Sheet/Sheet.js +11 -2
  282. package/dist/components/Sheet/Sheet.js.map +1 -1
  283. package/dist/components/Skeleton/Skeleton.d.ts +8 -1
  284. package/dist/components/Skeleton/Skeleton.js +1 -1
  285. package/dist/components/Skeleton/Skeleton.js.map +1 -1
  286. package/dist/components/Slider/Slider.d.ts +21 -3
  287. package/dist/components/Slider/Slider.js +54 -27
  288. package/dist/components/Slider/Slider.js.map +1 -1
  289. package/dist/components/Spinner/Spinner.d.ts +8 -0
  290. package/dist/components/Spinner/Spinner.js +3 -2
  291. package/dist/components/Spinner/Spinner.js.map +1 -1
  292. package/dist/components/StatusPill/StatusPill.d.ts +5 -0
  293. package/dist/components/StatusPill/StatusPill.js +6 -0
  294. package/dist/components/StatusPill/StatusPill.js.map +1 -1
  295. package/dist/components/Table/Table.d.ts +6 -0
  296. package/dist/components/Table/Table.js +3 -1
  297. package/dist/components/Table/Table.js.map +1 -1
  298. package/dist/components/Tag/Tag.d.ts +68 -9
  299. package/dist/components/Tag/Tag.js +50 -10
  300. package/dist/components/Tag/Tag.js.map +1 -1
  301. package/dist/components/Text/Text.d.ts +62 -0
  302. package/dist/components/Text/Text.js +30 -0
  303. package/dist/components/Text/Text.js.map +1 -0
  304. package/dist/components/Timestamp/Timestamp.d.ts +59 -0
  305. package/dist/components/Timestamp/Timestamp.js +56 -0
  306. package/dist/components/Timestamp/Timestamp.js.map +1 -0
  307. package/dist/components/Toast/Toast.d.ts +9 -1
  308. package/dist/components/Toast/Toast.js +27 -4
  309. package/dist/components/Toast/Toast.js.map +1 -1
  310. package/dist/components/ToggleGroup/ToggleGroup.d.ts +6 -1
  311. package/dist/components/ToggleGroup/ToggleGroup.js +11 -1
  312. package/dist/components/ToggleGroup/ToggleGroup.js.map +1 -1
  313. package/dist/components/Toolbar/Toolbar.d.ts +55 -0
  314. package/dist/components/Toolbar/Toolbar.js +43 -0
  315. package/dist/components/Toolbar/Toolbar.js.map +1 -0
  316. package/dist/diagrams/ArchitectureFigure/ArchitectureFigure.d.ts +6 -0
  317. package/dist/diagrams/ArchitectureFigure/ArchitectureFigure.js +48 -8
  318. package/dist/diagrams/ArchitectureFigure/ArchitectureFigure.js.map +1 -1
  319. package/dist/diagrams/DataflowFigure/DataflowFigure.js +21 -2
  320. package/dist/diagrams/DataflowFigure/DataflowFigure.js.map +1 -1
  321. package/dist/diagrams/DiagramCanvas/DiagramCanvas.d.ts +14 -0
  322. package/dist/diagrams/DiagramCanvas/DiagramCanvas.js +14 -7
  323. package/dist/diagrams/DiagramCanvas/DiagramCanvas.js.map +1 -1
  324. package/dist/diagrams/DiagramExportMenu/DiagramExportMenu.d.ts +38 -4
  325. package/dist/diagrams/DiagramExportMenu/DiagramExportMenu.js +14 -16
  326. package/dist/diagrams/DiagramExportMenu/DiagramExportMenu.js.map +1 -1
  327. package/dist/diagrams/DiagramInspector/DiagramInspector.js +2 -2
  328. package/dist/diagrams/DiagramInspector/DiagramInspector.js.map +1 -1
  329. package/dist/diagrams/DiagramMinimap/DiagramMinimap.d.ts +23 -7
  330. package/dist/diagrams/DiagramMinimap/DiagramMinimap.js +45 -18
  331. package/dist/diagrams/DiagramMinimap/DiagramMinimap.js.map +1 -1
  332. package/dist/diagrams/LifecycleFigure/LifecycleFigure.js +44 -12
  333. package/dist/diagrams/LifecycleFigure/LifecycleFigure.js.map +1 -1
  334. package/dist/diagrams/SequenceFigure/SequenceFigure.js +35 -5
  335. package/dist/diagrams/SequenceFigure/SequenceFigure.js.map +1 -1
  336. package/dist/diagrams/WorkflowFigure/WorkflowFigure.d.ts +3 -1
  337. package/dist/diagrams/WorkflowFigure/WorkflowFigure.js +76 -28
  338. package/dist/diagrams/WorkflowFigure/WorkflowFigure.js.map +1 -1
  339. package/dist/diagrams/index.d.ts +1 -0
  340. package/dist/diagrams/index.js +10 -0
  341. package/dist/diagrams/index.js.map +1 -1
  342. package/dist/diagrams/lib/dev.d.ts +61 -0
  343. package/dist/diagrams/lib/dev.js +96 -0
  344. package/dist/diagrams/lib/dev.js.map +1 -0
  345. package/dist/diagrams/lib/frame.d.ts +48 -7
  346. package/dist/diagrams/lib/frame.js +17 -2
  347. package/dist/diagrams/lib/frame.js.map +1 -1
  348. package/dist/diagrams/lib/wires.js.map +1 -1
  349. package/dist/diagrams/spec.d.ts +64 -12
  350. package/dist/index.d.ts +16 -4
  351. package/dist/index.js +9 -0
  352. package/dist/index.js.map +1 -1
  353. package/dist/keyframes.css +57 -3
  354. package/dist/lib/overlay-container.d.ts +14 -4
  355. package/dist/lib/overlay-container.js.map +1 -1
  356. package/dist/lib/warn.d.ts +73 -0
  357. package/dist/lib/warn.js +38 -0
  358. package/dist/lib/warn.js.map +1 -0
  359. package/dist/semantic.css +17 -6
  360. package/dist/styles.css +1 -1
  361. package/dist/tokens.css +20 -2
  362. package/dist/tokens.d.ts +1 -1
  363. package/dist/tokens.js +39 -36
  364. package/dist/tokens.json +39 -36
  365. package/llms.txt +35 -0
  366. package/package.json +5 -2
  367. package/skills/misoto22-design/evals/evals.json +84 -0
  368. package/skills/misoto22-design/rules/a11y.md +55 -3
  369. package/skills/misoto22-design/rules/forms.md +5 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/charts/ComposedChart/ComposedChart.tsx"],"sourcesContent":["'use client'\n\nimport {\n Children,\n createContext,\n isValidElement,\n useCallback,\n useContext,\n useId,\n useMemo,\n useState,\n type ComponentProps,\n type FC,\n type ReactElement,\n type ReactNode,\n} from 'react'\nimport {\n Bar as RechartsBar,\n CartesianGrid,\n ComposedChart as RechartsComposedChart,\n Line as RechartsLine,\n XAxis as RechartsXAxis,\n YAxis as RechartsYAxis,\n} from 'recharts'\nimport { motion, useReducedMotion } from 'motion/react'\nimport { ChartContainer, type ChartConfig } from '../lib/chart'\nimport { ChartFigure } from '../lib/figure'\nimport { ChartEmpty, type ChartEmptyProps } from '../lib/empty'\nimport { axisLabel } from '../lib/axis'\nimport { defaultTick } from '../lib/format'\nimport { Annotation, ReferenceBand, ReferenceLine } from '../lib/annotations'\nimport { Values, findSlot, resolveValues, type ValuesProps } from '../lib/values'\n\n/**\n * The axis props, with `label` narrowed to a string.\n *\n * Recharts' own `label` accepts a render function and a config object, neither\n * of which a call site should be reaching for to name an axis — and both of\n * which would let a consumer opt out of the system's own axis type styling\n * without noticing.\n */\nexport type ChartXAxisProps = Omit<ComponentProps<typeof RechartsXAxis>, 'label'> & {\n /** What this axis measures. Say the unit here or in the series label, once. */\n label?: string\n}\n\nexport type ChartYAxisProps = Omit<ComponentProps<typeof RechartsYAxis>, 'label'> & {\n /** What this axis measures. Say the unit here or in the series label, once. */\n label?: string\n}\n\nimport { ChartBackground, type ChartBackgroundVariant } from '../lib/background'\nimport { ChartDot, type ChartDotVariant } from '../lib/dot'\nimport { ChartLegend, ChartLegendContent } from '../lib/legend'\nimport { ChartTooltip, ChartTooltipContent } from '../lib/tooltip'\nimport { Brush, ChartBrush, type BrushProps, type ChartBrushRange } from '../lib/brush'\nimport { ChartControls, Toolbar, type ToolbarProps } from '../lib/toolbar'\nimport { useChartZoom } from '../lib/zoom'\nimport { LoadingIndicator, LoadingShimmer, LOADING_KEY, useLoadingRows } from '../lib/loading'\nimport {\n AnimatedDashedStroke,\n BarDuotoneFill,\n BarGradientFill,\n BarHatchedFill,\n BarStrippedFill,\n GlowFilter,\n REVEAL_EASE,\n RevealMask,\n SeriesGradient,\n type ChartRevealType,\n} from '../lib/paint'\nimport type { BarVariant } from '../BarChart/BarChart'\nimport type {\n AreaStrokeVariant,\n ChartCurveType,\n ChartLegendSlotProps,\n ChartTooltipSlotProps,\n} from '../AreaChart/AreaChart'\n\nconst STROKE_WIDTH = 2\nconst BAR_RADIUS = 4\nconst GROW_DURATION = 0.5\nconst GROW_STAGGER = 0.05\n\n/**\n * Room around the plot, so the first and last tick labels are not sliced by the\n * container's edge. Recharts' own default is 5px on every side, which is not\n * enough for a label centred on the axis's first point.\n */\nconst CHART_MARGIN = { top: 8, right: 12, bottom: 0, left: 12 }\n\ninterface ComposedChartContextValue {\n config: ChartConfig\n /** The rows on screen, so a mark can label its own extremes. */\n rows: Record<string, unknown>[]\n curveType: ChartCurveType\n animationType: ChartRevealType\n introStartedAt: number\n dataLength: number\n isLoading: boolean\n hoveredIndex: number | null\n selectedDataKey: string | null\n selectDataKey: (dataKey: string | null) => void\n}\n\nconst ComposedChartContext = createContext<ComposedChartContextValue | null>(null)\n\nfunction useComposedChart(): ComposedChartContextValue {\n const context = useContext(ComposedChartContext)\n if (!context) throw new Error('A composed chart part must be rendered inside <ComposedChart>')\n return context\n}\n\ntype ValidateKeys<TData, TConfig> = {\n [K in keyof TConfig]: K extends keyof TData ? ChartConfig[string] : never\n}\n\nexport interface ComposedChartProps<\n TData extends Record<string, unknown>,\n TConfig extends Record<string, ChartConfig[string]>,\n> {\n config: TConfig & ValidateKeys<TData, TConfig>\n /** The rows the chart draws. One entry per point, bar or category. */\n data: TData[]\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 /**\n * The composed parts — axes, grid, tooltip, legend, and the marks\n * themselves.\n */\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 RechartsComposedChart>\n /**\n * How the line between two points is interpolated. Every mark inherits it\n * unless it says otherwise.\n */\n curveType?: ChartCurveType\n animationType?: ChartRevealType\n barGap?: number\n barCategoryGap?: number\n /** The series lit on first render. Selection dims every other series. */\n defaultSelectedDataKey?: string | null\n /** Fires when the selection changes, and with null when it is cleared. */\n onSelectionChange?: (selectedDataKey: string | null) => void\n /**\n * Swaps the marks for an animated skeleton, keeping the measured height so\n * the page does not jump when the data lands.\n */\n isLoading?: boolean\n /** How many bars the skeleton draws. */\n loadingBars?: number\n /**\n * The row field on the category axis. Needed by the brush and by the table\n * view.\n */\n xDataKey?: keyof TData & string\n /**\n * Drops the hidden table view. Only correct when the page prints the data\n * itself.\n */\n hideDataTable?: boolean\n /**\n * What the chart shows when it has nothing to draw.\n *\n * Rendered instead of the plot whenever `data` is empty and the chart is not\n * loading — the state a real dashboard reaches within a week, and the one an\n * empty pair of axes is indistinguishable from a failed load. `false` keeps\n * the axes, for a chart whose emptiness is itself the reading.\n */\n empty?: ChartEmptyProps | false\n}\n\n/**\n * Bars and lines over one axis — the shape for \"the volume, and the rate it\n * moved at\".\n *\n * One axis, always. Two measures at different scales belong in two charts or\n * indexed to a common base; a second y-scale lets the author choose where the\n * lines cross, which is the single most misleading thing a chart can do.\n *\n * @example\n * <ComposedChart title=\"Revenue and profit\" config={config} data={data}>\n * <ComposedChart.Grid />\n * <ComposedChart.XAxis dataKey=\"month\" />\n * <ComposedChart.Tooltip />\n * <ComposedChart.Bar dataKey=\"revenue\" variant=\"duotone\" />\n * <ComposedChart.Line dataKey=\"profit\" />\n * </ComposedChart>\n */\nexport function ComposedChart<\n TData extends Record<string, unknown>,\n TConfig extends Record<string, ChartConfig[string]>,\n>({\n config,\n data,\n title,\n showTitle,\n description,\n children,\n className,\n chartProps,\n curveType = 'linear',\n animationType = 'forward',\n barGap,\n barCategoryGap,\n defaultSelectedDataKey = null,\n onSelectionChange,\n isLoading = false,\n loadingBars,\n xDataKey,\n hideDataTable = false,\n empty,\n}: ComposedChartProps<TData, TConfig>) {\n const chartId = useId().replace(/:/g, '')\n const [introStartedAt] = useState(() => Date.now())\n const [selectedDataKey, setSelectedDataKey] = useState<string | null>(defaultSelectedDataKey)\n const [hoveredIndex, setHoveredIndex] = useState<number | null>(null)\n const { rows: loadingData, onShimmerExit } = useLoadingRows(isLoading, loadingBars ?? 12)\n // One window, two views of it: the brush strip below the plot and the\n // toolbar above it both read and write this. See `useChartZoom` for why they\n // are not two separate windows.\n const zoom = useChartZoom({ data, xDataKey })\n\n // The brush and the toolbar are declared as children but never rendered into\n // the Recharts tree — one is a footer under the plot and the other a row\n // above it, and both live outside the SVG.\n const { brushSlot, toolbarSlot, chartChildren } = useMemo(() => {\n const parts = Children.toArray(children)\n const element = parts.find((child) => isValidElement(child) && child.type === Brush)\n const props = (isValidElement(element) ? element.props : {}) as BrushProps\n return {\n brushSlot: { present: isValidElement(element), ...props },\n toolbarSlot: findSlot<ToolbarProps>(parts, Toolbar),\n chartChildren: parts.filter(\n (child) => !(isValidElement(child) && (child.type === Brush || child.type === Toolbar)),\n ),\n }\n }, [children])\n\n const columns = useMemo(\n () => Object.entries(config).map(([key, series]) => ({ key, label: series.label })),\n [config],\n )\n\n const showBrush = brushSlot.present && !isLoading\n const showToolbar = toolbarSlot !== null && !isLoading\n // `empty === false` keeps the axes, for a chart whose emptiness is the\n // reading — a monitoring panel that should show a flat, silent range rather\n // than a message.\n const isEmpty = !isLoading && empty !== false && data.length === 0\n const displayData = showBrush || showToolbar ? zoom.visibleData : data\n\n const selectDataKey = useCallback(\n (next: string | null) => {\n setSelectedDataKey(next)\n onSelectionChange?.(next)\n },\n [onSelectionChange],\n )\n\n const context = useMemo<ComposedChartContextValue>(\n () => ({\n config,\n rows: displayData as Record<string, unknown>[],\n curveType,\n animationType,\n introStartedAt,\n dataLength: displayData.length,\n isLoading,\n hoveredIndex,\n selectedDataKey,\n selectDataKey,\n }),\n [\n animationType,\n config,\n curveType,\n displayData,\n hoveredIndex,\n introStartedAt,\n isLoading,\n selectDataKey,\n selectedDataKey,\n ],\n )\n\n return (\n <ComposedChartContext.Provider value={context}>\n <ChartFigure\n title={title}\n showTitle={showTitle}\n description={description}\n className={className}\n table={\n hideDataTable\n ? false\n : { rows: data, rowKey: xDataKey, columns }\n }\n >\n {isEmpty ? (\n <ChartEmpty {...(empty || {})} />\n ) : (\n <ChartControls\n toolbar={showToolbar ? toolbarSlot : null}\n zoom={zoom}\n title={title}\n rows={data}\n columns={columns}\n rowKey={xDataKey}\n >\n <ChartContainer\n config={config}\n footer={\n showBrush && (\n <ChartBrush\n data={data}\n config={config}\n xDataKey={xDataKey}\n variant=\"area\"\n curveType={curveType}\n height={brushSlot.height}\n formatLabel={brushSlot.formatLabel}\n className=\"mt-1\"\n {...zoom.brushProps}\n onChange={(range: ChartBrushRange) => {\n zoom.brushProps.onChange(range)\n brushSlot.onChange?.(range)\n }}\n />\n )\n }\n >\n <RechartsComposedChart\n id={chartId}\n accessibilityLayer\n margin={CHART_MARGIN}\n data={isLoading ? loadingData : displayData}\n barGap={barGap}\n barCategoryGap={barCategoryGap}\n // The hovered column, read from Recharts' own tooltip cursor. This\n // was the missing half of `enableHoverHighlight`: the state existed\n // and was cleared on leave, but nothing ever set it, so the\n // highlight never fired.\n onMouseMove={(state) => {\n const index = (state as { activeTooltipIndex?: number } | undefined)\n ?.activeTooltipIndex\n setHoveredIndex(typeof index === 'number' ? index : null)\n }}\n onMouseLeave={() => setHoveredIndex(null)}\n {...chartProps}\n >\n {chartChildren}\n {isLoading && <LoadingBar chartId={chartId} onShimmerExit={onShimmerExit} />}\n </RechartsComposedChart>\n </ChartContainer>\n </ChartControls>\n )}\n <LoadingIndicator isLoading={isLoading} />\n </ChartFigure>\n </ComposedChartContext.Provider>\n )\n}\n\nexport interface ComposedBarProps {\n dataKey: string\n variant?: BarVariant\n radius?: number\n /** A halo behind the bars. */\n glowing?: boolean\n animationType?: ChartRevealType\n isClickable?: boolean\n /** Dims this series while another COLUMN is hovered. */\n enableHoverHighlight?: boolean\n /** `<ComposedChart.Values>`. */\n children?: ReactNode\n barProps?: ComponentProps<typeof RechartsBar>\n}\n\n/** One bar series inside a composed chart. */\nfunction Bar({\n dataKey,\n variant = 'default',\n radius = BAR_RADIUS,\n glowing = false,\n animationType,\n isClickable = false,\n enableHoverHighlight = false,\n children,\n barProps,\n}: ComposedBarProps) {\n const {\n config,\n animationType: defaultAnimation,\n introStartedAt,\n dataLength,\n isLoading,\n rows,\n hoveredIndex,\n selectedDataKey,\n selectDataKey,\n } = useComposedChart()\n const id = useId().replace(/:/g, '')\n const reduceMotion = useReducedMotion()\n\n if (isLoading) return null\n\n const isSelected = selectedDataKey === null || selectedDataKey === dataKey\n const reveal: ChartRevealType = reduceMotion ? 'none' : (animationType ?? defaultAnimation)\n\n return (\n <>\n <RechartsBar\n dataKey={dataKey}\n fill={`url(#${id}-colors-${dataKey})`}\n radius={radius}\n isAnimationActive={false}\n style={isClickable || enableHoverHighlight ? { cursor: 'pointer' } : undefined}\n shape={(props: unknown) => {\n const geometry = props as BarGeometry\n const index = typeof geometry.index === 'number' ? geometry.index : -1\n\n return (\n <ComposedBarShape\n {...geometry}\n id={id}\n dataKey={dataKey}\n variant={variant}\n barRadius={radius}\n filter={glowing ? `url(#${id}-glow-${dataKey})` : undefined}\n fillOpacity={barOpacity({\n isClickable,\n isSelected,\n selectedDataKey,\n enableHoverHighlight,\n hoveredIndex,\n index,\n })}\n isClickable={isClickable}\n enableHoverHighlight={enableHoverHighlight}\n animationType={reveal}\n dataLength={dataLength}\n introStartedAt={introStartedAt}\n onSelect={() => {\n if (!isClickable) return\n selectDataKey(selectedDataKey === dataKey ? null : dataKey)\n }}\n />\n )\n }}\n {...barProps}\n >\n {resolveValues(\n findSlot<ValuesProps>(children, Values),\n rows.map((row) => Number(row[dataKey])).filter(Number.isFinite),\n )}\n </RechartsBar>\n <defs>\n <SeriesGradient id={id} dataKey={dataKey} config={config} direction=\"vertical\" />\n {variant === 'hatched' && <BarHatchedFill id={id} dataKey={dataKey} />}\n {variant === 'duotone' && <BarDuotoneFill id={id} dataKey={dataKey} config={config} />}\n {variant === 'duotone-reverse' && (\n <BarDuotoneFill id={id} dataKey={dataKey} config={config} reverse />\n )}\n {variant === 'gradient' && <BarGradientFill id={id} dataKey={dataKey} />}\n {variant === 'stripped' && <BarStrippedFill id={id} dataKey={dataKey} />}\n {glowing && <GlowFilter id={id} dataKey={dataKey} />}\n </defs>\n </>\n )\n}\n\ninterface BarGeometry {\n x?: number\n y?: number\n width?: number\n height?: number\n index?: number\n background?: { x?: number; y?: number; width?: number; height?: number }\n [key: string]: unknown\n}\n\n/** One drawn bar, with an invisible full-column rect keeping it hoverable. */\nfunction ComposedBarShape({\n x = 0,\n y = 0,\n width = 0,\n height = 0,\n background,\n index = -1,\n id,\n dataKey,\n variant,\n barRadius,\n filter,\n fillOpacity,\n isClickable,\n enableHoverHighlight,\n animationType,\n dataLength,\n introStartedAt,\n onSelect,\n}: BarGeometry & {\n id: string\n dataKey: string\n variant: BarVariant\n barRadius: number\n filter?: string\n fillOpacity: number\n isClickable: boolean\n enableHoverHighlight: boolean\n animationType: ChartRevealType\n dataLength: number\n introStartedAt: number\n onSelect: () => void\n}) {\n const cursor = isClickable || enableHoverHighlight ? { cursor: 'pointer' } : undefined\n const grow = growAnimation(animationType, index, dataLength, introStartedAt) ?? {}\n const fill =\n variant === 'default'\n ? `url(#${id}-colors-${dataKey})`\n : `url(#${id}-${variant}-${dataKey})`\n\n const hitArea = (\n <rect\n x={background?.x ?? x}\n y={background?.y ?? y}\n width={background?.width ?? width}\n height={background?.height ?? height}\n fill=\"transparent\"\n />\n )\n\n if (variant === 'stripped') {\n return (\n <g style={cursor} onClick={onSelect}>\n <motion.g\n {...grow}\n filter={filter}\n opacity={fillOpacity}\n className=\"transition-opacity duration-(--duration-base)\"\n >\n <rect x={x} y={y} width={width} height={height} fill={fill} />\n <rect x={x} y={y} width={width} height={2} fill={`url(#${id}-colors-${dataKey})`} />\n </motion.g>\n {hitArea}\n </g>\n )\n }\n\n return (\n <g style={cursor} onClick={onSelect}>\n <motion.g {...grow}>\n <rect\n x={x}\n y={y}\n width={width}\n height={height}\n rx={barRadius}\n ry={barRadius}\n fill={fill}\n opacity={fillOpacity}\n filter={filter}\n className=\"transition-opacity duration-(--duration-base)\"\n />\n </motion.g>\n {hitArea}\n </g>\n )\n}\n\n/** The staggered grow-in, anchored to the chart rather than to the bar's mount. */\nfunction growAnimation(\n animationType: ChartRevealType,\n index: number,\n dataLength: number,\n introStartedAt: number,\n) {\n if (animationType === 'none' || index < 0 || dataLength <= 0) return null\n\n const lastIndex = dataLength - 1\n const centre = lastIndex / 2\n\n let step: number\n switch (animationType) {\n case 'reverse':\n step = lastIndex - index\n break\n case 'center-out':\n step = Math.abs(index - centre)\n break\n case 'edges-in':\n step = centre - Math.abs(index - centre)\n break\n default:\n step = index\n }\n\n const startMs = step * GROW_STAGGER * 1000\n const endMs = startMs + GROW_DURATION * 1000\n const elapsed = Date.now() - introStartedAt\n if (elapsed >= endMs) return null\n\n const from = elapsed <= startMs ? 0 : (elapsed - startMs) / (GROW_DURATION * 1000)\n\n return {\n initial: { scaleY: from },\n animate: { scaleY: 1 },\n transition: {\n duration: (endMs - Math.max(elapsed, startMs)) / 1000,\n ease: REVEAL_EASE,\n delay: Math.max(0, startMs - elapsed) / 1000,\n },\n style: { originY: 1 },\n }\n}\n\n/** How lit a bar is, given the selection and the hovered column. */\nfunction barOpacity({\n isClickable,\n isSelected,\n selectedDataKey,\n enableHoverHighlight,\n hoveredIndex,\n index,\n}: {\n isClickable: boolean\n isSelected: boolean\n selectedDataKey: string | null\n enableHoverHighlight: boolean\n hoveredIndex: number | null\n index: number\n}) {\n const selectionOpacity = isClickable && selectedDataKey !== null ? (isSelected ? 1 : 0.15) : 1\n if (enableHoverHighlight && hoveredIndex !== null) {\n return hoveredIndex === index ? selectionOpacity : selectionOpacity * 0.3\n }\n return selectionOpacity\n}\n\nexport interface ComposedLineProps {\n dataKey: string\n strokeVariant?: AreaStrokeVariant\n curveType?: ChartCurveType\n animationType?: ChartRevealType\n connectNulls?: boolean\n glowing?: boolean\n isClickable?: boolean\n children?: ReactNode\n lineProps?: ComponentProps<typeof RechartsLine>\n}\n\n/** One line series inside a composed chart. */\nfunction Line({\n dataKey,\n strokeVariant = 'solid',\n curveType,\n animationType,\n connectNulls = false,\n glowing = false,\n isClickable = false,\n children,\n lineProps,\n}: ComposedLineProps) {\n const {\n config,\n curveType: defaultCurve,\n animationType: defaultAnimation,\n isLoading,\n rows,\n selectedDataKey,\n selectDataKey,\n } = useComposedChart()\n const id = useId().replace(/:/g, '')\n const reduceMotion = useReducedMotion()\n\n if (isLoading) return null\n\n const resolvedCurve = curveType ?? defaultCurve\n const reveal: ChartRevealType = reduceMotion ? 'none' : (animationType ?? defaultAnimation)\n const maskId = reveal === 'none' ? undefined : `${id}-reveal-mask`\n\n const hasSelection = selectedDataKey !== null\n const dimmed = hasSelection && selectedDataKey !== dataKey\n const opacity = dimmed ? { stroke: 0.3, dot: 0.3 } : { stroke: 1, dot: 1 }\n\n const { dot, activeDot } = resolveDots(children, id, dataKey, opacity.dot, maskId)\n const labels = resolveValues(\n findSlot<ValuesProps>(children, Values),\n rows.map((row) => Number(row[dataKey])).filter(Number.isFinite),\n )\n const animateDash = strokeVariant === 'animated-dashed' && !hasSelection && !reduceMotion\n const isDashed = strokeVariant !== 'solid'\n\n const select = () => {\n if (!isClickable) return\n selectDataKey(selectedDataKey === dataKey ? null : dataKey)\n }\n\n return (\n <>\n {isClickable && (\n // A 20px transparent line under the visible one, giving a 2px stroke a\n // pointer target a finger can hit.\n <RechartsLine\n type={resolvedCurve}\n dataKey={dataKey}\n connectNulls={connectNulls}\n stroke=\"transparent\"\n strokeWidth={20}\n dot={false}\n activeDot={false}\n isAnimationActive={false}\n legendType=\"none\"\n tooltipType=\"none\"\n style={{ cursor: 'pointer' }}\n onClick={select}\n />\n )}\n <RechartsLine\n type={resolvedCurve}\n dataKey={dataKey}\n connectNulls={connectNulls}\n strokeOpacity={opacity.stroke}\n stroke={`url(#${id}-colors-${dataKey})`}\n filter={glowing ? `url(#${id}-glow-${dataKey})` : undefined}\n dot={dot}\n activeDot={activeDot}\n strokeWidth={STROKE_WIDTH}\n strokeDasharray={isDashed ? '5 5' : undefined}\n isAnimationActive={false}\n style={{\n ...(maskId ? { mask: `url(#${maskId})` } : {}),\n // The hit line underneath owns the pointer; without this the visible\n // line swallows the click it is sitting on top of.\n ...(isClickable ? { cursor: 'pointer', pointerEvents: 'none' } : {}),\n }}\n {...lineProps}\n >\n {animateDash && <AnimatedDashedStroke dash={5} />}\n {labels}\n </RechartsLine>\n <defs>\n {reveal !== 'none' && <RevealMask id={id} type={reveal} />}\n <SeriesGradient id={id} dataKey={dataKey} config={config} />\n {glowing && <GlowFilter id={id} dataKey={dataKey} spread={10} intensity={2} />}\n </defs>\n </>\n )\n}\n\nexport interface DotProps {\n variant?: ChartDotVariant\n}\n\n/** The resting point marker for the line it is composed inside. A slot. */\nconst Dot: FC<DotProps> = () => null\n\n/** The marker under the pointer. */\nconst ActiveDot: FC<DotProps> = () => null\n\nfunction resolveDots(\n children: ReactNode,\n id: string,\n dataKey: string,\n dotOpacity: number,\n maskId: string | undefined,\n) {\n let dot: ComponentProps<typeof RechartsLine>['dot'] = false\n let activeDot: ComponentProps<typeof RechartsLine>['activeDot'] = false\n\n Children.forEach(children, (child) => {\n if (!isValidElement(child)) return\n const { variant } = (child as ReactElement<DotProps>).props\n\n if (child.type === Dot) {\n dot = (\n <ChartDot\n variant={variant}\n dataKey={dataKey}\n chartId={id}\n fillOpacity={dotOpacity}\n maskId={maskId}\n />\n )\n }\n if (child.type === ActiveDot) {\n activeDot = (\n <ChartDot variant={variant} dataKey={dataKey} chartId={id} fillOpacity={dotOpacity} />\n )\n }\n })\n\n return { dot, activeDot }\n}\n\n/** The category axis. */\nfunction XAxis({\n tickLine = false,\n axisLine = false,\n tickMargin = 8,\n minTickGap = 8,\n label,\n ...rest\n}: ChartXAxisProps) {\n const { isLoading } = useComposedChart()\n if (isLoading) return null\n return (\n <RechartsXAxis\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 — the only one, by design. */\nfunction YAxis({\n tickLine = false,\n axisLine = false,\n tickMargin = 8,\n minTickGap = 8,\n width = 'auto',\n tickFormatter = defaultTick,\n label,\n ...rest\n}: ChartYAxisProps) {\n const { isLoading } = useComposedChart()\n if (isLoading) return null\n return (\n <RechartsYAxis\n tickLine={tickLine}\n axisLine={axisLine}\n tickMargin={tickMargin}\n minTickGap={minTickGap}\n width={width}\n tickFormatter={tickFormatter}\n {...rest}\n >\n {axisLabel(label, 'y')}\n </RechartsYAxis>\n )\n}\n\n/** Horizontal dashed rules. */\nfunction Grid({\n vertical = false,\n strokeDasharray = '3 3',\n ...rest\n}: ComponentProps<typeof CartesianGrid>) {\n return <CartesianGrid vertical={vertical} strokeDasharray={strokeDasharray} {...rest} />\n}\n\n/** The hover panel, with a crosshair along the axis. */\nfunction Tooltip({ variant, roundness, defaultIndex, cursor = true }: ChartTooltipSlotProps) {\n const { isLoading, selectedDataKey } = useComposedChart()\n if (isLoading) return null\n\n return (\n <ChartTooltip\n defaultIndex={defaultIndex}\n cursor={cursor ? { strokeDasharray: '3 3', strokeWidth: STROKE_WIDTH } : false}\n content={\n <ChartTooltipContent selected={selectedDataKey} roundness={roundness} variant={variant} />\n }\n />\n )\n}\n\n/** The key. Required above one series. */\nfunction Legend({\n variant,\n align = 'right',\n verticalAlign = 'top',\n isClickable = false,\n}: ChartLegendSlotProps) {\n const { selectedDataKey, selectDataKey } = useComposedChart()\n\n return (\n <ChartLegend\n verticalAlign={verticalAlign}\n align={align}\n content={\n <ChartLegendContent\n selected={selectedDataKey}\n onSelectChange={selectDataKey}\n isClickable={isClickable}\n variant={variant}\n />\n }\n />\n )\n}\n\n/** The decorative plate behind the plot. */\nfunction Background({ variant }: { variant: ChartBackgroundVariant }) {\n const { isLoading } = useComposedChart()\n if (isLoading) return null\n return <ChartBackground variant={variant} />\n}\n\n/** The skeleton drawn in place of the real marks. */\nfunction LoadingBar({ chartId, onShimmerExit }: { chartId: string; onShimmerExit: () => void }) {\n return (\n <>\n <RechartsBar\n dataKey={LOADING_KEY}\n fill=\"currentColor\"\n fillOpacity={0.15}\n radius={BAR_RADIUS}\n isAnimationActive={false}\n legendType=\"none\"\n className=\"text-(--ink)\"\n style={{ mask: `url(#${chartId}-loading-mask)` }}\n />\n <defs>\n <LoadingShimmer id={chartId} onShimmerExit={onShimmerExit} />\n </defs>\n </>\n )\n}\n\nComposedChart.Bar = Bar\nComposedChart.Line = Line\nComposedChart.Dot = Dot\nComposedChart.ActiveDot = ActiveDot\nComposedChart.XAxis = XAxis\nComposedChart.YAxis = YAxis\nComposedChart.Grid = Grid\nComposedChart.Tooltip = Tooltip\nComposedChart.Legend = Legend\nComposedChart.Background = Background\nComposedChart.ReferenceLine = ReferenceLine\nComposedChart.ReferenceBand = ReferenceBand\nComposedChart.Annotation = Annotation\nComposedChart.Values = Values\nComposedChart.Brush = Brush\nComposedChart.Toolbar = Toolbar\n\nexport default ComposedChart\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/charts/ComposedChart/ComposedChart.tsx"],"sourcesContent":["'use client'\n\nimport {\n Children,\n createContext,\n isValidElement,\n useContext,\n useId,\n useMemo,\n useState,\n type ComponentProps,\n type FC,\n type ReactElement,\n type ReactNode,\n} from 'react'\nimport {\n Bar as RechartsBar,\n CartesianGrid,\n ComposedChart as RechartsComposedChart,\n Line as RechartsLine,\n XAxis as RechartsXAxis,\n YAxis as RechartsYAxis,\n} from 'recharts'\nimport { motion, useReducedMotion } from 'motion/react'\nimport { ChartContainer, type ChartConfig } from '../lib/chart'\nimport { ChartFigure } from '../lib/figure'\nimport { useChartSelection } from '../lib/selection'\nimport { type ChartEmptyProps } from '../lib/empty'\nimport { axisLabel } from '../lib/axis'\nimport { defaultTick } from '../lib/format'\nimport { Annotation, ReferenceBand, ReferenceLine } from '../lib/annotations'\nimport { Values, findSlot, resolveValues, type ValuesProps } from '../lib/values'\n\n/**\n * The axis props, with `label` narrowed to a string.\n *\n * Recharts' own `label` accepts a render function and a config object, neither\n * of which a call site should be reaching for to name an axis — and both of\n * which would let a consumer opt out of the system's own axis type styling\n * without noticing.\n */\nexport type ChartXAxisProps = Omit<ComponentProps<typeof RechartsXAxis>, 'label'> & {\n /** What this axis measures. Say the unit here or in the series label, once. */\n label?: string\n}\n\nexport type ChartYAxisProps = Omit<ComponentProps<typeof RechartsYAxis>, 'label'> & {\n /** What this axis measures. Say the unit here or in the series label, once. */\n label?: string\n}\n\nimport { ChartBackground, type ChartBackgroundVariant } from '../lib/background'\nimport { ChartDot, type ChartDotVariant } from '../lib/dot'\nimport { ChartLegend, ChartLegendContent } from '../lib/legend'\nimport { ChartTooltip, ChartTooltipContent } from '../lib/tooltip'\nimport { Brush, ChartBrush, type BrushProps, type ChartBrushRange } from '../lib/brush'\nimport { ChartControls, Toolbar, type ToolbarProps } from '../lib/toolbar'\nimport { useChartZoom } from '../lib/zoom'\nimport { LoadingIndicator, LoadingShimmer, LOADING_KEY, useLoadingRows } from '../lib/loading'\nimport {\n AnimatedDashedStroke,\n BarDuotoneFill,\n BarGradientFill,\n BarHatchedFill,\n BarStrippedFill,\n GlowFilter,\n REVEAL_EASE,\n RevealMask,\n SeriesGradient,\n type ChartRevealType,\n} from '../lib/paint'\nimport type { BarVariant } from '../BarChart/BarChart'\nimport type {\n AreaStrokeVariant,\n ChartCurveType,\n ChartLegendSlotProps,\n ChartTooltipSlotProps,\n} from '../AreaChart/AreaChart'\n\nconst STROKE_WIDTH = 2\nconst BAR_RADIUS = 4\nconst GROW_DURATION = 0.5\nconst GROW_STAGGER = 0.05\n\n/**\n * Room around the plot, so the first and last tick labels are not sliced by the\n * container's edge. Recharts' own default is 5px on every side, which is not\n * enough for a label centred on the axis's first point.\n */\nconst CHART_MARGIN = { top: 8, right: 12, bottom: 0, left: 12 }\n\ninterface ComposedChartContextValue {\n config: ChartConfig\n /** The rows on screen, so a mark can label its own extremes. */\n rows: Record<string, unknown>[]\n curveType: ChartCurveType\n animationType: ChartRevealType\n introStartedAt: number\n dataLength: number\n isLoading: boolean\n hoveredIndex: number | null\n selectedDataKey: string | null\n selectDataKey: (dataKey: string | null) => void\n}\n\nconst ComposedChartContext = createContext<ComposedChartContextValue | null>(null)\n\nfunction useComposedChart(): ComposedChartContextValue {\n const context = useContext(ComposedChartContext)\n if (!context) throw new Error('A composed chart part must be rendered inside <ComposedChart>')\n return context\n}\n\ntype ValidateKeys<TData, TConfig> = {\n [K in keyof TConfig]: K extends keyof TData ? ChartConfig[string] : never\n}\n\nexport interface ComposedChartProps<\n TData extends Record<string, unknown>,\n TConfig extends Record<string, ChartConfig[string]>,\n> {\n config: TConfig & ValidateKeys<TData, TConfig>\n /** The rows the chart draws. One entry per point, bar or category. */\n data: TData[]\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 /**\n * The composed parts — axes, grid, tooltip, legend, and the marks\n * themselves.\n */\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 RechartsComposedChart>\n /**\n * How the line between two points is interpolated. Every mark inherits it\n * unless it says otherwise.\n */\n curveType?: ChartCurveType\n animationType?: ChartRevealType\n barGap?: number\n barCategoryGap?: number\n /** The series lit on first render, when the chart keeps its own selection. */\n defaultSelectedDataKey?: string | null\n /**\n * The selected series, driven from outside.\n *\n * Give this and the chart follows it; leave it undefined and the chart keeps\n * its own, starting from `defaultSelectedDataKey`.\n */\n selectedDataKey?: string | null\n /** Fires when the selection changes, and with null when it is cleared. */\n onSelectionChange?: (selectedDataKey: string | null) => void\n /**\n * Swaps the marks for an animated skeleton, keeping the measured height so\n * the page does not jump when the data lands.\n */\n isLoading?: boolean\n /** How many bars the skeleton draws. */\n loadingBars?: number\n /**\n * The row field on the category axis. Needed by the brush and by the table\n * view.\n */\n xDataKey?: keyof TData & string\n /**\n * Drops the hidden table view. Only correct when the page prints the data\n * itself.\n */\n hideDataTable?: boolean\n /**\n * What the chart shows when it has nothing to draw.\n *\n * Rendered instead of the plot whenever `data` is empty and the chart is not\n * loading — the state a real dashboard reaches within a week, and the one an\n * empty pair of axes is indistinguishable from a failed load. `false` keeps\n * the axes, for a chart whose emptiness is itself the reading.\n */\n empty?: ChartEmptyProps | false\n}\n\n/**\n * Bars and lines over one axis — the shape for \"the volume, and the rate it\n * moved at\".\n *\n * One axis, always. Two measures at different scales belong in two charts or\n * indexed to a common base; a second y-scale lets the author choose where the\n * lines cross, which is the single most misleading thing a chart can do.\n *\n * @example\n * <ComposedChart title=\"Revenue and profit\" config={config} data={data}>\n * <ComposedChart.Grid />\n * <ComposedChart.XAxis dataKey=\"month\" />\n * <ComposedChart.Tooltip />\n * <ComposedChart.Bar dataKey=\"revenue\" variant=\"duotone\" />\n * <ComposedChart.Line dataKey=\"profit\" />\n * </ComposedChart>\n */\nexport function ComposedChart<\n TData extends Record<string, unknown>,\n TConfig extends Record<string, ChartConfig[string]>,\n>({\n config,\n data,\n title,\n showTitle,\n description,\n children,\n className,\n chartProps,\n curveType = 'linear',\n animationType = 'forward',\n barGap,\n barCategoryGap,\n defaultSelectedDataKey = null,\n selectedDataKey: controlledDataKey,\n onSelectionChange,\n isLoading = false,\n loadingBars,\n xDataKey,\n hideDataTable = false,\n empty,\n}: ComposedChartProps<TData, TConfig>) {\n const chartId = useId().replace(/:/g, '')\n const [introStartedAt] = useState(() => Date.now())\n const [hoveredIndex, setHoveredIndex] = useState<number | null>(null)\n const { rows: loadingData, onShimmerExit } = useLoadingRows(isLoading, loadingBars ?? 12)\n // One window, two views of it: the brush strip below the plot and the\n // toolbar above it both read and write this. See `useChartZoom` for why they\n // are not two separate windows.\n const zoom = useChartZoom({ data, xDataKey })\n\n // The brush and the toolbar are declared as children but never rendered into\n // the Recharts tree — one is a footer under the plot and the other a row\n // above it, and both live outside the SVG.\n const { brushSlot, toolbarSlot, chartChildren } = useMemo(() => {\n const parts = Children.toArray(children)\n const element = parts.find((child) => isValidElement(child) && child.type === Brush)\n const props = (isValidElement(element) ? element.props : {}) as BrushProps\n return {\n brushSlot: { present: isValidElement(element), ...props },\n toolbarSlot: findSlot<ToolbarProps>(parts, Toolbar),\n chartChildren: parts.filter(\n (child) => !(isValidElement(child) && (child.type === Brush || child.type === Toolbar)),\n ),\n }\n }, [children])\n\n const columns = useMemo(\n () => Object.entries(config).map(([key, series]) => ({ key, label: series.label })),\n [config],\n )\n\n const showBrush = brushSlot.present && !isLoading\n const showToolbar = toolbarSlot !== null && !isLoading\n // `empty === false` keeps the axes, for a chart whose emptiness is the\n // reading — a monitoring panel that should show a flat, silent range rather\n // than a message.\n const isEmpty = !isLoading && empty !== false && data.length === 0\n const displayData = showBrush || showToolbar ? zoom.visibleData : data\n\n const [selectedDataKey, selectDataKey] = useChartSelection(\n controlledDataKey,\n defaultSelectedDataKey,\n onSelectionChange,\n )\n\n const context = useMemo<ComposedChartContextValue>(\n () => ({\n config,\n rows: displayData as Record<string, unknown>[],\n curveType,\n animationType,\n introStartedAt,\n dataLength: displayData.length,\n isLoading,\n hoveredIndex,\n selectedDataKey,\n selectDataKey,\n }),\n [\n animationType,\n config,\n curveType,\n displayData,\n hoveredIndex,\n introStartedAt,\n isLoading,\n selectDataKey,\n selectedDataKey,\n ],\n )\n\n return (\n <ComposedChartContext.Provider value={context}>\n <ChartFigure\n title={title}\n showTitle={showTitle}\n description={description}\n className={className}\n table={\n hideDataTable\n ? false\n : { rows: data, rowKey: xDataKey, columns }\n }\n isEmpty={isEmpty}\n empty={empty}\n >\n <ChartControls\n toolbar={showToolbar ? toolbarSlot : null}\n zoom={zoom}\n title={title}\n rows={data}\n columns={columns}\n rowKey={xDataKey}\n >\n <ChartContainer\n config={config}\n footer={\n showBrush && (\n <ChartBrush\n data={data}\n config={config}\n xDataKey={xDataKey}\n variant=\"area\"\n curveType={curveType}\n height={brushSlot.height}\n formatLabel={brushSlot.formatLabel}\n className=\"mt-1\"\n {...zoom.brushProps}\n onChange={(range: ChartBrushRange) => {\n zoom.brushProps.onChange(range)\n brushSlot.onChange?.(range)\n }}\n />\n )\n }\n >\n <RechartsComposedChart\n id={chartId}\n accessibilityLayer\n margin={CHART_MARGIN}\n data={isLoading ? loadingData : displayData}\n barGap={barGap}\n barCategoryGap={barCategoryGap}\n // The hovered column, read from Recharts' own tooltip cursor. This\n // was the missing half of `enableHoverHighlight`: the state existed\n // and was cleared on leave, but nothing ever set it, so the\n // highlight never fired.\n onMouseMove={(state) => {\n const index = (state as { activeTooltipIndex?: number } | undefined)\n ?.activeTooltipIndex\n setHoveredIndex(typeof index === 'number' ? index : null)\n }}\n onMouseLeave={() => setHoveredIndex(null)}\n {...chartProps}\n >\n {chartChildren}\n {isLoading && <LoadingBar chartId={chartId} onShimmerExit={onShimmerExit} />}\n </RechartsComposedChart>\n </ChartContainer>\n </ChartControls>\n <LoadingIndicator isLoading={isLoading} />\n </ChartFigure>\n </ComposedChartContext.Provider>\n )\n}\n\nexport interface ComposedBarProps {\n dataKey: string\n variant?: BarVariant\n radius?: number\n /** A halo behind the bars. */\n glowing?: boolean\n animationType?: ChartRevealType\n isClickable?: boolean\n /** Dims this series while another COLUMN is hovered. */\n enableHoverHighlight?: boolean\n /** `<ComposedChart.Values>`. */\n children?: ReactNode\n barProps?: ComponentProps<typeof RechartsBar>\n}\n\n/** One bar series inside a composed chart. */\nfunction Bar({\n dataKey,\n variant = 'default',\n radius = BAR_RADIUS,\n glowing = false,\n animationType,\n isClickable = false,\n enableHoverHighlight = false,\n children,\n barProps,\n}: ComposedBarProps) {\n const {\n config,\n animationType: defaultAnimation,\n introStartedAt,\n dataLength,\n isLoading,\n rows,\n hoveredIndex,\n selectedDataKey,\n selectDataKey,\n } = useComposedChart()\n const id = useId().replace(/:/g, '')\n const reduceMotion = useReducedMotion()\n\n if (isLoading) return null\n\n const isSelected = selectedDataKey === null || selectedDataKey === dataKey\n const reveal: ChartRevealType = reduceMotion ? 'none' : (animationType ?? defaultAnimation)\n\n return (\n <>\n <RechartsBar\n dataKey={dataKey}\n fill={`url(#${id}-colors-${dataKey})`}\n radius={radius}\n isAnimationActive={false}\n style={isClickable || enableHoverHighlight ? { cursor: 'pointer' } : undefined}\n shape={(props: unknown) => {\n const geometry = props as BarGeometry\n const index = typeof geometry.index === 'number' ? geometry.index : -1\n\n return (\n <ComposedBarShape\n {...geometry}\n id={id}\n dataKey={dataKey}\n variant={variant}\n barRadius={radius}\n filter={glowing ? `url(#${id}-glow-${dataKey})` : undefined}\n fillOpacity={barOpacity({\n isClickable,\n isSelected,\n selectedDataKey,\n enableHoverHighlight,\n hoveredIndex,\n index,\n })}\n isClickable={isClickable}\n enableHoverHighlight={enableHoverHighlight}\n animationType={reveal}\n dataLength={dataLength}\n introStartedAt={introStartedAt}\n onSelect={() => {\n if (!isClickable) return\n selectDataKey(selectedDataKey === dataKey ? null : dataKey)\n }}\n />\n )\n }}\n {...barProps}\n >\n {resolveValues(\n findSlot<ValuesProps>(children, Values),\n rows.map((row) => Number(row[dataKey])).filter(Number.isFinite),\n )}\n </RechartsBar>\n <defs>\n <SeriesGradient id={id} dataKey={dataKey} config={config} direction=\"vertical\" />\n {variant === 'hatched' && <BarHatchedFill id={id} dataKey={dataKey} />}\n {variant === 'duotone' && <BarDuotoneFill id={id} dataKey={dataKey} config={config} />}\n {variant === 'duotone-reverse' && (\n <BarDuotoneFill id={id} dataKey={dataKey} config={config} reverse />\n )}\n {variant === 'gradient' && <BarGradientFill id={id} dataKey={dataKey} />}\n {variant === 'stripped' && <BarStrippedFill id={id} dataKey={dataKey} />}\n {glowing && <GlowFilter id={id} dataKey={dataKey} />}\n </defs>\n </>\n )\n}\n\ninterface BarGeometry {\n x?: number\n y?: number\n width?: number\n height?: number\n index?: number\n background?: { x?: number; y?: number; width?: number; height?: number }\n [key: string]: unknown\n}\n\n/** One drawn bar, with an invisible full-column rect keeping it hoverable. */\nfunction ComposedBarShape({\n x = 0,\n y = 0,\n width = 0,\n height = 0,\n background,\n index = -1,\n id,\n dataKey,\n variant,\n barRadius,\n filter,\n fillOpacity,\n isClickable,\n enableHoverHighlight,\n animationType,\n dataLength,\n introStartedAt,\n onSelect,\n}: BarGeometry & {\n id: string\n dataKey: string\n variant: BarVariant\n barRadius: number\n filter?: string\n fillOpacity: number\n isClickable: boolean\n enableHoverHighlight: boolean\n animationType: ChartRevealType\n dataLength: number\n introStartedAt: number\n onSelect: () => void\n}) {\n const cursor = isClickable || enableHoverHighlight ? { cursor: 'pointer' } : undefined\n const grow = growAnimation(animationType, index, dataLength, introStartedAt) ?? {}\n const fill =\n variant === 'default'\n ? `url(#${id}-colors-${dataKey})`\n : `url(#${id}-${variant}-${dataKey})`\n\n const hitArea = (\n <rect\n x={background?.x ?? x}\n y={background?.y ?? y}\n width={background?.width ?? width}\n height={background?.height ?? height}\n fill=\"transparent\"\n />\n )\n\n if (variant === 'stripped') {\n return (\n <g style={cursor} onClick={onSelect}>\n <motion.g\n {...grow}\n filter={filter}\n opacity={fillOpacity}\n className=\"transition-opacity duration-(--duration-base)\"\n >\n <rect x={x} y={y} width={width} height={height} fill={fill} />\n <rect x={x} y={y} width={width} height={2} fill={`url(#${id}-colors-${dataKey})`} />\n </motion.g>\n {hitArea}\n </g>\n )\n }\n\n return (\n <g style={cursor} onClick={onSelect}>\n <motion.g {...grow}>\n <rect\n x={x}\n y={y}\n width={width}\n height={height}\n rx={barRadius}\n ry={barRadius}\n fill={fill}\n opacity={fillOpacity}\n filter={filter}\n className=\"transition-opacity duration-(--duration-base)\"\n />\n </motion.g>\n {hitArea}\n </g>\n )\n}\n\n/** The staggered grow-in, anchored to the chart rather than to the bar's mount. */\nfunction growAnimation(\n animationType: ChartRevealType,\n index: number,\n dataLength: number,\n introStartedAt: number,\n) {\n if (animationType === 'none' || index < 0 || dataLength <= 0) return null\n\n const lastIndex = dataLength - 1\n const centre = lastIndex / 2\n\n let step: number\n switch (animationType) {\n case 'reverse':\n step = lastIndex - index\n break\n case 'center-out':\n step = Math.abs(index - centre)\n break\n case 'edges-in':\n step = centre - Math.abs(index - centre)\n break\n default:\n step = index\n }\n\n const startMs = step * GROW_STAGGER * 1000\n const endMs = startMs + GROW_DURATION * 1000\n const elapsed = Date.now() - introStartedAt\n if (elapsed >= endMs) return null\n\n const from = elapsed <= startMs ? 0 : (elapsed - startMs) / (GROW_DURATION * 1000)\n\n return {\n initial: { scaleY: from },\n animate: { scaleY: 1 },\n transition: {\n duration: (endMs - Math.max(elapsed, startMs)) / 1000,\n ease: REVEAL_EASE,\n delay: Math.max(0, startMs - elapsed) / 1000,\n },\n style: { originY: 1 },\n }\n}\n\n/** How lit a bar is, given the selection and the hovered column. */\nfunction barOpacity({\n isClickable,\n isSelected,\n selectedDataKey,\n enableHoverHighlight,\n hoveredIndex,\n index,\n}: {\n isClickable: boolean\n isSelected: boolean\n selectedDataKey: string | null\n enableHoverHighlight: boolean\n hoveredIndex: number | null\n index: number\n}) {\n const selectionOpacity = isClickable && selectedDataKey !== null ? (isSelected ? 1 : 0.15) : 1\n if (enableHoverHighlight && hoveredIndex !== null) {\n return hoveredIndex === index ? selectionOpacity : selectionOpacity * 0.3\n }\n return selectionOpacity\n}\n\nexport interface ComposedLineProps {\n dataKey: string\n strokeVariant?: AreaStrokeVariant\n curveType?: ChartCurveType\n animationType?: ChartRevealType\n connectNulls?: boolean\n glowing?: boolean\n isClickable?: boolean\n children?: ReactNode\n lineProps?: ComponentProps<typeof RechartsLine>\n}\n\n/** One line series inside a composed chart. */\nfunction Line({\n dataKey,\n strokeVariant = 'solid',\n curveType,\n animationType,\n connectNulls = false,\n glowing = false,\n isClickable = false,\n children,\n lineProps,\n}: ComposedLineProps) {\n const {\n config,\n curveType: defaultCurve,\n animationType: defaultAnimation,\n isLoading,\n rows,\n selectedDataKey,\n selectDataKey,\n } = useComposedChart()\n const id = useId().replace(/:/g, '')\n const reduceMotion = useReducedMotion()\n\n if (isLoading) return null\n\n const resolvedCurve = curveType ?? defaultCurve\n const reveal: ChartRevealType = reduceMotion ? 'none' : (animationType ?? defaultAnimation)\n const maskId = reveal === 'none' ? undefined : `${id}-reveal-mask`\n\n const hasSelection = selectedDataKey !== null\n const dimmed = hasSelection && selectedDataKey !== dataKey\n const opacity = dimmed ? { stroke: 0.3, dot: 0.3 } : { stroke: 1, dot: 1 }\n\n const { dot, activeDot } = resolveDots(children, id, dataKey, opacity.dot, maskId)\n const labels = resolveValues(\n findSlot<ValuesProps>(children, Values),\n rows.map((row) => Number(row[dataKey])).filter(Number.isFinite),\n )\n const animateDash = strokeVariant === 'animated-dashed' && !hasSelection && !reduceMotion\n const isDashed = strokeVariant !== 'solid'\n\n const select = () => {\n if (!isClickable) return\n selectDataKey(selectedDataKey === dataKey ? null : dataKey)\n }\n\n return (\n <>\n {isClickable && (\n // A 20px transparent line under the visible one, giving a 2px stroke a\n // pointer target a finger can hit.\n <RechartsLine\n type={resolvedCurve}\n dataKey={dataKey}\n connectNulls={connectNulls}\n stroke=\"transparent\"\n strokeWidth={20}\n dot={false}\n activeDot={false}\n isAnimationActive={false}\n legendType=\"none\"\n tooltipType=\"none\"\n style={{ cursor: 'pointer' }}\n onClick={select}\n />\n )}\n <RechartsLine\n type={resolvedCurve}\n dataKey={dataKey}\n connectNulls={connectNulls}\n strokeOpacity={opacity.stroke}\n stroke={`url(#${id}-colors-${dataKey})`}\n filter={glowing ? `url(#${id}-glow-${dataKey})` : undefined}\n dot={dot}\n activeDot={activeDot}\n strokeWidth={STROKE_WIDTH}\n strokeDasharray={isDashed ? '5 5' : undefined}\n isAnimationActive={false}\n style={{\n ...(maskId ? { mask: `url(#${maskId})` } : {}),\n // The hit line underneath owns the pointer; without this the visible\n // line swallows the click it is sitting on top of.\n ...(isClickable ? { cursor: 'pointer', pointerEvents: 'none' } : {}),\n }}\n {...lineProps}\n >\n {animateDash && <AnimatedDashedStroke dash={5} />}\n {labels}\n </RechartsLine>\n <defs>\n {reveal !== 'none' && <RevealMask id={id} type={reveal} />}\n <SeriesGradient id={id} dataKey={dataKey} config={config} />\n {glowing && <GlowFilter id={id} dataKey={dataKey} spread={10} intensity={2} />}\n </defs>\n </>\n )\n}\n\nexport interface DotProps {\n variant?: ChartDotVariant\n}\n\n/** The resting point marker for the line it is composed inside. A slot. */\nconst Dot: FC<DotProps> = () => null\n\n/** The marker under the pointer. */\nconst ActiveDot: FC<DotProps> = () => null\n\nfunction resolveDots(\n children: ReactNode,\n id: string,\n dataKey: string,\n dotOpacity: number,\n maskId: string | undefined,\n) {\n let dot: ComponentProps<typeof RechartsLine>['dot'] = false\n let activeDot: ComponentProps<typeof RechartsLine>['activeDot'] = false\n\n Children.forEach(children, (child) => {\n if (!isValidElement(child)) return\n const { variant } = (child as ReactElement<DotProps>).props\n\n if (child.type === Dot) {\n dot = (\n <ChartDot\n variant={variant}\n dataKey={dataKey}\n chartId={id}\n fillOpacity={dotOpacity}\n maskId={maskId}\n />\n )\n }\n if (child.type === ActiveDot) {\n activeDot = (\n <ChartDot variant={variant} dataKey={dataKey} chartId={id} fillOpacity={dotOpacity} />\n )\n }\n })\n\n return { dot, activeDot }\n}\n\n/** The category axis. */\nfunction XAxis({\n tickLine = false,\n axisLine = false,\n tickMargin = 8,\n minTickGap = 8,\n label,\n ...rest\n}: ChartXAxisProps) {\n const { isLoading } = useComposedChart()\n if (isLoading) return null\n return (\n <RechartsXAxis\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 — the only one, by design. */\nfunction YAxis({\n tickLine = false,\n axisLine = false,\n tickMargin = 8,\n minTickGap = 8,\n width = 'auto',\n tickFormatter = defaultTick,\n label,\n ...rest\n}: ChartYAxisProps) {\n const { isLoading } = useComposedChart()\n if (isLoading) return null\n return (\n <RechartsYAxis\n tickLine={tickLine}\n axisLine={axisLine}\n tickMargin={tickMargin}\n minTickGap={minTickGap}\n width={width}\n tickFormatter={tickFormatter}\n {...rest}\n >\n {axisLabel(label, 'y')}\n </RechartsYAxis>\n )\n}\n\n/** Horizontal dashed rules. */\nfunction Grid({\n vertical = false,\n strokeDasharray = '3 3',\n ...rest\n}: ComponentProps<typeof CartesianGrid>) {\n return <CartesianGrid vertical={vertical} strokeDasharray={strokeDasharray} {...rest} />\n}\n\n/** The hover panel, with a crosshair along the axis. */\nfunction Tooltip({ variant, roundness, defaultIndex, cursor = true }: ChartTooltipSlotProps) {\n const { isLoading, selectedDataKey } = useComposedChart()\n if (isLoading) return null\n\n return (\n <ChartTooltip\n defaultIndex={defaultIndex}\n cursor={cursor ? { strokeDasharray: '3 3', strokeWidth: STROKE_WIDTH } : false}\n content={\n <ChartTooltipContent selected={selectedDataKey} roundness={roundness} variant={variant} />\n }\n />\n )\n}\n\n/** The key. Required above one series. */\nfunction Legend({\n variant,\n align = 'right',\n verticalAlign = 'top',\n isClickable = false,\n}: ChartLegendSlotProps) {\n const { selectedDataKey, selectDataKey } = useComposedChart()\n\n return (\n <ChartLegend\n verticalAlign={verticalAlign}\n align={align}\n content={\n <ChartLegendContent\n selected={selectedDataKey}\n onSelectChange={selectDataKey}\n isClickable={isClickable}\n variant={variant}\n />\n }\n />\n )\n}\n\n/** The decorative plate behind the plot. */\nfunction Background({ variant }: { variant: ChartBackgroundVariant }) {\n const { isLoading } = useComposedChart()\n if (isLoading) return null\n return <ChartBackground variant={variant} />\n}\n\n/** The skeleton drawn in place of the real marks. */\nfunction LoadingBar({ chartId, onShimmerExit }: { chartId: string; onShimmerExit: () => void }) {\n return (\n <>\n <RechartsBar\n dataKey={LOADING_KEY}\n fill=\"currentColor\"\n fillOpacity={0.15}\n radius={BAR_RADIUS}\n isAnimationActive={false}\n legendType=\"none\"\n className=\"text-(--ink)\"\n style={{ mask: `url(#${chartId}-loading-mask)` }}\n />\n <defs>\n <LoadingShimmer id={chartId} onShimmerExit={onShimmerExit} />\n </defs>\n </>\n )\n}\n\nComposedChart.Bar = Bar\nComposedChart.Line = Line\nComposedChart.Dot = Dot\nComposedChart.ActiveDot = ActiveDot\nComposedChart.XAxis = XAxis\nComposedChart.YAxis = YAxis\nComposedChart.Grid = Grid\nComposedChart.Tooltip = Tooltip\nComposedChart.Legend = Legend\nComposedChart.Background = Background\nComposedChart.ReferenceLine = ReferenceLine\nComposedChart.ReferenceBand = ReferenceBand\nComposedChart.Annotation = Annotation\nComposedChart.Values = Values\nComposedChart.Brush = Brush\nComposedChart.Toolbar = Toolbar\n\nexport default ComposedChart\n"],"mappings":"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","names":[]}
@@ -2,8 +2,8 @@ import * as react from 'react';
2
2
  import { ReactNode, ComponentProps } from 'react';
3
3
  import { FunnelChart as FunnelChart$1, Funnel, LabelList } from 'recharts';
4
4
  import { ChartConfig } from '../lib/chart.js';
5
+ import { ChartEmptyProps } from '../lib/empty.js';
5
6
  import { ChartTooltipSlotProps } from '../AreaChart/AreaChart.js';
6
- import '../lib/empty.js';
7
7
  import '../lib/values.js';
8
8
  import '../lib/sonify-control.js';
9
9
  import '../lib/sonify.js';
@@ -53,6 +53,11 @@ interface FunnelChartProps<TData extends Record<string, unknown>> {
53
53
  chartProps?: ComponentProps<typeof FunnelChart$1>;
54
54
  /** Drops the hidden table view. Only correct when the page prints the data itself. */
55
55
  hideDataTable?: boolean;
56
+ /**
57
+ * What the chart shows when it has nothing to draw. `false` keeps the empty
58
+ * plot, for a chart whose emptiness is itself the reading.
59
+ */
60
+ empty?: ChartEmptyProps | false;
56
61
  }
57
62
  /**
58
63
  * Stages that only ever narrow — a signup flow, a hiring pipeline, a checkout.
@@ -74,7 +79,7 @@ interface FunnelChartProps<TData extends Record<string, unknown>> {
74
79
  * <FunnelChart.Tooltip />
75
80
  * </FunnelChart>
76
81
  */
77
- declare function FunnelChart<TData extends Record<string, unknown>>({ config, data, dataKey, nameKey, title, showTitle, description, children, className, chartProps, hideDataTable, }: FunnelChartProps<TData>): react.JSX.Element;
82
+ declare function FunnelChart<TData extends Record<string, unknown>>({ config, data, dataKey, nameKey, title, showTitle, description, children, className, chartProps, hideDataTable, empty, }: FunnelChartProps<TData>): react.JSX.Element;
78
83
  declare namespace FunnelChart {
79
84
  var Funnel: ({ variant, orientation, gap, children, funnelProps, }: FunnelProps) => react.JSX.Element;
80
85
  var Label: ({ dataKey, position, labelListProps }: FunnelLabelProps) => react.JSX.Element;
@@ -13,6 +13,7 @@ import {
13
13
  } from "recharts";
14
14
  import { ChartContainer, colorStops, cssName } from "../lib/chart.js";
15
15
  import { ChartFigure } from "../lib/figure.js";
16
+ import {} from "../lib/empty.js";
16
17
  import { ChartTooltip, ChartTooltipContent } from "../lib/tooltip.js";
17
18
  import { ColorStops } from "../lib/paint.js";
18
19
  const FunnelChartContext = createContext(null);
@@ -32,7 +33,8 @@ function FunnelChart({
32
33
  children,
33
34
  className,
34
35
  chartProps,
35
- hideDataTable = false
36
+ hideDataTable = false,
37
+ empty
36
38
  }) {
37
39
  const chartId = useId().replace(/:/g, "");
38
40
  const context = useMemo(
@@ -47,21 +49,9 @@ function FunnelChart({
47
49
  description,
48
50
  className,
49
51
  table: hideDataTable ? false : { rows: data, rowKey: nameKey, columns: [{ key: dataKey, label: "Value" }] },
50
- children: /* @__PURE__ */ jsx(ChartContainer, { config, children: /* @__PURE__ */ jsxs(RechartsFunnelChart, { id: chartId, accessibilityLayer: true, ...chartProps, children: [
51
- children,
52
- /* @__PURE__ */ jsx("defs", { children: Object.entries(config).map(([key, series]) => /* @__PURE__ */ jsx(
53
- "linearGradient",
54
- {
55
- id: `${chartId}-stage-${cssName(key)}`,
56
- x1: "0",
57
- y1: "0",
58
- x2: "1",
59
- y2: "0",
60
- children: /* @__PURE__ */ jsx(ColorStops, { dataKey: cssName(key), stops: colorStops(series) })
61
- },
62
- key
63
- )) })
64
- ] }) })
52
+ isEmpty: data.length === 0,
53
+ empty,
54
+ children: /* @__PURE__ */ jsx(ChartContainer, { config, children: /* @__PURE__ */ jsx(RechartsFunnelChart, { id: chartId, accessibilityLayer: true, ...chartProps, children }) })
65
55
  }
66
56
  ) });
67
57
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/charts/FunnelChart/FunnelChart.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 Funnel as RechartsFunnel,\n FunnelChart as RechartsFunnelChart,\n LabelList,\n} from 'recharts'\nimport { ChartContainer, colorStops, cssName, type ChartConfig } from '../lib/chart'\nimport { ChartFigure } from '../lib/figure'\nimport { ChartTooltip, ChartTooltipContent } from '../lib/tooltip'\nimport { ColorStops } from '../lib/paint'\nimport type { ChartTooltipSlotProps } from '../AreaChart/AreaChart'\n\n/**\n * How each stage is painted.\n *\n * `stepped` is the honest default: every stage keeps the same fill and the\n * TAPER carries the drop. A ramp that also darkens each stage encodes the same\n * fact twice and invites the reader to compare two things that are one.\n */\nexport type FunnelVariant = 'stepped' | 'ramp'\n\ninterface FunnelChartContextValue {\n config: ChartConfig\n nameKey: string\n dataKey: string\n data: Record<string, unknown>[]\n}\n\nconst FunnelChartContext = createContext<FunnelChartContextValue | null>(null)\n\nfunction useFunnelChart(): FunnelChartContextValue {\n const context = useContext(FunnelChartContext)\n if (!context) throw new Error('A funnel chart part must be rendered inside <FunnelChart>')\n return context\n}\n\nexport interface FunnelChartProps<TData extends Record<string, unknown>> {\n /** Stage names → their label and paint. Keys must match the `nameKey` values. */\n config: ChartConfig\n /** The stages, widest first. Order is the funnel; it is not sorted for you. */\n data: TData[]\n /** The row field holding each stage's number. */\n dataKey: keyof TData & string\n /** The row field naming each stage. */\n nameKey: keyof TData & string\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 — `<Funnel>`, `<Tooltip>`. */\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 RechartsFunnelChart>\n /** Drops the hidden table view. Only correct when the page prints the data itself. */\n hideDataTable?: boolean\n}\n\n/**\n * Stages that only ever narrow — a signup flow, a hiring pipeline, a checkout.\n *\n * The caveat is the same one every funnel has: the taper encodes a RATIO\n * between neighbouring stages, and the eye reads the enclosed area, so a funnel\n * exaggerates a shallow drop and flattens a steep one. Where the exact fall-off\n * is the point, put the percentages on the stages — `<Funnel.Label>` does — or\n * use a `<BarChart>`, which encodes each stage on one honest scale.\n *\n * Reach for `<SankeyChart>` instead when the flow can SPLIT rather than only\n * shrink: a funnel has one path through it by construction.\n *\n * @example\n * <FunnelChart title=\"Signup funnel\" config={config} data={stages} dataKey=\"people\" nameKey=\"stage\">\n * <FunnelChart.Funnel>\n * <FunnelChart.Label />\n * </FunnelChart.Funnel>\n * <FunnelChart.Tooltip />\n * </FunnelChart>\n */\nexport function FunnelChart<TData extends Record<string, unknown>>({\n config,\n data,\n dataKey,\n nameKey,\n title,\n showTitle,\n description,\n children,\n className,\n chartProps,\n hideDataTable = false,\n}: FunnelChartProps<TData>) {\n const chartId = useId().replace(/:/g, '')\n\n const context = useMemo<FunnelChartContextValue>(\n () => ({ config, nameKey, dataKey, data }),\n [config, data, dataKey, nameKey],\n )\n\n return (\n <FunnelChartContext.Provider value={context}>\n <ChartFigure\n title={title}\n showTitle={showTitle}\n description={description}\n className={className}\n table={\n hideDataTable\n ? false\n : { rows: data, rowKey: nameKey, columns: [{ key: dataKey, label: 'Value' }] }\n }\n >\n <ChartContainer config={config}>\n <RechartsFunnelChart id={chartId} accessibilityLayer {...chartProps}>\n {children}\n <defs>\n {Object.entries(config).map(([key, series]) => (\n <linearGradient\n key={key}\n id={`${chartId}-stage-${cssName(key)}`}\n x1=\"0\"\n y1=\"0\"\n x2=\"1\"\n y2=\"0\"\n >\n <ColorStops dataKey={cssName(key)} stops={colorStops(series)} />\n </linearGradient>\n ))}\n </defs>\n </RechartsFunnelChart>\n </ChartContainer>\n </ChartFigure>\n </FunnelChartContext.Provider>\n )\n}\n\nexport interface FunnelProps {\n variant?: FunnelVariant\n /** Whether the funnel narrows downward or along the inline axis. */\n orientation?: 'vertical' | 'horizontal'\n /** Gap between stages, in pixels. */\n gap?: number\n /** `<FunnelChart.Label>`. */\n children?: ReactNode\n funnelProps?: Omit<ComponentProps<typeof RechartsFunnel>, 'data' | 'dataKey'>\n}\n\n/** The stages themselves. */\nfunction Funnel({\n variant = 'stepped',\n orientation = 'vertical',\n gap = 2,\n children,\n funnelProps,\n}: FunnelProps) {\n const { config, data, dataKey, nameKey } = useFunnelChart()\n const chartId = useId().replace(/:/g, '')\n\n // `ramp` walks the series ramp stage by stage; `stepped` holds slot one for\n // all of them and lets the shape do the talking.\n const painted = data.map((row, index) => {\n const name = String(row[nameKey])\n const slot = variant === 'ramp' ? `var(--series-${(index % 8) + 1})` : undefined\n return {\n ...row,\n fill: slot ?? (name in config ? `url(#${chartId}-stage-${cssName(name)})` : 'var(--series-1)'),\n }\n })\n\n return (\n <>\n <RechartsFunnel\n dataKey={dataKey}\n nameKey={nameKey}\n data={painted}\n isAnimationActive={false}\n // A surface-coloured stroke rather than a transparent gap: the stages\n // touch by construction, and a hairline of the page between them is\n // what makes two similar stages countable.\n stroke=\"var(--chart-surface)\"\n strokeWidth={gap}\n orientation={orientation}\n {...funnelProps}\n >\n {children}\n </RechartsFunnel>\n <defs>\n {Object.entries(config).map(([key, series]) => (\n <linearGradient\n key={key}\n id={`${chartId}-stage-${cssName(key)}`}\n x1=\"0\"\n y1=\"0\"\n x2=\"1\"\n y2=\"0\"\n >\n <ColorStops dataKey={cssName(key)} stops={colorStops(series)} />\n </linearGradient>\n ))}\n </defs>\n </>\n )\n}\n\nexport interface FunnelLabelProps {\n /** The field printed on each stage. Defaults to the stage's name. */\n dataKey?: string\n /** Where the label sits relative to its stage. */\n position?: ComponentProps<typeof LabelList>['position']\n labelListProps?: Omit<ComponentProps<typeof LabelList>, 'dataKey'>\n}\n\n/**\n * Names printed on the stages.\n *\n * Worth reaching for: a funnel's taper is a ratio the eye reads badly, and a\n * printed number is the relief for that.\n */\nfunction Label({ dataKey, position = 'right', labelListProps }: FunnelLabelProps) {\n const { nameKey } = useFunnelChart()\n\n return (\n <LabelList\n dataKey={dataKey ?? nameKey}\n position={position}\n stroke=\"none\"\n fontSize={12}\n className=\"fill-(--ink)\"\n {...labelListProps}\n />\n )\n}\n\n/** The hover panel. No heading: the stage's own name is the row label. */\nfunction Tooltip({ variant, roundness, defaultIndex }: ChartTooltipSlotProps) {\n const { nameKey } = useFunnelChart()\n\n return (\n <ChartTooltip\n defaultIndex={defaultIndex}\n cursor={false}\n content={\n <ChartTooltipContent nameKey={nameKey} hideLabel roundness={roundness} variant={variant} />\n }\n />\n )\n}\n\nFunnelChart.Funnel = Funnel\nFunnelChart.Label = Label\nFunnelChart.Tooltip = Tooltip\n\nexport default FunnelChart\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/charts/FunnelChart/FunnelChart.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 Funnel as RechartsFunnel,\n FunnelChart as RechartsFunnelChart,\n LabelList,\n} from 'recharts'\nimport { ChartContainer, colorStops, cssName, type ChartConfig } from '../lib/chart'\nimport { ChartFigure } from '../lib/figure'\nimport { type ChartEmptyProps } from '../lib/empty'\nimport { ChartTooltip, ChartTooltipContent } from '../lib/tooltip'\nimport { ColorStops } from '../lib/paint'\nimport type { ChartTooltipSlotProps } from '../AreaChart/AreaChart'\n\n/**\n * How each stage is painted.\n *\n * `stepped` is the honest default: every stage keeps the same fill and the\n * TAPER carries the drop. A ramp that also darkens each stage encodes the same\n * fact twice and invites the reader to compare two things that are one.\n */\nexport type FunnelVariant = 'stepped' | 'ramp'\n\ninterface FunnelChartContextValue {\n config: ChartConfig\n nameKey: string\n dataKey: string\n data: Record<string, unknown>[]\n}\n\nconst FunnelChartContext = createContext<FunnelChartContextValue | null>(null)\n\nfunction useFunnelChart(): FunnelChartContextValue {\n const context = useContext(FunnelChartContext)\n if (!context) throw new Error('A funnel chart part must be rendered inside <FunnelChart>')\n return context\n}\n\nexport interface FunnelChartProps<TData extends Record<string, unknown>> {\n /** Stage names → their label and paint. Keys must match the `nameKey` values. */\n config: ChartConfig\n /** The stages, widest first. Order is the funnel; it is not sorted for you. */\n data: TData[]\n /** The row field holding each stage's number. */\n dataKey: keyof TData & string\n /** The row field naming each stage. */\n nameKey: keyof TData & string\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 — `<Funnel>`, `<Tooltip>`. */\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 RechartsFunnelChart>\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 * Stages that only ever narrow — a signup flow, a hiring pipeline, a checkout.\n *\n * The caveat is the same one every funnel has: the taper encodes a RATIO\n * between neighbouring stages, and the eye reads the enclosed area, so a funnel\n * exaggerates a shallow drop and flattens a steep one. Where the exact fall-off\n * is the point, put the percentages on the stages — `<Funnel.Label>` does — or\n * use a `<BarChart>`, which encodes each stage on one honest scale.\n *\n * Reach for `<SankeyChart>` instead when the flow can SPLIT rather than only\n * shrink: a funnel has one path through it by construction.\n *\n * @example\n * <FunnelChart title=\"Signup funnel\" config={config} data={stages} dataKey=\"people\" nameKey=\"stage\">\n * <FunnelChart.Funnel>\n * <FunnelChart.Label />\n * </FunnelChart.Funnel>\n * <FunnelChart.Tooltip />\n * </FunnelChart>\n */\nexport function FunnelChart<TData extends Record<string, unknown>>({\n config,\n data,\n dataKey,\n nameKey,\n title,\n showTitle,\n description,\n children,\n className,\n chartProps,\n hideDataTable = false,\n empty,\n}: FunnelChartProps<TData>) {\n const chartId = useId().replace(/:/g, '')\n\n const context = useMemo<FunnelChartContextValue>(\n () => ({ config, nameKey, dataKey, data }),\n [config, data, dataKey, nameKey],\n )\n\n return (\n <FunnelChartContext.Provider value={context}>\n <ChartFigure\n title={title}\n showTitle={showTitle}\n description={description}\n className={className}\n table={\n hideDataTable\n ? false\n : { rows: data, rowKey: nameKey, columns: [{ key: dataKey, label: 'Value' }] }\n }\n isEmpty={data.length === 0}\n empty={empty}\n >\n <ChartContainer config={config}>\n {/* No `<defs>` here. `<Funnel>` scopes the stage gradients under its\n OWN generated id and paints from those, so a second set under the\n root's id was markup nothing referenced — one gradient definition\n per stage, per chart, drawn and never used. */}\n <RechartsFunnelChart id={chartId} accessibilityLayer {...chartProps}>\n {children}\n </RechartsFunnelChart>\n </ChartContainer>\n </ChartFigure>\n </FunnelChartContext.Provider>\n )\n}\n\nexport interface FunnelProps {\n variant?: FunnelVariant\n /** Whether the funnel narrows downward or along the inline axis. */\n orientation?: 'vertical' | 'horizontal'\n /** Gap between stages, in pixels. */\n gap?: number\n /** `<FunnelChart.Label>`. */\n children?: ReactNode\n funnelProps?: Omit<ComponentProps<typeof RechartsFunnel>, 'data' | 'dataKey'>\n}\n\n/** The stages themselves. */\nfunction Funnel({\n variant = 'stepped',\n orientation = 'vertical',\n gap = 2,\n children,\n funnelProps,\n}: FunnelProps) {\n const { config, data, dataKey, nameKey } = useFunnelChart()\n const chartId = useId().replace(/:/g, '')\n\n // `ramp` walks the series ramp stage by stage; `stepped` holds slot one for\n // all of them and lets the shape do the talking.\n const painted = data.map((row, index) => {\n const name = String(row[nameKey])\n const slot = variant === 'ramp' ? `var(--series-${(index % 8) + 1})` : undefined\n return {\n ...row,\n fill: slot ?? (name in config ? `url(#${chartId}-stage-${cssName(name)})` : 'var(--series-1)'),\n }\n })\n\n return (\n <>\n <RechartsFunnel\n dataKey={dataKey}\n nameKey={nameKey}\n data={painted}\n isAnimationActive={false}\n // A surface-coloured stroke rather than a transparent gap: the stages\n // touch by construction, and a hairline of the page between them is\n // what makes two similar stages countable.\n stroke=\"var(--chart-surface)\"\n strokeWidth={gap}\n orientation={orientation}\n {...funnelProps}\n >\n {children}\n </RechartsFunnel>\n <defs>\n {Object.entries(config).map(([key, series]) => (\n <linearGradient\n key={key}\n id={`${chartId}-stage-${cssName(key)}`}\n x1=\"0\"\n y1=\"0\"\n x2=\"1\"\n y2=\"0\"\n >\n <ColorStops dataKey={cssName(key)} stops={colorStops(series)} />\n </linearGradient>\n ))}\n </defs>\n </>\n )\n}\n\nexport interface FunnelLabelProps {\n /** The field printed on each stage. Defaults to the stage's name. */\n dataKey?: string\n /** Where the label sits relative to its stage. */\n position?: ComponentProps<typeof LabelList>['position']\n labelListProps?: Omit<ComponentProps<typeof LabelList>, 'dataKey'>\n}\n\n/**\n * Names printed on the stages.\n *\n * Worth reaching for: a funnel's taper is a ratio the eye reads badly, and a\n * printed number is the relief for that.\n */\nfunction Label({ dataKey, position = 'right', labelListProps }: FunnelLabelProps) {\n const { nameKey } = useFunnelChart()\n\n return (\n <LabelList\n dataKey={dataKey ?? nameKey}\n position={position}\n stroke=\"none\"\n fontSize={12}\n className=\"fill-(--ink)\"\n {...labelListProps}\n />\n )\n}\n\n/** The hover panel. No heading: the stage's own name is the row label. */\nfunction Tooltip({ variant, roundness, defaultIndex }: ChartTooltipSlotProps) {\n const { nameKey } = useFunnelChart()\n\n return (\n <ChartTooltip\n defaultIndex={defaultIndex}\n cursor={false}\n content={\n <ChartTooltipContent nameKey={nameKey} hideLabel roundness={roundness} variant={variant} />\n }\n />\n )\n}\n\nFunnelChart.Funnel = Funnel\nFunnelChart.Label = Label\nFunnelChart.Tooltip = Tooltip\n\nexport default FunnelChart\n"],"mappings":"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","names":[]}
@@ -1,5 +1,6 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
+ import { ChartEmptyProps } from '../lib/empty.js';
3
4
 
4
5
  /**
5
6
  * How a cell's value is turned into ink.
@@ -14,9 +15,15 @@ import { ReactNode } from 'react';
14
15
  */
15
16
  type HeatmapScale = 'sequential' | 'diverging';
16
17
  interface HeatmapCell {
17
- /** The column this cell sits in. Must match one of `columns`. */
18
+ /**
19
+ * The column this cell sits in. Must match one of `columns`, exactly: the
20
+ * grid is drawn by looking each pair up, so a cell naming a column that is
21
+ * not there is a cell the grid has nowhere to put. It is not drawn, and it
22
+ * takes no part in the derived domain either — a misspelt header used to
23
+ * stretch the scale with a number nothing on screen was painted from.
24
+ */
18
25
  column: string;
19
- /** The row this cell sits in. Must match one of `rows`. */
26
+ /** The row this cell sits in. Must match one of `rows`, on the same terms. */
20
27
  row: string;
21
28
  /** The number. `null` is drawn as an explicit gap, not as a zero. */
22
29
  value: number | null;
@@ -47,6 +54,13 @@ interface HeatmapProps {
47
54
  /** Prints the number inside each cell. Only legible on a coarse grid. */
48
55
  showValues?: boolean;
49
56
  className?: string;
57
+ /**
58
+ * What the grid shows when it has no rows or no columns to draw.
59
+ *
60
+ * A heatmap with no headers renders as a caption over nothing, which reads
61
+ * as a grid that failed to load rather than as one with nothing in it.
62
+ */
63
+ empty?: ChartEmptyProps;
50
64
  }
51
65
  /**
52
66
  * A grid of values read by weight — a calendar of activity, a confusion matrix,
@@ -73,6 +87,6 @@ interface HeatmapProps {
73
87
  * cells={cells}
74
88
  * />
75
89
  */
76
- declare function Heatmap({ title, showTitle, description, columns, rows, cells, scale, domain, formatValue, showValues, className, }: HeatmapProps): react.JSX.Element;
90
+ declare function Heatmap({ title, showTitle, description, columns, rows, cells, scale, domain, formatValue, showValues, className, empty, }: HeatmapProps): react.JSX.Element;
77
91
 
78
92
  export { Heatmap, type HeatmapCell, type HeatmapProps, type HeatmapScale, Heatmap as default };
@@ -1,9 +1,17 @@
1
- import { jsx, jsxs } from "react/jsx-runtime";
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
2
  import { cn } from "../../lib/cn.js";
3
+ import { ChartEmpty } from "../lib/empty.js";
4
+ import { clampedFraction } from "../lib/scale.js";
3
5
  const LABELLED_CEILING = 0.35;
4
- function position(value, min, max) {
5
- if (max === min) return 0.5;
6
- return Math.min(1, Math.max(0, (value - min) / (max - min)));
6
+ function cellKey(row, column) {
7
+ return `${row}\0${column}`;
8
+ }
9
+ function placed(cells, rows, columns) {
10
+ const rowSet = new Set(rows);
11
+ const columnSet = new Set(columns);
12
+ return new Map(
13
+ cells.filter((cell) => rowSet.has(cell.row) && columnSet.has(cell.column)).map((cell) => [cellKey(cell.row, cell.column), cell.value])
14
+ );
7
15
  }
8
16
  function Heatmap({
9
17
  title,
@@ -16,17 +24,18 @@ function Heatmap({
16
24
  domain,
17
25
  formatValue = (value) => value.toLocaleString(),
18
26
  showValues = false,
19
- className
27
+ className,
28
+ empty
20
29
  }) {
21
- const values = cells.map((cell) => cell.value).filter((value) => value !== null);
30
+ const byKey = placed(cells, rows, columns);
31
+ const values = [...byKey.values()].filter((value) => value !== null);
22
32
  const [min, max] = domain ?? [Math.min(0, ...values), Math.max(0, ...values)];
23
- const byKey = new Map(cells.map((cell) => [`${cell.row}\0${cell.column}`, cell.value]));
24
33
  return /* @__PURE__ */ jsxs("figure", { className: cn("flex w-full flex-col", className), children: [
25
34
  /* @__PURE__ */ jsxs("figcaption", { className: cn(!showTitle && "sr-only", showTitle && "mb-3 flex flex-col gap-1"), children: [
26
35
  /* @__PURE__ */ jsx("span", { className: cn(showTitle && "font-heading text-[length:var(--fs-item)] text-(--ink)"), children: title }),
27
36
  description && /* @__PURE__ */ jsx("span", { className: cn(showTitle && "mono-meta text-(--ink-3-aa)"), children: description })
28
37
  ] }),
29
- /* @__PURE__ */ jsxs("table", { className: "w-full border-separate border-spacing-[2px] text-xs", children: [
38
+ rows.length === 0 || columns.length === 0 ? /* @__PURE__ */ jsx(ChartEmpty, { ...empty || {} }) : /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs("table", { className: "w-full border-separate border-spacing-[2px] text-xs", children: [
30
39
  /* @__PURE__ */ jsx("caption", { className: "sr-only", children: title }),
31
40
  /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
32
41
  /* @__PURE__ */ jsx("th", { scope: "col", className: "sr-only", children: "Row" }),
@@ -44,7 +53,7 @@ function Heatmap({
44
53
  columns.map((column) => /* @__PURE__ */ jsx(
45
54
  HeatmapTile,
46
55
  {
47
- value: byKey.get(`${row}\0${column}`) ?? null,
56
+ value: byKey.get(cellKey(row, column)) ?? null,
48
57
  min,
49
58
  max,
50
59
  scale,
@@ -55,7 +64,7 @@ function Heatmap({
55
64
  column
56
65
  ))
57
66
  ] }, row)) })
58
- ] })
67
+ ] }) })
59
68
  ] });
60
69
  }
61
70
  function HeatmapTile({
@@ -77,7 +86,7 @@ function HeatmapTile({
77
86
  ] }) })
78
87
  );
79
88
  }
80
- const t = position(value, min, max);
89
+ const t = clampedFraction(value, min, max);
81
90
  const ramp = scale === "diverging" ? Math.abs(t - 0.5) * 2 : t;
82
91
  const weight = showValue ? ramp * LABELLED_CEILING : ramp;
83
92
  return /* @__PURE__ */ jsxs(
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/charts/Heatmap/Heatmap.tsx"],"sourcesContent":["import type { ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\n\n/**\n * How a cell's value is turned into ink.\n *\n * `sequential` is one ramp light to dark, and is the right answer for a\n * magnitude that only goes up: counts, durations, volume. `diverging` puts the\n * page's own surface at the midpoint and darkens outward in both directions,\n * for a value with a meaningful zero — a change, a delta, a residual.\n *\n * There is no third option, and that is the point. A rainbow scale reads as\n * ordered to nobody, and is the single most common defect in a heatmap.\n */\nexport type HeatmapScale = 'sequential' | 'diverging'\n\nexport interface HeatmapCell {\n /** The column this cell sits in. Must match one of `columns`. */\n column: string\n /** The row this cell sits in. Must match one of `rows`. */\n row: string\n /** The number. `null` is drawn as an explicit gap, not as a zero. */\n value: number | null\n}\n\nexport interface HeatmapProps {\n /** What the grid shows. Required, and it names the table. */\n title: string\n /** Prints the title above the grid 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 /** Column headers, in the order they should appear. */\n columns: string[]\n /** Row headers, in the order they should appear. */\n rows: string[]\n cells: HeatmapCell[]\n scale?: HeatmapScale\n /**\n * The domain, as `[min, max]`. Derived from the data when omitted.\n *\n * Pin it whenever two grids are meant to be compared: two heatmaps on\n * independent domains look alike and mean different things, which is the one\n * failure a shared legend cannot fix.\n */\n domain?: [number, number]\n /** Formats a value for the cell's announcement and its printed label. */\n formatValue?: (value: number) => string\n /** Prints the number inside each cell. Only legible on a coarse grid. */\n showValues?: boolean\n className?: string\n}\n\n/**\n * How far up the ramp a cell may go.\n *\n * A printed number has to clear 4.5:1 against whatever it is printed on, and a\n * mid-grey clears it against neither ink nor paper — which is the reason a\n * heatmap with values on it usually ends up with unreadable cells somewhere in\n * the middle of its own scale. Capping the wash is the trade: the ORDER of the\n * cells survives untouched, the contrast range narrows, and the numbers — which\n * are the precise read anyway — become the thing carrying the detail.\n *\n * With no number printed the span is the full ramp, because the only text in\n * the cell is then visually hidden and has nothing to contrast against.\n */\nconst LABELLED_CEILING = 0.35\n\n/** Where a value sits in the domain, 0 to 1. */\nfunction position(value: number, min: number, max: number): number {\n if (max === min) return 0.5\n return Math.min(1, Math.max(0, (value - min) / (max - min)))\n}\n\n/**\n * A grid of values read by weight — a calendar of activity, a confusion matrix,\n * an hour-by-weekday load.\n *\n * The one form in the set that a monochrome system renders BETTER than a\n * chromatic one. A heatmap needs a scale whose order the eye can read without\n * consulting a legend, and lightness is the only channel that is unambiguously\n * ordered — which is why \"one hue, light to dark\" is the standing advice\n * everywhere else, and why the rainbow heatmap is a perennial mistake. Here\n * there is no hue left to get wrong.\n *\n * It is built as a real `<table>` rather than as an SVG, so the structure a\n * screen reader walks is the structure the eye reads: a header row, a header\n * column, and a cell that announces its own value. Nothing else in the package\n * can say that, and nothing else needs to — which is also why this one takes no\n * rendering engine at all.\n *\n * @example\n * <Heatmap\n * title=\"Commits by weekday and hour\"\n * columns={hours}\n * rows={weekdays}\n * cells={cells}\n * />\n */\nexport function Heatmap({\n title,\n showTitle = false,\n description,\n columns,\n rows,\n cells,\n scale = 'sequential',\n domain,\n formatValue = (value) => value.toLocaleString(),\n showValues = false,\n className,\n}: HeatmapProps) {\n const values = cells.map((cell) => cell.value).filter((value): value is number => value !== null)\n const [min, max] = domain ?? [Math.min(0, ...values), Math.max(0, ...values)]\n\n const byKey = new Map(cells.map((cell) => [`${cell.row}\u0000${cell.column}`, cell.value]))\n\n return (\n <figure className={cn('flex w-full flex-col', className)}>\n <figcaption className={cn(!showTitle && 'sr-only', showTitle && 'mb-3 flex flex-col gap-1')}>\n <span className={cn(showTitle && 'font-heading text-[length:var(--fs-item)] text-(--ink)')}>\n {title}\n </span>\n {description && (\n <span className={cn(showTitle && 'mono-meta text-(--ink-3-aa)')}>{description}</span>\n )}\n </figcaption>\n\n {/* `border-separate` with a spacing rather than a gap on a grid: the two\n pixels of page between cells are what make two neighbouring weights\n countable, and a table is the only layout that keeps a header tied to\n its own line. */}\n <table className=\"w-full border-separate border-spacing-[2px] text-xs\">\n <caption className=\"sr-only\">{title}</caption>\n <thead>\n <tr>\n <th scope=\"col\" className=\"sr-only\">\n Row\n </th>\n {columns.map((column) => (\n <th key={column} scope=\"col\" className=\"pb-1 mono-meta font-normal text-(--chart-axis)\">\n {column}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {rows.map((row) => (\n <tr key={row}>\n <th\n scope=\"row\"\n className=\"pe-2 text-end mono-meta font-normal whitespace-nowrap text-(--chart-axis)\"\n >\n {row}\n </th>\n {columns.map((column) => (\n <HeatmapTile\n key={column}\n value={byKey.get(`${row}\u0000${column}`) ?? null}\n min={min}\n max={max}\n scale={scale}\n label={`${row}, ${column}`}\n formatValue={formatValue}\n showValue={showValues}\n />\n ))}\n </tr>\n ))}\n </tbody>\n </table>\n </figure>\n )\n}\n\nfunction HeatmapTile({\n value,\n min,\n max,\n scale,\n label,\n formatValue,\n showValue,\n}: {\n value: number | null\n min: number\n max: number\n scale: HeatmapScale\n label: string\n formatValue: (value: number) => string\n showValue: boolean\n}) {\n if (value === null) {\n return (\n // A missing reading is not a zero, and drawing it as the palest cell\n // would say that it was. The hairline says \"no data\" instead.\n <td className=\"h-7 rounded-[2px] border border-dashed border-(--rule) bg-transparent\">\n <span className=\"sr-only\">{label}: no data</span>\n </td>\n )\n }\n\n const t = position(value, min, max)\n // Diverging reads DISTANCE from the midpoint, so both directions darken and\n // the middle of the scale is the page itself.\n const ramp = scale === 'diverging' ? Math.abs(t - 0.5) * 2 : t\n const weight = showValue ? ramp * LABELLED_CEILING : ramp\n\n return (\n <td\n className=\"h-7 rounded-[2px] text-center align-middle tabular-nums\"\n style={{\n // `color-mix` rather than eight discrete steps: a heatmap's whole job is\n // a continuous read, and banding it into a ramp invents boundaries the\n // data does not have.\n backgroundColor: `color-mix(in srgb, var(--series-1) ${(weight * 100).toFixed(1)}%, var(--chart-surface))`,\n // One text colour at every weight, which is only safe because the ramp\n // above is capped when a number is printed on it.\n color: 'var(--ink)',\n }}\n >\n {/* Revealed under forced colours, where the cell's own background is\n remapped away and the wash that WAS the encoding no longer exists.\n The grid degrades to the table it always was underneath, rather than\n to a page of identical empty boxes. */}\n <span className={cn(!showValue && 'sr-only forced-colors:not-sr-only')}>\n {formatValue(value)}\n </span>\n {!showValue && <span className=\"sr-only\">{label}</span>}\n </td>\n )\n}\n\nexport default Heatmap\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/charts/Heatmap/Heatmap.tsx"],"sourcesContent":["import type { ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\nimport { ChartEmpty, type ChartEmptyProps } from '../lib/empty'\nimport { clampedFraction } from '../lib/scale'\n\n/**\n * How a cell's value is turned into ink.\n *\n * `sequential` is one ramp light to dark, and is the right answer for a\n * magnitude that only goes up: counts, durations, volume. `diverging` puts the\n * page's own surface at the midpoint and darkens outward in both directions,\n * for a value with a meaningful zero — a change, a delta, a residual.\n *\n * There is no third option, and that is the point. A rainbow scale reads as\n * ordered to nobody, and is the single most common defect in a heatmap.\n */\nexport type HeatmapScale = 'sequential' | 'diverging'\n\nexport interface HeatmapCell {\n /**\n * The column this cell sits in. Must match one of `columns`, exactly: the\n * grid is drawn by looking each pair up, so a cell naming a column that is\n * not there is a cell the grid has nowhere to put. It is not drawn, and it\n * takes no part in the derived domain either — a misspelt header used to\n * stretch the scale with a number nothing on screen was painted from.\n */\n column: string\n /** The row this cell sits in. Must match one of `rows`, on the same terms. */\n row: string\n /** The number. `null` is drawn as an explicit gap, not as a zero. */\n value: number | null\n}\n\nexport interface HeatmapProps {\n /** What the grid shows. Required, and it names the table. */\n title: string\n /** Prints the title above the grid 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 /** Column headers, in the order they should appear. */\n columns: string[]\n /** Row headers, in the order they should appear. */\n rows: string[]\n cells: HeatmapCell[]\n scale?: HeatmapScale\n /**\n * The domain, as `[min, max]`. Derived from the data when omitted.\n *\n * Pin it whenever two grids are meant to be compared: two heatmaps on\n * independent domains look alike and mean different things, which is the one\n * failure a shared legend cannot fix.\n */\n domain?: [number, number]\n /** Formats a value for the cell's announcement and its printed label. */\n formatValue?: (value: number) => string\n /** Prints the number inside each cell. Only legible on a coarse grid. */\n showValues?: boolean\n className?: string\n /**\n * What the grid shows when it has no rows or no columns to draw.\n *\n * A heatmap with no headers renders as a caption over nothing, which reads\n * as a grid that failed to load rather than as one with nothing in it.\n */\n empty?: ChartEmptyProps\n}\n\n/**\n * How far up the ramp a cell may go.\n *\n * A printed number has to clear 4.5:1 against whatever it is printed on, and a\n * mid-grey clears it against neither ink nor paper — which is the reason a\n * heatmap with values on it usually ends up with unreadable cells somewhere in\n * the middle of its own scale. Capping the wash is the trade: the ORDER of the\n * cells survives untouched, the contrast range narrows, and the numbers — which\n * are the precise read anyway — become the thing carrying the detail.\n *\n * With no number printed the span is the full ramp, because the only text in\n * the cell is then visually hidden and has nothing to contrast against.\n */\nconst LABELLED_CEILING = 0.35\n\n/**\n * The key a row and a column are looked up by.\n *\n * Joined on a character no header can contain, so `\"a b\" × \"c\"` and\n * `\"a\" × \"b c\"` stay two different cells. This used to be a literal NUL byte in\n * the source — the same key, written invisibly, in a file every text tool then\n * treated as binary.\n */\nfunction cellKey(row: string, column: string): string {\n return `${row}\u0000${column}`\n}\n\n/** The cells the grid can actually place, keyed by the pair that places them. */\nfunction placed(\n cells: HeatmapCell[],\n rows: string[],\n columns: string[],\n): Map<string, number | null> {\n const rowSet = new Set(rows)\n const columnSet = new Set(columns)\n return new Map(\n cells\n .filter((cell) => rowSet.has(cell.row) && columnSet.has(cell.column))\n .map((cell) => [cellKey(cell.row, cell.column), cell.value]),\n )\n}\n\n/**\n * A grid of values read by weight — a calendar of activity, a confusion matrix,\n * an hour-by-weekday load.\n *\n * The one form in the set that a monochrome system renders BETTER than a\n * chromatic one. A heatmap needs a scale whose order the eye can read without\n * consulting a legend, and lightness is the only channel that is unambiguously\n * ordered — which is why \"one hue, light to dark\" is the standing advice\n * everywhere else, and why the rainbow heatmap is a perennial mistake. Here\n * there is no hue left to get wrong.\n *\n * It is built as a real `<table>` rather than as an SVG, so the structure a\n * screen reader walks is the structure the eye reads: a header row, a header\n * column, and a cell that announces its own value. Nothing else in the package\n * can say that, and nothing else needs to — which is also why this one takes no\n * rendering engine at all.\n *\n * @example\n * <Heatmap\n * title=\"Commits by weekday and hour\"\n * columns={hours}\n * rows={weekdays}\n * cells={cells}\n * />\n */\nexport function Heatmap({\n title,\n showTitle = false,\n description,\n columns,\n rows,\n cells,\n scale = 'sequential',\n domain,\n formatValue = (value) => value.toLocaleString(),\n showValues = false,\n className,\n empty,\n}: HeatmapProps) {\n // Keyed first, so the domain comes from the cells the grid draws. A cell\n // whose row or column is not in the headers is never looked up, and it used\n // to stretch the scale anyway — one misspelt header pushing every drawn cell\n // into the first fraction of the ramp, with nothing on screen to explain it.\n const byKey = placed(cells, rows, columns)\n const values = [...byKey.values()].filter((value): value is number => value !== null)\n const [min, max] = domain ?? [Math.min(0, ...values), Math.max(0, ...values)]\n\n return (\n <figure className={cn('flex w-full flex-col', className)}>\n <figcaption className={cn(!showTitle && 'sr-only', showTitle && 'mb-3 flex flex-col gap-1')}>\n <span className={cn(showTitle && 'font-heading text-[length:var(--fs-item)] text-(--ink)')}>\n {title}\n </span>\n {description && (\n <span className={cn(showTitle && 'mono-meta text-(--ink-3-aa)')}>{description}</span>\n )}\n </figcaption>\n\n {rows.length === 0 || columns.length === 0 ? (\n <ChartEmpty {...(empty || {})} />\n ) : (\n <>\n {/* `border-separate` with a spacing rather than a gap on a grid: the two\n pixels of page between cells are what make two neighbouring weights\n countable, and a table is the only layout that keeps a header tied to\n its own line. */}\n <table className=\"w-full border-separate border-spacing-[2px] text-xs\">\n <caption className=\"sr-only\">{title}</caption>\n <thead>\n <tr>\n <th scope=\"col\" className=\"sr-only\">\n Row\n </th>\n {columns.map((column) => (\n <th key={column} scope=\"col\" className=\"pb-1 mono-meta font-normal text-(--chart-axis)\">\n {column}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {rows.map((row) => (\n <tr key={row}>\n <th\n scope=\"row\"\n className=\"pe-2 text-end mono-meta font-normal whitespace-nowrap text-(--chart-axis)\"\n >\n {row}\n </th>\n {columns.map((column) => (\n <HeatmapTile\n key={column}\n value={byKey.get(cellKey(row, column)) ?? null}\n min={min}\n max={max}\n scale={scale}\n label={`${row}, ${column}`}\n formatValue={formatValue}\n showValue={showValues}\n />\n ))}\n </tr>\n ))}\n </tbody>\n </table>\n </>\n )}\n </figure>\n )\n}\n\nfunction HeatmapTile({\n value,\n min,\n max,\n scale,\n label,\n formatValue,\n showValue,\n}: {\n value: number | null\n min: number\n max: number\n scale: HeatmapScale\n label: string\n formatValue: (value: number) => string\n showValue: boolean\n}) {\n if (value === null) {\n return (\n // A missing reading is not a zero, and drawing it as the palest cell\n // would say that it was. The hairline says \"no data\" instead.\n <td className=\"h-7 rounded-[2px] border border-dashed border-(--rule) bg-transparent\">\n <span className=\"sr-only\">{label}: no data</span>\n </td>\n )\n }\n\n const t = clampedFraction(value, min, max)\n // Diverging reads DISTANCE from the midpoint, so both directions darken and\n // the middle of the scale is the page itself.\n const ramp = scale === 'diverging' ? Math.abs(t - 0.5) * 2 : t\n const weight = showValue ? ramp * LABELLED_CEILING : ramp\n\n return (\n <td\n className=\"h-7 rounded-[2px] text-center align-middle tabular-nums\"\n style={{\n // `color-mix` rather than eight discrete steps: a heatmap's whole job is\n // a continuous read, and banding it into a ramp invents boundaries the\n // data does not have.\n backgroundColor: `color-mix(in srgb, var(--series-1) ${(weight * 100).toFixed(1)}%, var(--chart-surface))`,\n // One text colour at every weight, which is only safe because the ramp\n // above is capped when a number is printed on it.\n color: 'var(--ink)',\n }}\n >\n {/* Revealed under forced colours, where the cell's own background is\n remapped away and the wash that WAS the encoding no longer exists.\n The grid degrades to the table it always was underneath, rather than\n to a page of identical empty boxes. */}\n <span className={cn(!showValue && 'sr-only forced-colors:not-sr-only')}>\n {formatValue(value)}\n </span>\n {!showValue && <span className=\"sr-only\">{label}</span>}\n </td>\n )\n}\n\nexport default Heatmap\n"],"mappings":"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","names":[]}
@@ -31,6 +31,23 @@ interface HistogramBin {
31
31
  /** How many observations fell in it. */
32
32
  count: number;
33
33
  }
34
+ /**
35
+ * Observations the bins do not reach, and the edge each fell outside.
36
+ *
37
+ * Only ever non-zero when the call site gave explicit `bins` edges: a derived
38
+ * width spans the data by construction. When it IS non-zero it is the reading
39
+ * that matters most — a tail cut off by a hand-picked range — and dropping it
40
+ * silently hid it from the picture, from the table and from the tooltip's
41
+ * share-of-total all at once.
42
+ */
43
+ interface HistogramOutside {
44
+ /** How many observations fell below the first edge. */
45
+ below: number;
46
+ /** How many fell above the last. */
47
+ above: number;
48
+ /** The first edge, and the last. */
49
+ edges: [number, number];
50
+ }
34
51
  interface HistogramProps {
35
52
  /**
36
53
  * The single series — its label and its paint. Only the FIRST entry is read;
@@ -56,6 +73,12 @@ interface HistogramProps {
56
73
  * How to cut `values` up: a number of equal-width bins, or the explicit
57
74
  * edges. Defaults to Freedman–Diaconis (`2 × IQR × n^(-1/3)`), falling back
58
75
  * to Sturges when the interquartile range is zero.
76
+ *
77
+ * Explicit edges are a RANGE as well as a set of buckets: an observation
78
+ * outside the first and last edge has no bucket to fall in. It is counted
79
+ * anyway — into the tooltip's share-of-total, and into a "Below" or "Above"
80
+ * row in the table — so a tail your edges cut off is still somewhere a reader
81
+ * can find it.
59
82
  */
60
83
  bins?: number | number[];
61
84
  /**
@@ -162,4 +185,4 @@ interface HistogramBarsProps {
162
185
  barProps?: ComponentProps<typeof Bar>;
163
186
  }
164
187
 
165
- export { Histogram, type HistogramBarsProps, type HistogramBin, type HistogramMode, type HistogramProps, Histogram as default };
188
+ export { Histogram, type HistogramBarsProps, type HistogramBin, type HistogramMode, type HistogramOutside, type HistogramProps, Histogram as default };