@seekalfred/primitives 0.6.0 → 0.7.1

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 (322) hide show
  1. package/README.md +19 -11
  2. package/dist/activity-heatmap/activity-heatmap.d.ts +92 -0
  3. package/dist/activity-heatmap/activity-heatmap.js +699 -0
  4. package/dist/activity-heatmap/activity-heatmap.js.map +1 -0
  5. package/dist/activity-heatmap/index.d.ts +2 -0
  6. package/dist/activity-heatmap/index.js +2 -0
  7. package/dist/alert/alert.d.ts +5 -1
  8. package/dist/alert/alert.js +68 -7
  9. package/dist/alert/alert.js.map +1 -1
  10. package/dist/alert-dialog/alert-dialog.d.ts +45 -18
  11. package/dist/alert-dialog/alert-dialog.js +128 -9
  12. package/dist/alert-dialog/alert-dialog.js.map +1 -1
  13. package/dist/alert-dialog/index.d.ts +2 -2
  14. package/dist/alert-dialog/index.js +2 -2
  15. package/dist/animated-counter/animated-counter.d.ts +38 -0
  16. package/dist/animated-counter/animated-counter.js +230 -0
  17. package/dist/animated-counter/animated-counter.js.map +1 -0
  18. package/dist/animated-counter/index.d.ts +2 -0
  19. package/dist/animated-counter/index.js +2 -0
  20. package/dist/announcement-bar/announcement-bar.d.ts +67 -0
  21. package/dist/announcement-bar/announcement-bar.js +364 -0
  22. package/dist/announcement-bar/announcement-bar.js.map +1 -0
  23. package/dist/announcement-bar/index.d.ts +2 -0
  24. package/dist/announcement-bar/index.js +2 -0
  25. package/dist/badge/badge.d.ts +1 -1
  26. package/dist/bar-chart/bar-chart.d.ts +51 -0
  27. package/dist/bar-chart/bar-chart.js +565 -0
  28. package/dist/bar-chart/bar-chart.js.map +1 -0
  29. package/dist/bar-chart/index.d.ts +2 -0
  30. package/dist/bar-chart/index.js +2 -0
  31. package/dist/breadcrumb/breadcrumb.d.ts +2 -2
  32. package/dist/breadcrumb/breadcrumb.js +12 -3
  33. package/dist/breadcrumb/breadcrumb.js.map +1 -1
  34. package/dist/brush-chart/brush-chart.d.ts +64 -0
  35. package/dist/brush-chart/brush-chart.js +1056 -0
  36. package/dist/brush-chart/brush-chart.js.map +1 -0
  37. package/dist/brush-chart/index.d.ts +2 -0
  38. package/dist/brush-chart/index.js +2 -0
  39. package/dist/button/button.d.ts +2 -2
  40. package/dist/button/button.js +31 -21
  41. package/dist/button/button.js.map +1 -1
  42. package/dist/button-group/button-group.js +3 -4
  43. package/dist/button-group/button-group.js.map +1 -1
  44. package/dist/card/card.js +1 -1
  45. package/dist/card/card.js.map +1 -1
  46. package/dist/card-stack/card-stack.d.ts +54 -0
  47. package/dist/card-stack/card-stack.js +492 -0
  48. package/dist/card-stack/card-stack.js.map +1 -0
  49. package/dist/card-stack/index.d.ts +2 -0
  50. package/dist/card-stack/index.js +2 -0
  51. package/dist/carousel/carousel.d.ts +56 -0
  52. package/dist/carousel/carousel.js +389 -0
  53. package/dist/carousel/carousel.js.map +1 -0
  54. package/dist/carousel/index.d.ts +2 -0
  55. package/dist/carousel/index.js +2 -0
  56. package/dist/checkbox/checkbox.js +4 -4
  57. package/dist/checkbox/checkbox.js.map +1 -1
  58. package/dist/chip-group/chip-group.d.ts +37 -0
  59. package/dist/chip-group/chip-group.js +148 -0
  60. package/dist/chip-group/chip-group.js.map +1 -0
  61. package/dist/chip-group/index.d.ts +2 -0
  62. package/dist/chip-group/index.js +2 -0
  63. package/dist/countdown/countdown.d.ts +45 -0
  64. package/dist/countdown/countdown.js +298 -0
  65. package/dist/countdown/countdown.js.map +1 -0
  66. package/dist/countdown/index.d.ts +2 -0
  67. package/dist/countdown/index.js +2 -0
  68. package/dist/date-picker/picker-parts.js +1 -1
  69. package/dist/date-picker/picker-parts.js.map +1 -1
  70. package/dist/dialog/dialog.js +1 -1
  71. package/dist/dialog/dialog.js.map +1 -1
  72. package/dist/donut-chart/donut-chart.d.ts +64 -0
  73. package/dist/donut-chart/donut-chart.js +576 -0
  74. package/dist/donut-chart/donut-chart.js.map +1 -0
  75. package/dist/donut-chart/index.d.ts +2 -0
  76. package/dist/donut-chart/index.js +2 -0
  77. package/dist/empty-state/empty-state.d.ts +21 -10
  78. package/dist/empty-state/empty-state.js +32 -26
  79. package/dist/empty-state/empty-state.js.map +1 -1
  80. package/dist/empty-state/index.d.ts +2 -2
  81. package/dist/empty-state/index.js +2 -2
  82. package/dist/file-drop-zone/file-drop-zone.js +1 -1
  83. package/dist/file-drop-zone/file-drop-zone.js.map +1 -1
  84. package/dist/filter-bar/filter-bar.d.ts +47 -26
  85. package/dist/filter-bar/filter-bar.js +272 -156
  86. package/dist/filter-bar/filter-bar.js.map +1 -1
  87. package/dist/gauge/gauge.d.ts +45 -0
  88. package/dist/gauge/gauge.js +219 -0
  89. package/dist/gauge/gauge.js.map +1 -0
  90. package/dist/gauge/index.d.ts +2 -0
  91. package/dist/gauge/index.js +2 -0
  92. package/dist/in-view-title/in-view-title.d.ts +33 -0
  93. package/dist/in-view-title/in-view-title.js +119 -0
  94. package/dist/in-view-title/in-view-title.js.map +1 -0
  95. package/dist/in-view-title/index.d.ts +2 -0
  96. package/dist/in-view-title/index.js +2 -0
  97. package/dist/index.d.ts +49 -6
  98. package/dist/index.js +48 -8
  99. package/dist/lib/cn.js +11 -1
  100. package/dist/lib/cn.js.map +1 -1
  101. package/dist/lib/field-message.js +28 -10
  102. package/dist/lib/field-message.js.map +1 -1
  103. package/dist/lib/field-surface.js +9 -8
  104. package/dist/lib/field-surface.js.map +1 -1
  105. package/dist/lib/flag-emoji.js +50 -0
  106. package/dist/lib/flag-emoji.js.map +1 -0
  107. package/dist/lib/flip.js +11 -3
  108. package/dist/lib/flip.js.map +1 -1
  109. package/dist/lib/menu-highlight.js +64 -0
  110. package/dist/lib/menu-highlight.js.map +1 -0
  111. package/dist/lib/motion.js +9 -2
  112. package/dist/lib/motion.js.map +1 -1
  113. package/dist/lib/rolling-value.js +1 -1
  114. package/dist/lib/rolling-value.js.map +1 -1
  115. package/dist/lib/spring-tween.js +500 -0
  116. package/dist/lib/spring-tween.js.map +1 -0
  117. package/dist/lib/swap-text.js +3 -1
  118. package/dist/lib/swap-text.js.map +1 -1
  119. package/dist/line-chart/index.d.ts +2 -0
  120. package/dist/line-chart/index.js +2 -0
  121. package/dist/line-chart/line-chart.d.ts +76 -0
  122. package/dist/line-chart/line-chart.js +611 -0
  123. package/dist/line-chart/line-chart.js.map +1 -0
  124. package/dist/mention-input/index.d.ts +2 -0
  125. package/dist/mention-input/index.js +2 -0
  126. package/dist/mention-input/mention-input.d.ts +98 -0
  127. package/dist/mention-input/mention-input.js +576 -0
  128. package/dist/mention-input/mention-input.js.map +1 -0
  129. package/dist/menu/menu.d.ts +2 -0
  130. package/dist/menu/menu.js +23 -12
  131. package/dist/menu/menu.js.map +1 -1
  132. package/dist/money-input/index.d.ts +2 -0
  133. package/dist/money-input/index.js +2 -0
  134. package/dist/money-input/money-input.d.ts +65 -0
  135. package/dist/money-input/money-input.js +607 -0
  136. package/dist/money-input/money-input.js.map +1 -0
  137. package/dist/morph-select/index.d.ts +2 -0
  138. package/dist/morph-select/index.js +2 -0
  139. package/dist/morph-select/morph-select.d.ts +71 -0
  140. package/dist/morph-select/morph-select.js +693 -0
  141. package/dist/morph-select/morph-select.js.map +1 -0
  142. package/dist/multi-select/index.d.ts +2 -0
  143. package/dist/multi-select/index.js +2 -0
  144. package/dist/multi-select/multi-select.d.ts +36 -0
  145. package/dist/multi-select/multi-select.js +152 -0
  146. package/dist/multi-select/multi-select.js.map +1 -0
  147. package/dist/number-field/index.d.ts +2 -0
  148. package/dist/number-field/index.js +2 -0
  149. package/dist/number-field/number-field.d.ts +42 -0
  150. package/dist/number-field/number-field.js +199 -0
  151. package/dist/number-field/number-field.js.map +1 -0
  152. package/dist/onboarding-checklist/index.d.ts +2 -0
  153. package/dist/onboarding-checklist/index.js +2 -0
  154. package/dist/onboarding-checklist/onboarding-checklist.d.ts +55 -0
  155. package/dist/onboarding-checklist/onboarding-checklist.js +577 -0
  156. package/dist/onboarding-checklist/onboarding-checklist.js.map +1 -0
  157. package/dist/password-field/password-field.js +2 -2
  158. package/dist/password-field/password-field.js.map +1 -1
  159. package/dist/phone-input/index.d.ts +3 -0
  160. package/dist/phone-input/index.js +3 -0
  161. package/dist/phone-input/phone-countries.d.ts +29 -0
  162. package/dist/phone-input/phone-countries.js +170 -0
  163. package/dist/phone-input/phone-countries.js.map +1 -0
  164. package/dist/phone-input/phone-input.d.ts +66 -0
  165. package/dist/phone-input/phone-input.js +440 -0
  166. package/dist/phone-input/phone-input.js.map +1 -0
  167. package/dist/popover/popover.d.ts +12 -0
  168. package/dist/popover/popover.js +31 -7
  169. package/dist/popover/popover.js.map +1 -1
  170. package/dist/progress/progress.js +1 -1
  171. package/dist/radio-group/radio-group.js +1 -1
  172. package/dist/radio-group/radio-group.js.map +1 -1
  173. package/dist/reactions/index.d.ts +2 -0
  174. package/dist/reactions/index.js +2 -0
  175. package/dist/reactions/reactions.d.ts +50 -0
  176. package/dist/reactions/reactions.js +315 -0
  177. package/dist/reactions/reactions.js.map +1 -0
  178. package/dist/rich-text-editor/index.d.ts +3 -0
  179. package/dist/rich-text-editor/index.js +2 -0
  180. package/dist/rich-text-editor/rich-text-editor.d.ts +70 -0
  181. package/dist/rich-text-editor/rich-text-editor.js +909 -0
  182. package/dist/rich-text-editor/rich-text-editor.js.map +1 -0
  183. package/dist/rich-text-editor/rich-text-engine.d.ts +16 -0
  184. package/dist/rich-text-editor/rich-text-engine.js +366 -0
  185. package/dist/rich-text-editor/rich-text-engine.js.map +1 -0
  186. package/dist/rich-text-editor/rich-text-html.js +174 -0
  187. package/dist/rich-text-editor/rich-text-html.js.map +1 -0
  188. package/dist/rich-text-editor/rich-text-icons.js +43 -0
  189. package/dist/rich-text-editor/rich-text-icons.js.map +1 -0
  190. package/dist/ridgeline/index.d.ts +2 -0
  191. package/dist/ridgeline/index.js +2 -0
  192. package/dist/ridgeline/ridgeline.d.ts +55 -0
  193. package/dist/ridgeline/ridgeline.js +701 -0
  194. package/dist/ridgeline/ridgeline.js.map +1 -0
  195. package/dist/scroll-area/index.d.ts +2 -2
  196. package/dist/scroll-area/index.js +2 -2
  197. package/dist/scroll-area/scroll-area.d.ts +68 -31
  198. package/dist/scroll-area/scroll-area.js +208 -59
  199. package/dist/scroll-area/scroll-area.js.map +1 -1
  200. package/dist/scrub-input/evaluate.d.ts +14 -0
  201. package/dist/scrub-input/evaluate.js +72 -0
  202. package/dist/scrub-input/evaluate.js.map +1 -0
  203. package/dist/scrub-input/index.d.ts +3 -0
  204. package/dist/scrub-input/index.js +3 -0
  205. package/dist/scrub-input/scrub-input.d.ts +74 -0
  206. package/dist/scrub-input/scrub-input.js +486 -0
  207. package/dist/scrub-input/scrub-input.js.map +1 -0
  208. package/dist/search-field/search-field.d.ts +2 -1
  209. package/dist/search-field/search-field.js +4 -4
  210. package/dist/search-field/search-field.js.map +1 -1
  211. package/dist/select/select.d.ts +3 -1
  212. package/dist/select/select.js +20 -8
  213. package/dist/select/select.js.map +1 -1
  214. package/dist/separator/separator.d.ts +1 -1
  215. package/dist/sheet/sheet.js +2 -1
  216. package/dist/sheet/sheet.js.map +1 -1
  217. package/dist/shortcut-recorder/index.d.ts +3 -0
  218. package/dist/shortcut-recorder/index.js +3 -0
  219. package/dist/shortcut-recorder/shortcut-model.d.ts +27 -0
  220. package/dist/shortcut-recorder/shortcut-model.js +206 -0
  221. package/dist/shortcut-recorder/shortcut-model.js.map +1 -0
  222. package/dist/shortcut-recorder/shortcut-recorder.d.ts +93 -0
  223. package/dist/shortcut-recorder/shortcut-recorder.js +489 -0
  224. package/dist/shortcut-recorder/shortcut-recorder.js.map +1 -0
  225. package/dist/slider/index.d.ts +2 -2
  226. package/dist/slider/slider.d.ts +46 -14
  227. package/dist/slider/slider.js +151 -67
  228. package/dist/slider/slider.js.map +1 -1
  229. package/dist/slope-chart/index.d.ts +2 -0
  230. package/dist/slope-chart/index.js +2 -0
  231. package/dist/slope-chart/slope-chart.d.ts +54 -0
  232. package/dist/slope-chart/slope-chart.js +568 -0
  233. package/dist/slope-chart/slope-chart.js.map +1 -0
  234. package/dist/sparkline/index.d.ts +2 -0
  235. package/dist/sparkline/index.js +2 -0
  236. package/dist/sparkline/sparkline.d.ts +46 -0
  237. package/dist/sparkline/sparkline.js +390 -0
  238. package/dist/sparkline/sparkline.js.map +1 -0
  239. package/dist/split-bar/index.d.ts +2 -0
  240. package/dist/split-bar/index.js +2 -0
  241. package/dist/split-bar/split-bar.d.ts +62 -0
  242. package/dist/split-bar/split-bar.js +513 -0
  243. package/dist/split-bar/split-bar.js.map +1 -0
  244. package/dist/split-button/split-button.d.ts +1 -1
  245. package/dist/split-button/split-button.js +12 -11
  246. package/dist/split-button/split-button.js.map +1 -1
  247. package/dist/stat-card/index.d.ts +2 -2
  248. package/dist/stat-card/index.js +2 -2
  249. package/dist/stat-card/stat-card.d.ts +24 -4
  250. package/dist/stat-card/stat-card.js +114 -17
  251. package/dist/stat-card/stat-card.js.map +1 -1
  252. package/dist/stepper/index.d.ts +2 -0
  253. package/dist/stepper/index.js +2 -0
  254. package/dist/stepper/stepper.d.ts +51 -0
  255. package/dist/stepper/stepper.js +215 -0
  256. package/dist/stepper/stepper.js.map +1 -0
  257. package/dist/streamgraph/index.d.ts +2 -0
  258. package/dist/streamgraph/index.js +2 -0
  259. package/dist/streamgraph/streamgraph.d.ts +70 -0
  260. package/dist/streamgraph/streamgraph.js +613 -0
  261. package/dist/streamgraph/streamgraph.js.map +1 -0
  262. package/dist/styles.css +1 -1
  263. package/dist/swipe-actions/index.d.ts +2 -0
  264. package/dist/swipe-actions/index.js +2 -0
  265. package/dist/swipe-actions/swipe-actions.d.ts +67 -0
  266. package/dist/swipe-actions/swipe-actions.js +552 -0
  267. package/dist/swipe-actions/swipe-actions.js.map +1 -0
  268. package/dist/switch/switch.js +2 -2
  269. package/dist/switch/switch.js.map +1 -1
  270. package/dist/table/index.d.ts +2 -2
  271. package/dist/table/index.js +2 -2
  272. package/dist/table/table.d.ts +56 -4
  273. package/dist/table/table.js +340 -165
  274. package/dist/table/table.js.map +1 -1
  275. package/dist/tabs/tabs.js +2 -2
  276. package/dist/tabs/tabs.js.map +1 -1
  277. package/dist/tag-input/index.d.ts +2 -0
  278. package/dist/tag-input/index.js +2 -0
  279. package/dist/tag-input/tag-input.d.ts +40 -0
  280. package/dist/tag-input/tag-input.js +269 -0
  281. package/dist/tag-input/tag-input.js.map +1 -0
  282. package/dist/text-morph/index.d.ts +2 -0
  283. package/dist/text-morph/index.js +2 -0
  284. package/dist/text-morph/text-morph.d.ts +23 -0
  285. package/dist/text-morph/text-morph.js +21 -0
  286. package/dist/text-morph/text-morph.js.map +1 -0
  287. package/dist/text-reveal/index.d.ts +2 -0
  288. package/dist/text-reveal/index.js +2 -0
  289. package/dist/text-reveal/text-reveal.d.ts +27 -0
  290. package/dist/text-reveal/text-reveal.js +51 -0
  291. package/dist/text-reveal/text-reveal.js.map +1 -0
  292. package/dist/text-shimmer/index.d.ts +2 -0
  293. package/dist/text-shimmer/index.js +2 -0
  294. package/dist/text-shimmer/text-shimmer.d.ts +29 -0
  295. package/dist/text-shimmer/text-shimmer.js +29 -0
  296. package/dist/text-shimmer/text-shimmer.js.map +1 -0
  297. package/dist/time-picker/time-picker.js +2 -2
  298. package/dist/time-picker/time-picker.js.map +1 -1
  299. package/dist/toast/index.d.ts +2 -2
  300. package/dist/toast/index.js +2 -2
  301. package/dist/toast/toast.d.ts +6 -0
  302. package/dist/toast/toast.js +82 -40
  303. package/dist/toast/toast.js.map +1 -1
  304. package/dist/tooltip/tooltip.d.ts +9 -2
  305. package/dist/tooltip/tooltip.js +7 -7
  306. package/dist/tooltip/tooltip.js.map +1 -1
  307. package/dist/treemap/index.d.ts +2 -0
  308. package/dist/treemap/index.js +2 -0
  309. package/dist/treemap/treemap.d.ts +53 -0
  310. package/dist/treemap/treemap.js +699 -0
  311. package/dist/treemap/treemap.js.map +1 -0
  312. package/dist/usage-meter/index.d.ts +2 -0
  313. package/dist/usage-meter/index.js +2 -0
  314. package/dist/usage-meter/usage-meter.d.ts +50 -0
  315. package/dist/usage-meter/usage-meter.js +440 -0
  316. package/dist/usage-meter/usage-meter.js.map +1 -0
  317. package/dist/waffle-chart/index.d.ts +2 -0
  318. package/dist/waffle-chart/index.js +2 -0
  319. package/dist/waffle-chart/waffle-chart.d.ts +57 -0
  320. package/dist/waffle-chart/waffle-chart.js +426 -0
  321. package/dist/waffle-chart/waffle-chart.js.map +1 -0
  322. package/package.json +165 -3
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usage-meter.js","names":[],"sources":["../../src/usage-meter/usage-meter.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n forwardRef,\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n type ComponentPropsWithoutRef,\n type KeyboardEvent,\n type PointerEvent,\n} from \"react\";\n\nimport { Badge } from \"../badge/badge.js\";\nimport { cn } from \"../lib/cn.js\";\nimport { useCountTo } from \"../lib/count-to.js\";\nimport { mergeRefs } from \"../lib/merge-refs.js\";\nimport { prefersReducedMotion, useIsomorphicLayoutEffect } from \"../lib/motion.js\";\nimport { FILL, live, retarget, SMOOTH, type Live } from \"../lib/spring-tween.js\";\nimport { useSvgId } from \"../lib/svg-id.js\";\nimport { SwapText } from \"../lib/swap-text.js\";\n\n/**\n * UsageMeter: a fixed allowance, such as storage or seats, when people need to\n * see what takes the space and how close they are to the limit.\n *\n * The segments grow into place from the left once the meter is seen, each a\n * beat after the one before. After that every change re-flows them: a segment\n * that grows pushes the ones after it along in the same frame. The bar spans\n * the limit, or everything used once that is more; past the limit the overflow\n * keeps its colours but is struck through with a hatch, and a marker shows\n * where the limit was, so \"over\" never rests on colour alone. The headline and\n * every legend value count to their new number, and the status mark swaps its\n * words and icon while its width follows.\n *\n * Hover previews a category, focus previews it too, and a press pins it until\n * pressed again or Escape. The legend is the keyboard path: arrow keys move\n * between categories. The bar names every amount for assistive technology,\n * and a change of status (almost full, over, back under) is announced.\n *\n * Plain DOM and CSS; one small spring tween writes the segments straight to\n * their elements.\n */\nexport interface UsageMeterSegment {\n /** Stable identity. Keep it the same while the value changes, so the segment re-flows instead of being replaced. */\n id: string;\n label: string;\n value: number;\n}\n\nexport interface UsageMeterProps extends Omit<ComponentPropsWithoutRef<\"div\">, \"children\"> {\n /** What is measured, such as \"Workspace storage\". */\n label: string;\n /** Used amounts in display order. Up to four read clearly. */\n segments: UsageMeterSegment[];\n /** The plan limit, in the same unit as the segments. */\n limit: number;\n /** Unit after each number, such as \"GB\". */\n unit?: string;\n /** Digits after the decimal point. @default 1 */\n decimals?: number;\n /** Name of the unused share. @default \"Free\" */\n freeLabel?: string;\n /** Name of the share past the limit. @default \"Over limit\" */\n overLabel?: string;\n /** Share of the limit at which the meter warns that it is almost full. @default 0.9 */\n warnAt?: number;\n}\n\ntype Status = \"ok\" | \"near\" | \"over\";\n\n/** A segment stops two pixels short of the next, so neighbours read apart without an outline. */\nconst GAP = 2;\n/** Stagger of each segment's first growth. */\nconst STAGGER_MS = 70;\nconst formats = new Map<number, Intl.NumberFormat>();\nfunction numberFormat(decimals: number) {\n let format = formats.get(decimals);\n if (!format) {\n format = new Intl.NumberFormat(\"en-US\", {\n minimumFractionDigits: decimals,\n maximumFractionDigits: decimals,\n });\n formats.set(decimals, format);\n }\n return format;\n}\n\n/** A number that counts to each new value on the smooth spring. useCountTo counts whole steps, so decimals are counted in units of the last place. */\nfunction Count({ value, decimals }: { value: number; decimals: number }) {\n const scale = 10 ** decimals;\n const shown = useCountTo(Math.round(value * scale));\n return <>{numberFormat(decimals).format(shown / scale)}</>;\n}\n\nfunction AlertCircleIcon() {\n return (\n <svg\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n className=\"size-3.5\"\n >\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M12 8v4M12 16h.01\" />\n </svg>\n );\n}\n\nfunction AlertTriangleIcon() {\n return (\n <svg\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n className=\"size-3.5\"\n >\n <path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\" />\n <path d=\"M12 9v4M12 17h.01\" />\n </svg>\n );\n}\n\nexport const UsageMeter = forwardRef<HTMLDivElement, UsageMeterProps>(function UsageMeter(\n {\n label,\n segments,\n limit,\n unit = \"\",\n decimals = 1,\n freeLabel = \"Free\",\n overLabel = \"Over limit\",\n warnAt = 0.9,\n className,\n ...props\n },\n ref,\n) {\n const root = useRef<HTMLDivElement | null>(null);\n const refs = useMemo(() => mergeRefs(ref, root), [ref]);\n const bar = useRef<HTMLDivElement>(null);\n const track = useRef<HTMLDivElement>(null);\n const marker = useRef<HTMLSpanElement>(null);\n const overflow = useRef<SVGSVGElement>(null);\n const legend = useRef<HTMLDivElement>(null);\n const titleId = useId();\n const hatchId = `hatch${useSvgId()}`;\n\n const round = (value: number) => Math.round(value * 10 ** decimals) / 10 ** decimals;\n const total = round(segments.reduce((sum, segment) => sum + segment.value, 0));\n const free = round(Math.max(0, limit - total));\n const over = round(Math.max(0, total - limit));\n const status: Status = total > limit ? \"over\" : total >= limit * warnAt ? \"near\" : \"ok\";\n const format = (value: number) =>\n `${numberFormat(decimals).format(value)}${unit ? ` ${unit}` : \"\"}`;\n const limitText = format(limit).replace(/\\.0+(?= |$)/, \"\");\n\n // Seen: the segments grow once a good part of the meter is in view.\n const [ready, setReady] = useState(false);\n useEffect(() => {\n const node = root.current;\n if (!node || prefersReducedMotion() || typeof IntersectionObserver === \"undefined\") {\n setReady(true);\n return;\n }\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (!entry?.isIntersecting) return;\n observer.disconnect();\n setReady(true);\n },\n { threshold: 0.4 },\n );\n observer.observe(node);\n return () => observer.disconnect();\n }, []);\n\n // One live value per segment id, kept across renders so a changed value springs from where it is.\n const [amounts] = useState(() => new Map<string, Live>());\n const shownLimit = useRef(live(limit));\n const width = useRef(0);\n\n /** One writer: every segment's place and length, the limit marker and the overflow hatch. */\n const paint = useCallback(() => {\n const frame = track.current;\n if (!frame) return;\n const w = width.current;\n const nodes = frame.querySelectorAll<HTMLElement>(\"[data-segment]\");\n let used = 0;\n for (const node of nodes) used += amounts.get(node.dataset.segment!)?.value ?? 0;\n const limitNow = shownLimit.current.value;\n // The bar spans the limit, or everything used once that is more; both follow their springs, so crossing the limit is continuous.\n const span = Math.max(limitNow, used, 1e-6);\n let start = 0;\n for (const node of nodes) {\n const amount = amounts.get(node.dataset.segment!)?.value ?? 0;\n const x = (start / span) * w;\n const size = (amount / span) * w;\n const rest = w - x - size;\n const scale = w ? Math.max(0, size - Math.min(GAP, size, Math.max(0, rest))) / w : 0;\n node.style.transform = `translateX(${x.toFixed(2)}px) scaleX(${scale.toFixed(4)})`;\n start += amount;\n }\n const limitX = Math.round((limitNow / span) * w);\n if (marker.current) marker.current.style.translate = `${limitX}px 0`;\n if (overflow.current) overflow.current.style.clipPath = `inset(0 0 0 ${limitX + GAP}px)`;\n if (bar.current) {\n if (used > limitNow) bar.current.setAttribute(\"data-over\", \"\");\n else bar.current.removeAttribute(\"data-over\");\n }\n }, [amounts]);\n\n useIsomorphicLayoutEffect(() => {\n const node = track.current;\n if (!node) return;\n const read = () => {\n width.current = node.clientWidth;\n paint();\n };\n read();\n if (typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(read);\n observer.observe(node);\n return () => observer.disconnect();\n }, [paint]);\n useIsomorphicLayoutEffect(paint);\n\n // Segments grow into place once seen, left to right; later changes re-flow every segment on one spring each.\n const values = segments.map((segment) => `${segment.id}:${segment.value}`).join(\"|\");\n const revealed = useRef(false);\n const latest = useRef(segments);\n useIsomorphicLayoutEffect(() => {\n latest.current = segments;\n });\n useEffect(() => {\n if (!ready) return;\n const first = !revealed.current;\n revealed.current = true;\n latest.current.forEach((segment, index) => {\n let amount = amounts.get(segment.id);\n if (!amount) {\n amount = live(0);\n amounts.set(segment.id, amount);\n }\n retarget(amount, segment.value, first ? FILL : SMOOTH, paint, first ? index * STAGGER_MS : 0);\n });\n // `values` stands for every segment id and value.\n }, [values, ready, paint, amounts]);\n useEffect(() => {\n retarget(shownLimit.current, limit, SMOOTH, paint);\n }, [limit, paint]);\n useEffect(() => {\n const bound = shownLimit.current;\n return () => {\n for (const amount of amounts.values()) amount.stop();\n bound.stop();\n };\n }, [amounts]);\n\n // Hover previews a category, focus previews it too, and a press pins it until pressed again or Escape.\n const [hovered, setHovered] = useState<string | null>(null);\n const [focused, setFocused] = useState<string | null>(null);\n const [pinned, setPinned] = useState<string | null>(null);\n const [roving, setRoving] = useState(0);\n const items = [\n ...segments.map((segment) => ({ id: segment.id, label: segment.label, value: segment.value })),\n {\n id: \"free\",\n label: status === \"over\" ? overLabel : freeLabel,\n value: status === \"over\" ? over : free,\n },\n ];\n const active = hovered ?? focused ?? pinned;\n const activeItem = items.find((item) => item.id === active) ?? null;\n\n const idAt = (clientX: number) => {\n const rect = track.current?.getBoundingClientRect();\n if (!rect?.width) return null;\n const at = ((clientX - rect.left) / rect.width) * Math.max(limit, total);\n // Past the limit the hatch stands for the overage, so pointing there highlights it rather than the category beneath.\n if (at > limit) return \"free\";\n let end = 0;\n for (const segment of segments) {\n end += segment.value;\n if (at <= end) return segment.id;\n }\n return \"free\";\n };\n const onTrackMove = (event: PointerEvent<HTMLDivElement>) => {\n if (event.pointerType === \"mouse\") setHovered(idAt(event.clientX));\n };\n const onTrackTap = (event: PointerEvent<HTMLDivElement>) => {\n if (event.pointerType === \"mouse\") return;\n const id = idAt(event.clientX);\n setPinned((current) => (current === id ? null : id));\n };\n const onLegendKey = (event: KeyboardEvent<HTMLDivElement>) => {\n if (event.key === \"Escape\") {\n if (pinned || focused) {\n event.preventDefault();\n setPinned(null);\n setFocused(null);\n }\n return;\n }\n const next = (\n {\n ArrowRight: roving + 1,\n ArrowDown: roving + 1,\n ArrowLeft: roving - 1,\n ArrowUp: roving - 1,\n Home: 0,\n End: items.length - 1,\n } as Record<string, number>\n )[event.key];\n if (next === undefined) return;\n event.preventDefault();\n const index = (next + items.length) % items.length;\n setRoving(index);\n legend.current?.querySelectorAll<HTMLButtonElement>(\"button\")[index]?.focus();\n };\n\n const headline = ready ? (activeItem ? activeItem.value : total) : 0;\n const share = activeItem ? Math.round((activeItem.value / limit) * 100) : 0;\n const caption = !activeItem\n ? `${unit} of ${limitText} used`\n : activeItem.id === \"free\"\n ? `${unit} ${status === \"over\" ? \"over the limit\" : \"free\"}`\n : `${unit} in ${activeItem.label} · ${share}% of plan`;\n const badge =\n status === \"over\"\n ? `${format(over)} over`\n : status === \"near\"\n ? \"Almost full\"\n : `${format(free)} free`;\n const summary = `${label}: ${format(total)} of ${limitText} used. ${segments\n .map((segment) => `${segment.label} ${format(segment.value)}`)\n .join(\", \")}. ${\n status === \"over\"\n ? `Over the limit by ${format(over)}.`\n : `${format(free)} free${status === \"near\" ? \", almost full\" : \"\"}.`\n }`;\n\n // Status changes are announced, and so is a growing overage; routine changes inside a status stay quiet.\n const notice = status === \"over\" ? `over:${over}` : status;\n const [announced, setAnnounced] = useState({ notice, message: \"\" });\n if (announced.notice !== notice) {\n setAnnounced({\n notice,\n message:\n status === \"over\"\n ? `Over the plan limit by ${format(over)}.`\n : status === \"near\"\n ? `Almost full. ${format(free)} free.`\n : `Under the limit. ${format(free)} free.`,\n });\n }\n\n return (\n <div\n ref={refs}\n {...props}\n role=\"group\"\n aria-labelledby={titleId}\n data-slot=\"usage-meter\"\n data-status={status}\n className={cn(\"usage-meter\", className)}\n >\n <div className=\"flex min-h-7 min-w-0 items-center justify-between gap-3\">\n <span id={titleId} className=\"min-w-0 text-base font-medium text-foreground text-balance\">\n {label}\n </span>\n <Badge\n tone={status === \"over\" ? \"danger\" : status === \"near\" ? \"warning\" : \"neutral\"}\n shape=\"pill\"\n size=\"lg\"\n icon={\n status === \"over\" ? (\n <AlertTriangleIcon />\n ) : status === \"near\" ? (\n <AlertCircleIcon />\n ) : undefined\n }\n >\n {badge}\n </Badge>\n </div>\n <p className=\"m-0 flex min-w-0 items-baseline gap-2\">\n <span className=\"sr-only\">\n {format(total)} of {limitText} used\n </span>\n <span\n data-slot=\"usage-meter-amount\"\n aria-hidden=\"true\"\n className=\"flex-none font-display text-h2 font-medium tabular-nums\"\n >\n <Count value={headline} decimals={decimals} />\n </span>\n <span aria-hidden=\"true\" className=\"min-w-0 flex-1 text-sm text-text-secondary\">\n <SwapText swapKey={caption} className=\"block truncate\">\n {caption}\n </SwapText>\n </span>\n </p>\n <div ref={bar} className=\"usage-meter-bar\" data-slot=\"usage-meter-bar\">\n <div\n ref={track}\n role=\"img\"\n aria-label={summary}\n data-active={active ?? undefined}\n onPointerMove={onTrackMove}\n onPointerLeave={() => setHovered(null)}\n onPointerUp={onTrackTap}\n className=\"usage-meter-track\"\n >\n {segments.map((segment, index) => (\n <span\n key={segment.id}\n data-segment={segment.id}\n data-index={index}\n data-highlight={active ? (active === segment.id ? \"on\" : \"off\") : undefined}\n className=\"usage-meter-segment\"\n />\n ))}\n {/* Past the limit the bar is struck through: the overflow keeps its colours, hatched so it reads as over without colour. */}\n <svg\n ref={overflow}\n aria-hidden=\"true\"\n focusable=\"false\"\n data-highlight={active ? (active === \"free\" ? \"on\" : \"off\") : undefined}\n className=\"usage-meter-overflow\"\n >\n <defs>\n <pattern\n id={hatchId}\n width={6}\n height={6}\n patternUnits=\"userSpaceOnUse\"\n patternTransform=\"rotate(45)\"\n >\n <rect width={2.5} height={6} className=\"usage-meter-hatch\" />\n </pattern>\n </defs>\n <rect width=\"100%\" height=\"100%\" className=\"usage-meter-over-tint\" />\n <rect width=\"100%\" height=\"100%\" fill={`url(#${hatchId})`} />\n </svg>\n </div>\n <span ref={marker} aria-hidden=\"true\" className=\"usage-meter-limit\" />\n </div>\n <div\n ref={legend}\n role=\"group\"\n aria-label={`${label} by category`}\n onKeyDown={onLegendKey}\n className=\"usage-meter-legend\"\n >\n {items.map((item, index) => {\n const warning = item.id === \"free\" && status === \"over\";\n return (\n <button\n key={item.id}\n type=\"button\"\n data-slot=\"usage-meter-item\"\n data-highlight={active ? (active === item.id ? \"on\" : \"off\") : undefined}\n aria-pressed={pinned === item.id}\n aria-label={`${item.label}, ${format(item.value)}`}\n tabIndex={index === roving ? 0 : -1}\n onClick={() => {\n setRoving(index);\n setPinned((current) => (current === item.id ? null : item.id));\n }}\n onFocus={(event) => {\n setRoving(index);\n if (event.currentTarget.matches(\":focus-visible\")) setFocused(item.id);\n }}\n onBlur={() => setFocused(null)}\n onPointerEnter={(event) => {\n if (event.pointerType === \"mouse\") setHovered(item.id);\n }}\n onPointerLeave={(event) => {\n if (event.pointerType === \"mouse\") setHovered(null);\n }}\n className=\"usage-meter-item\"\n >\n <span className=\"flex min-w-0 max-w-full items-center gap-2 text-sm text-text-secondary\">\n <span aria-hidden=\"true\" className=\"usage-meter-key\">\n <SwapText\n swapKey={warning ? \"alert\" : \"swatch\"}\n motion=\"glyph\"\n className=\"grid place-items-center\"\n >\n {warning ? (\n <span className=\"text-destructive-strong\">\n <AlertTriangleIcon />\n </span>\n ) : (\n <span\n data-index={item.id === \"free\" ? \"free\" : index}\n className=\"usage-meter-swatch\"\n />\n )}\n </SwapText>\n </span>\n <span className=\"min-w-0 flex-1\">\n <SwapText swapKey={item.label} className=\"block truncate\">\n {item.label}\n </SwapText>\n </span>\n </span>\n <span\n aria-hidden=\"true\"\n className=\"flex items-baseline gap-1 ps-5 text-base font-medium text-foreground tabular-nums\"\n >\n <Count value={item.value} decimals={decimals} />\n {unit ? (\n <span className=\"text-sm font-normal text-text-secondary\">{unit}</span>\n ) : null}\n </span>\n </button>\n );\n })}\n </div>\n <p className=\"sr-only\" aria-live=\"polite\" aria-atomic=\"true\">\n {announced.message}\n </p>\n </div>\n );\n});\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { WaffleCategory, WaffleChart, WaffleChartProps } from "./waffle-chart.js";
2
+ export { type WaffleCategory, WaffleChart, type WaffleChartProps };
@@ -0,0 +1,2 @@
1
+ import { WaffleChart } from "./waffle-chart.js";
2
+ export { WaffleChart };
@@ -0,0 +1,57 @@
1
+ //#region src/waffle-chart/waffle-chart.d.ts
2
+ /**
3
+ * WaffleChart: parts of a whole as countable squares. Every cell is one share of
4
+ * the total (a 10 by 10 grid is one cell per percent), filled column by column
5
+ * from the bottom-left corner like a glass filling up.
6
+ *
7
+ * The cells cascade in once the chart is a third in view. New data hands cells
8
+ * to categories while moving as few as possible: each cell that changes category
9
+ * travels to its new place (dipping as it flies) and changes colour on the same
10
+ * stagger. Hovering a cell, a legend row or arrowing through the grid lifts that
11
+ * category and dims the rest; the legend shares count to their new values.
12
+ * Neutral categories differ in shape as well as lightness (square, round,
13
+ * framed), so colour is never the only channel.
14
+ *
15
+ * Plain CSS transitions place the cells, with nothing measured: a cell's place is
16
+ * two unitless numbers and its size comes from the width of the grid.
17
+ */
18
+ export interface WaffleCategory {
19
+ /** Stable identity. Cells keep following their category when the data changes, so a new dataset flows instead of repainting. */
20
+ key: string;
21
+ /** Name in the legend, the tooltip and the data table. */
22
+ label: string;
23
+ /** Raw amount. Shares are computed from the total; negatives count as zero. */
24
+ value: number;
25
+ /** Any CSS colour. Defaults to chart-1 for the accent, chart-2 to chart-4 for the next three, then neutral steps. */
26
+ color?: string;
27
+ }
28
+ export interface WaffleChartProps {
29
+ /** Categories in fill order. The first fills from the bottom-left corner, column by column. */
30
+ data: WaffleCategory[];
31
+ /** What the whole is, such as "Electricity generation, 2023". Names the chart for assistive technology. */
32
+ label: string;
33
+ /** Unit after each raw value in the tooltip and table, such as "TWh". */
34
+ unit?: string;
35
+ formatValue?: (value: number, category: WaffleCategory) => string;
36
+ /** @default 10 */
37
+ rows?: number;
38
+ /** rows times columns cells make the whole; 10 by 10 is one cell per percent. @default 10 */
39
+ columns?: number;
40
+ /** The category painted in chart-1. Pass null to keep every category on its own step. Defaults to the first category. */
41
+ accentKey?: string | null;
42
+ /** Controlled pinned category. Others dim while one is pinned. */
43
+ activeKey?: string | null;
44
+ defaultActiveKey?: string | null;
45
+ onActiveChange?: (key: string | null) => void;
46
+ /** The category list beside or below the grid, with shares that count to their new value. @default true */
47
+ legend?: boolean;
48
+ /** Decimal places for shares. @default 0 */
49
+ decimals?: number;
50
+ /** @default "No data" */
51
+ emptyLabel?: string;
52
+ /** Extra classes for the root. Merged last. */
53
+ className?: string;
54
+ }
55
+ export declare const WaffleChart: import("react").ForwardRefExoticComponent<WaffleChartProps & import("react").RefAttributes<HTMLElement>>;
56
+ //#endregion
57
+ //# sourceMappingURL=waffle-chart.d.ts.map
@@ -0,0 +1,426 @@
1
+ "use client";
2
+ import { cn } from "../lib/cn.js";
3
+ import { mergeRefs } from "../lib/merge-refs.js";
4
+ import { prefersReducedMotion, useIsomorphicLayoutEffect } from "../lib/motion.js";
5
+ import { useCountTo } from "../lib/count-to.js";
6
+ import { forwardRef, useEffect, useMemo, useRef, useState } from "react";
7
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
8
+ //#region src/waffle-chart/waffle-chart.tsx
9
+ /** Space between cells, in pixels; the same number is in the waffle-chart-grid utility. */
10
+ const GAP = 3;
11
+ /** Lightness steps for categories past the four series colours; they alternate so neighbours differ. */
12
+ const SHADES = [
13
+ 40,
14
+ 20,
15
+ 30,
16
+ 14
17
+ ];
18
+ /** The colour-free second channel. */
19
+ const SHAPES = [
20
+ "square",
21
+ "round",
22
+ "framed"
23
+ ];
24
+ const clamp = (value, low, high) => Math.min(high, Math.max(low, value));
25
+ const GROUPED = new Intl.NumberFormat("en-US", { maximumFractionDigits: 1 });
26
+ /** Whole cells per category by largest remainder, so the counts always add up to the grid. */
27
+ function allocate(data, total, cells) {
28
+ if (total <= 0) return data.map(() => 0);
29
+ const exact = data.map((item) => Math.max(0, item.value) / total * cells);
30
+ const counts = exact.map(Math.floor);
31
+ let left = cells - counts.reduce((sum, count) => sum + count, 0);
32
+ const order = exact.map((value, index) => ({
33
+ index,
34
+ rest: value - Math.floor(value)
35
+ })).sort((a, b) => b.rest - a.rest);
36
+ for (let k = 0; left > 0 && k < order.length; k++, left--) counts[order[k].index]++;
37
+ return counts;
38
+ }
39
+ /**
40
+ * Hands cells to categories while moving as few as possible: each category keeps the cells it already had,
41
+ * surplus cells join the categories that grew, and every block takes its cells in their previous order so paths never cross.
42
+ */
43
+ function reassign(previous, data, counts) {
44
+ const bySlot = [...previous].sort((a, b) => a.slot - b.slot);
45
+ const kept = /* @__PURE__ */ new Map();
46
+ const pool = [];
47
+ const want = new Map(data.map((item, index) => [item.key, counts[index]]));
48
+ for (const cell of bySlot) {
49
+ const list = cell.key !== null ? kept.get(cell.key) ?? [] : [];
50
+ if (cell.key !== null && list.length < (want.get(cell.key) ?? 0)) {
51
+ list.push(cell);
52
+ kept.set(cell.key, list);
53
+ } else pool.push(cell);
54
+ }
55
+ const next = [];
56
+ let slot = 0;
57
+ data.forEach((item, index) => {
58
+ const own = kept.get(item.key) ?? [];
59
+ while (own.length < counts[index] && pool.length) own.push(pool.shift());
60
+ own.sort((a, b) => a.slot - b.slot).forEach((cell) => next.push({
61
+ id: cell.id,
62
+ key: item.key,
63
+ slot: slot++
64
+ }));
65
+ });
66
+ pool.forEach((cell) => next.push({
67
+ id: cell.id,
68
+ key: null,
69
+ slot: slot++
70
+ }));
71
+ return next.sort((a, b) => a.id - b.id);
72
+ }
73
+ /** A share that counts to its new value and holds its width with tabular figures. */
74
+ function Share({ value, decimals }) {
75
+ const scale = 10 ** decimals;
76
+ const shown = useCountTo(Math.round(value * scale));
77
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [(shown / scale).toFixed(decimals), "%"] });
78
+ }
79
+ const WaffleChart = forwardRef(function WaffleChart({ data, label, unit = "", formatValue, rows = 10, columns = 10, accentKey, activeKey, defaultActiveKey = null, onActiveChange, legend = true, decimals = 0, emptyLabel = "No data", className }, ref) {
80
+ const figure = useRef(null);
81
+ const refs = useMemo(() => mergeRefs(ref, figure), [ref]);
82
+ const board = useRef(null);
83
+ const grid = useRef(null);
84
+ const tip = useRef(null);
85
+ const count = Math.max(1, rows * columns);
86
+ const total = data.reduce((sum, item) => sum + Math.max(0, item.value), 0);
87
+ const empty = !data.length || total <= 0;
88
+ const accent = accentKey === void 0 ? data[0]?.key ?? null : accentKey;
89
+ const signature = `${count}|${data.map((item) => `${item.key}:${item.value}`).join(",")}`;
90
+ const counts = useMemo(() => allocate(data, total, count), [signature]);
91
+ const [layout, setLayout] = useState(() => ({
92
+ cells: reassign(Array.from({ length: count }, (_, id) => ({
93
+ id,
94
+ key: null,
95
+ slot: id
96
+ })), data, counts),
97
+ moved: /* @__PURE__ */ new Set(),
98
+ version: 0
99
+ }));
100
+ const [seen, setSeen] = useState(signature);
101
+ if (seen !== signature) {
102
+ setSeen(signature);
103
+ const base = Array.from({ length: count }, (_, id) => layout.cells[id] ?? {
104
+ id,
105
+ key: null,
106
+ slot: id
107
+ });
108
+ const next = reassign(base, data, counts);
109
+ setLayout({
110
+ cells: next,
111
+ moved: new Set(next.filter((cell) => base[cell.id].slot !== cell.slot || base[cell.id].key !== cell.key).map((cell) => cell.id)),
112
+ version: layout.version + 1
113
+ });
114
+ }
115
+ const { cells, moved, version } = layout;
116
+ const [shown, setShown] = useState(false);
117
+ useEffect(() => {
118
+ const node = figure.current;
119
+ if (!node || prefersReducedMotion() || typeof IntersectionObserver === "undefined") {
120
+ setShown(true);
121
+ return;
122
+ }
123
+ const observer = new IntersectionObserver(([entry]) => {
124
+ if (!entry?.isIntersecting) return;
125
+ observer.disconnect();
126
+ setShown(true);
127
+ }, { threshold: .35 });
128
+ observer.observe(node);
129
+ return () => observer.disconnect();
130
+ }, []);
131
+ const [ownPinned, setOwnPinned] = useState(defaultActiveKey);
132
+ const pinned = activeKey !== void 0 ? activeKey : ownPinned;
133
+ const pin = (key) => {
134
+ if (activeKey === void 0) setOwnPinned(key);
135
+ onActiveChange?.(key);
136
+ };
137
+ const [reading, setReading] = useState(null);
138
+ const [preview, setPreview] = useState(null);
139
+ const focusKey = reading?.key ?? preview ?? pinned;
140
+ const active = focusKey && data.some((item) => item.key === focusKey) ? focusKey : null;
141
+ const rank = (key) => data.filter((entry) => entry.key !== accent).findIndex((entry) => entry.key === key);
142
+ const colorOf = (key) => {
143
+ const item = data.find((entry) => entry.key === key);
144
+ if (!item) return "transparent";
145
+ if (item.color) return item.color;
146
+ if (key === accent) return "var(--color-chart-1)";
147
+ const at = rank(key);
148
+ return at < 3 ? `var(--color-chart-${at + 2})` : `color-mix(in oklab, var(--color-foreground) ${SHADES[(at - 3) % SHADES.length]}%, var(--color-surface))`;
149
+ };
150
+ const shapeOf = (key) => key === null || key === accent || rank(key) < 0 ? "square" : SHAPES[rank(key) % SHAPES.length];
151
+ const place = (slot) => ({
152
+ x: Math.floor(slot / rows),
153
+ y: rows - 1 - slot % rows
154
+ });
155
+ const filled = cells.filter((cell) => cell.key !== null).length;
156
+ const bubble = reading ? data.find((item) => item.key === reading.key) ?? null : null;
157
+ const shownBefore = useRef(false);
158
+ useIsomorphicLayoutEffect(() => {
159
+ const node = tip.current;
160
+ const box = grid.current?.getBoundingClientRect();
161
+ const host = board.current?.getBoundingClientRect();
162
+ if (!reading || !node || !box?.width || !host) {
163
+ shownBefore.current = false;
164
+ return;
165
+ }
166
+ const side = (box.width - GAP * (columns - 1)) / columns;
167
+ const { x, y } = place(reading.slot);
168
+ const cx = box.left - host.left + x * (side + GAP) + side / 2;
169
+ const cy = box.top - host.top + y * (side + GAP);
170
+ const tw = node.offsetWidth;
171
+ const th = node.offsetHeight;
172
+ let top = cy - th - 10;
173
+ if (top < 0) top = cy + side + 10;
174
+ if (!shownBefore.current) node.style.transition = "none";
175
+ node.style.translate = `${clamp(cx - tw / 2, 0, Math.max(0, host.width - tw))}px ${clamp(top, 0, Math.max(0, host.height - th))}px`;
176
+ if (!shownBefore.current) {
177
+ node.offsetWidth;
178
+ node.style.transition = "";
179
+ }
180
+ shownBefore.current = true;
181
+ });
182
+ const slotAt = (clientX, clientY) => {
183
+ const box = grid.current?.getBoundingClientRect();
184
+ if (!box?.width) return null;
185
+ const side = (box.width - GAP * (columns - 1)) / columns;
186
+ const col = clamp(Math.floor((clientX - box.left) / (side + GAP)), 0, columns - 1);
187
+ const row = clamp(Math.floor((clientY - box.top) / (side + GAP)), 0, rows - 1);
188
+ return col * rows + (rows - 1 - row);
189
+ };
190
+ const readingAt = (slot) => {
191
+ if (slot === null) return null;
192
+ const cell = cells.find((item) => item.slot === slot);
193
+ return cell?.key ? {
194
+ key: cell.key,
195
+ slot
196
+ } : null;
197
+ };
198
+ const show = (next) => setReading((now) => now?.slot === next?.slot ? now : next);
199
+ const onPointerMove = (event) => {
200
+ if (event.pointerType === "mouse" || event.buttons) show(readingAt(slotAt(event.clientX, event.clientY)));
201
+ };
202
+ const onPointerDown = (event) => {
203
+ if (event.pointerType !== "mouse") event.currentTarget.setPointerCapture?.(event.pointerId);
204
+ show(readingAt(slotAt(event.clientX, event.clientY)));
205
+ };
206
+ const onKeyDown = (event) => {
207
+ if (empty) return;
208
+ if (event.key === "Escape" && reading) {
209
+ event.preventDefault();
210
+ setReading(null);
211
+ return;
212
+ }
213
+ const at = reading?.slot ?? 0;
214
+ const col = Math.floor(at / rows);
215
+ const row = at % rows;
216
+ const blocks = counts.map((size, index) => ({
217
+ start: counts.slice(0, index).reduce((sum, value) => sum + value, 0),
218
+ size
219
+ })).filter((block) => block.size > 0);
220
+ const block = Math.max(0, blocks.findIndex((item) => at >= item.start && at < item.start + item.size));
221
+ const move = {
222
+ ArrowUp: () => col * rows + Math.min(rows - 1, row + 1),
223
+ ArrowDown: () => col * rows + Math.max(0, row - 1),
224
+ ArrowRight: () => Math.min(columns - 1, col + 1) * rows + row,
225
+ ArrowLeft: () => Math.max(0, col - 1) * rows + row,
226
+ PageDown: () => blocks[Math.min(blocks.length - 1, block + 1)].start,
227
+ PageUp: () => at > blocks[block].start ? blocks[block].start : blocks[Math.max(0, block - 1)].start,
228
+ Home: () => 0,
229
+ End: () => filled - 1
230
+ }[event.key];
231
+ if (!move) return;
232
+ event.preventDefault();
233
+ show(readingAt(clamp(reading ? move() : 0, 0, Math.max(0, filled - 1))));
234
+ };
235
+ const suffix = unit ? ` ${unit}` : "";
236
+ const format = (value, item) => `${formatValue ? formatValue(value, item) : GROUPED.format(value)}${suffix}`;
237
+ const share = (item) => total > 0 ? Math.max(0, item.value) / total * 100 : 0;
238
+ const describe = (item) => `${item.label}, ${share(item).toFixed(decimals)}%, ${format(item.value, item)}`;
239
+ const summary = empty ? `${label}. ${emptyLabel}.` : `${label}. ${data.map((item) => `${item.label} ${share(item).toFixed(decimals)}%`).join(", ")}.`;
240
+ return /* @__PURE__ */ jsxs("figure", {
241
+ ref: refs,
242
+ "data-slot": "waffle-chart",
243
+ "aria-label": label,
244
+ "data-legend": legend || void 0,
245
+ "data-scrubbing": reading !== null || void 0,
246
+ className: cn("waffle-chart box-border font-sans text-foreground", className),
247
+ children: [
248
+ /* @__PURE__ */ jsxs("div", {
249
+ ref: board,
250
+ className: "relative min-w-0",
251
+ children: [
252
+ /* @__PURE__ */ jsx("div", {
253
+ ref: grid,
254
+ "data-slot": "waffle-chart-grid",
255
+ "data-active": active ? "" : void 0,
256
+ role: empty ? void 0 : "slider",
257
+ tabIndex: empty ? void 0 : 0,
258
+ "aria-label": empty ? void 0 : `${label}, explore cells`,
259
+ "aria-valuemin": empty ? void 0 : 1,
260
+ "aria-valuemax": empty ? void 0 : Math.max(1, filled),
261
+ "aria-valuenow": empty ? void 0 : (reading?.slot ?? 0) + 1,
262
+ "aria-valuetext": empty ? void 0 : bubble ? describe(bubble) : summary,
263
+ "aria-hidden": empty || void 0,
264
+ className: "waffle-chart-grid",
265
+ style: {
266
+ "--c": columns,
267
+ "--r": rows
268
+ },
269
+ onPointerMove,
270
+ onPointerDown,
271
+ onPointerUp: (event) => {
272
+ if (event.pointerType !== "mouse") setReading(null);
273
+ },
274
+ onPointerCancel: () => setReading(null),
275
+ onPointerLeave: (event) => {
276
+ if (event.pointerType === "mouse") setReading(null);
277
+ },
278
+ onKeyDown,
279
+ onBlur: () => setReading(null),
280
+ onFocus: (event) => {
281
+ if (event.currentTarget.matches(":focus-visible") && !empty) setReading((now) => now ?? readingAt(0));
282
+ },
283
+ children: cells.map((cell) => {
284
+ const { x, y } = place(cell.slot);
285
+ return /* @__PURE__ */ jsx("span", {
286
+ "aria-hidden": "true",
287
+ "data-cell": cell.slot,
288
+ "data-shown": shown || void 0,
289
+ "data-on": cell.key !== null && cell.key === active || void 0,
290
+ "data-cursor": reading?.slot === cell.slot || void 0,
291
+ "data-empty": cell.key === null || void 0,
292
+ "data-shape": shapeOf(cell.key),
293
+ "data-hop": moved.has(cell.id) && shown ? version % 2 ? "a" : "b" : void 0,
294
+ className: "waffle-chart-cell",
295
+ style: {
296
+ "--x": x,
297
+ "--y": y,
298
+ "--series": colorOf(cell.key),
299
+ "--delay": `${Math.round(cell.slot / count * 320)}ms`
300
+ }
301
+ }, cell.id);
302
+ })
303
+ }),
304
+ empty ? null : /* @__PURE__ */ jsxs("div", {
305
+ ref: tip,
306
+ "aria-hidden": "true",
307
+ "data-slot": "waffle-chart-tooltip",
308
+ className: "line-chart-tooltip",
309
+ children: [/* @__PURE__ */ jsxs("p", {
310
+ className: "m-0 flex min-w-0 items-center gap-2 text-sm",
311
+ children: [
312
+ /* @__PURE__ */ jsx("span", {
313
+ "data-shape": shapeOf(bubble?.key ?? null),
314
+ style: { "--series": colorOf(bubble?.key ?? null) },
315
+ className: "chart-key-swatch"
316
+ }),
317
+ /* @__PURE__ */ jsx("span", {
318
+ className: "min-w-0 flex-1 truncate text-text-secondary",
319
+ children: bubble?.label ?? ""
320
+ }),
321
+ /* @__PURE__ */ jsx("span", {
322
+ className: "ms-2 flex-none font-medium tabular-nums",
323
+ children: bubble ? `${share(bubble).toFixed(decimals)}%` : ""
324
+ })
325
+ ]
326
+ }), /* @__PURE__ */ jsx("p", {
327
+ className: "m-0 ms-4.5 whitespace-nowrap text-xs tabular-nums text-muted-foreground",
328
+ children: bubble ? format(bubble.value, bubble) : ""
329
+ })]
330
+ }),
331
+ empty ? /* @__PURE__ */ jsx("p", {
332
+ className: "m-0 grid min-h-40 place-items-center text-sm text-muted-foreground",
333
+ children: emptyLabel
334
+ }) : null
335
+ ]
336
+ }),
337
+ legend && !empty ? /* @__PURE__ */ jsx("ul", {
338
+ "aria-label": `${label}, categories`,
339
+ onPointerLeave: () => setPreview(null),
340
+ className: "waffle-chart-legend",
341
+ children: data.map((item) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("button", {
342
+ type: "button",
343
+ "aria-pressed": pinned === item.key,
344
+ "data-on": active === item.key || void 0,
345
+ "data-dim": active !== null && active !== item.key || void 0,
346
+ className: "chart-key",
347
+ onClick: () => pin(pinned === item.key ? null : item.key),
348
+ onPointerEnter: (event) => {
349
+ if (event.pointerType === "mouse") setPreview(item.key);
350
+ },
351
+ onFocus: (event) => {
352
+ if (event.currentTarget.matches(":focus-visible")) setPreview(item.key);
353
+ },
354
+ onBlur: () => setPreview(null),
355
+ children: [
356
+ /* @__PURE__ */ jsx("span", {
357
+ "aria-hidden": "true",
358
+ "data-shape": shapeOf(item.key),
359
+ style: { "--series": colorOf(item.key) },
360
+ className: "chart-key-swatch"
361
+ }),
362
+ /* @__PURE__ */ jsx("span", {
363
+ className: "min-w-0 truncate",
364
+ children: item.label
365
+ }),
366
+ /* @__PURE__ */ jsx("span", {
367
+ "aria-hidden": "true",
368
+ className: "min-w-13 text-end text-base font-medium tabular-nums text-foreground",
369
+ children: /* @__PURE__ */ jsx(Share, {
370
+ value: share(item),
371
+ decimals
372
+ })
373
+ })
374
+ ]
375
+ }) }, item.key))
376
+ }) : null,
377
+ /* @__PURE__ */ jsx("p", {
378
+ className: "sr-only",
379
+ children: summary
380
+ }),
381
+ /* @__PURE__ */ jsx("p", {
382
+ className: "sr-only",
383
+ "aria-live": "polite",
384
+ "aria-atomic": "true",
385
+ children: bubble ? describe(bubble) : ""
386
+ }),
387
+ empty ? null : /* @__PURE__ */ jsx("div", {
388
+ className: "sr-only",
389
+ children: /* @__PURE__ */ jsxs("table", { children: [
390
+ /* @__PURE__ */ jsx("caption", { children: label }),
391
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
392
+ /* @__PURE__ */ jsx("th", {
393
+ scope: "col",
394
+ children: "Category"
395
+ }),
396
+ /* @__PURE__ */ jsx("th", {
397
+ scope: "col",
398
+ children: "Value"
399
+ }),
400
+ /* @__PURE__ */ jsx("th", {
401
+ scope: "col",
402
+ children: "Share"
403
+ }),
404
+ /* @__PURE__ */ jsx("th", {
405
+ scope: "col",
406
+ children: "Cells"
407
+ })
408
+ ] }) }),
409
+ /* @__PURE__ */ jsx("tbody", { children: data.map((item, index) => /* @__PURE__ */ jsxs("tr", { children: [
410
+ /* @__PURE__ */ jsx("th", {
411
+ scope: "row",
412
+ children: item.label
413
+ }),
414
+ /* @__PURE__ */ jsx("td", { children: format(item.value, item) }),
415
+ /* @__PURE__ */ jsxs("td", { children: [share(item).toFixed(decimals), "%"] }),
416
+ /* @__PURE__ */ jsx("td", { children: counts[index] })
417
+ ] }, item.key)) })
418
+ ] })
419
+ })
420
+ ]
421
+ });
422
+ });
423
+ //#endregion
424
+ export { WaffleChart };
425
+
426
+ //# sourceMappingURL=waffle-chart.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"waffle-chart.js","names":[],"sources":["../../src/waffle-chart/waffle-chart.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n forwardRef,\n useEffect,\n useMemo,\n useRef,\n useState,\n type CSSProperties,\n type KeyboardEvent,\n type PointerEvent,\n} from \"react\";\n\nimport { cn } from \"../lib/cn.js\";\nimport { useCountTo } from \"../lib/count-to.js\";\nimport { mergeRefs } from \"../lib/merge-refs.js\";\nimport { prefersReducedMotion, useIsomorphicLayoutEffect } from \"../lib/motion.js\";\n\n/**\n * WaffleChart: parts of a whole as countable squares. Every cell is one share of\n * the total (a 10 by 10 grid is one cell per percent), filled column by column\n * from the bottom-left corner like a glass filling up.\n *\n * The cells cascade in once the chart is a third in view. New data hands cells\n * to categories while moving as few as possible: each cell that changes category\n * travels to its new place (dipping as it flies) and changes colour on the same\n * stagger. Hovering a cell, a legend row or arrowing through the grid lifts that\n * category and dims the rest; the legend shares count to their new values.\n * Neutral categories differ in shape as well as lightness (square, round,\n * framed), so colour is never the only channel.\n *\n * Plain CSS transitions place the cells, with nothing measured: a cell's place is\n * two unitless numbers and its size comes from the width of the grid.\n */\nexport interface WaffleCategory {\n /** Stable identity. Cells keep following their category when the data changes, so a new dataset flows instead of repainting. */\n key: string;\n /** Name in the legend, the tooltip and the data table. */\n label: string;\n /** Raw amount. Shares are computed from the total; negatives count as zero. */\n value: number;\n /** Any CSS colour. Defaults to chart-1 for the accent, chart-2 to chart-4 for the next three, then neutral steps. */\n color?: string;\n}\n\nexport interface WaffleChartProps {\n /** Categories in fill order. The first fills from the bottom-left corner, column by column. */\n data: WaffleCategory[];\n /** What the whole is, such as \"Electricity generation, 2023\". Names the chart for assistive technology. */\n label: string;\n /** Unit after each raw value in the tooltip and table, such as \"TWh\". */\n unit?: string;\n formatValue?: (value: number, category: WaffleCategory) => string;\n /** @default 10 */\n rows?: number;\n /** rows times columns cells make the whole; 10 by 10 is one cell per percent. @default 10 */\n columns?: number;\n /** The category painted in chart-1. Pass null to keep every category on its own step. Defaults to the first category. */\n accentKey?: string | null;\n /** Controlled pinned category. Others dim while one is pinned. */\n activeKey?: string | null;\n defaultActiveKey?: string | null;\n onActiveChange?: (key: string | null) => void;\n /** The category list beside or below the grid, with shares that count to their new value. @default true */\n legend?: boolean;\n /** Decimal places for shares. @default 0 */\n decimals?: number;\n /** @default \"No data\" */\n emptyLabel?: string;\n /** Extra classes for the root. Merged last. */\n className?: string;\n}\n\ntype Cell = { id: number; key: string | null; slot: number };\ntype Reading = { key: string; slot: number } | null;\n\n/** Space between cells, in pixels; the same number is in the waffle-chart-grid utility. */\nconst GAP = 3;\n/** Lightness steps for categories past the four series colours; they alternate so neighbours differ. */\nconst SHADES = [40, 20, 30, 14];\n/** The colour-free second channel. */\nconst SHAPES = [\"square\", \"round\", \"framed\"] as const;\nconst clamp = (value: number, low: number, high: number) => Math.min(high, Math.max(low, value));\nconst GROUPED = new Intl.NumberFormat(\"en-US\", { maximumFractionDigits: 1 });\n\n/** Whole cells per category by largest remainder, so the counts always add up to the grid. */\nfunction allocate(data: WaffleCategory[], total: number, cells: number): number[] {\n if (total <= 0) return data.map(() => 0);\n const exact = data.map((item) => (Math.max(0, item.value) / total) * cells);\n const counts = exact.map(Math.floor);\n let left = cells - counts.reduce((sum, count) => sum + count, 0);\n const order = exact.map((value, index) => ({ index, rest: value - Math.floor(value) })).sort((a, b) => b.rest - a.rest);\n for (let k = 0; left > 0 && k < order.length; k++, left--) counts[order[k]!.index]!++;\n return counts;\n}\n\n/**\n * Hands cells to categories while moving as few as possible: each category keeps the cells it already had,\n * surplus cells join the categories that grew, and every block takes its cells in their previous order so paths never cross.\n */\nfunction reassign(previous: Cell[], data: WaffleCategory[], counts: number[]): Cell[] {\n const bySlot = [...previous].sort((a, b) => a.slot - b.slot);\n const kept = new Map<string, Cell[]>();\n const pool: Cell[] = [];\n const want = new Map(data.map((item, index) => [item.key, counts[index]]));\n for (const cell of bySlot) {\n const list = cell.key !== null ? (kept.get(cell.key) ?? []) : [];\n if (cell.key !== null && list.length < (want.get(cell.key) ?? 0)) {\n list.push(cell);\n kept.set(cell.key, list);\n } else pool.push(cell);\n }\n const next: Cell[] = [];\n let slot = 0;\n data.forEach((item, index) => {\n const own = kept.get(item.key) ?? [];\n while (own.length < counts[index]! && pool.length) own.push(pool.shift()!);\n own.sort((a, b) => a.slot - b.slot).forEach((cell) => next.push({ id: cell.id, key: item.key, slot: slot++ }));\n });\n pool.forEach((cell) => next.push({ id: cell.id, key: null, slot: slot++ }));\n return next.sort((a, b) => a.id - b.id);\n}\n\n/** A share that counts to its new value and holds its width with tabular figures. */\nfunction Share({ value, decimals }: { value: number; decimals: number }) {\n const scale = 10 ** decimals;\n const shown = useCountTo(Math.round(value * scale));\n return <>{(shown / scale).toFixed(decimals)}%</>;\n}\n\nexport const WaffleChart = forwardRef<HTMLElement, WaffleChartProps>(function WaffleChart(\n {\n data,\n label,\n unit = \"\",\n formatValue,\n rows = 10,\n columns = 10,\n accentKey,\n activeKey,\n defaultActiveKey = null,\n onActiveChange,\n legend = true,\n decimals = 0,\n emptyLabel = \"No data\",\n className,\n },\n ref,\n) {\n const figure = useRef<HTMLElement | null>(null);\n const refs = useMemo(() => mergeRefs(ref, figure), [ref]);\n const board = useRef<HTMLDivElement>(null);\n const grid = useRef<HTMLDivElement>(null);\n const tip = useRef<HTMLDivElement>(null);\n const count = Math.max(1, rows * columns);\n const total = data.reduce((sum, item) => sum + Math.max(0, item.value), 0);\n const empty = !data.length || total <= 0;\n const accent = accentKey === undefined ? (data[0]?.key ?? null) : accentKey;\n\n // Cells are persistent objects; each data change hands them to categories again, so they travel instead of repainting.\n const signature = `${count}|${data.map((item) => `${item.key}:${item.value}`).join(\",\")}`;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n const counts = useMemo(() => allocate(data, total, count), [signature]);\n const [layout, setLayout] = useState(() => ({\n cells: reassign(Array.from({ length: count }, (_, id) => ({ id, key: null, slot: id })), data, counts),\n moved: new Set<number>(),\n version: 0,\n }));\n const [seen, setSeen] = useState(signature);\n if (seen !== signature) {\n setSeen(signature);\n const base = Array.from({ length: count }, (_, id) => layout.cells[id] ?? { id, key: null, slot: id });\n const next = reassign(base, data, counts);\n setLayout({\n cells: next,\n moved: new Set(next.filter((cell) => base[cell.id]!.slot !== cell.slot || base[cell.id]!.key !== cell.key).map((cell) => cell.id)),\n version: layout.version + 1,\n });\n }\n const { cells, moved, version } = layout;\n\n // The cells cascade in once a third of the chart is in view.\n const [shown, setShown] = useState(false);\n useEffect(() => {\n const node = figure.current;\n if (!node || prefersReducedMotion() || typeof IntersectionObserver === \"undefined\") {\n setShown(true);\n return;\n }\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (!entry?.isIntersecting) return;\n observer.disconnect();\n setShown(true);\n },\n { threshold: 0.35 },\n );\n observer.observe(node);\n return () => observer.disconnect();\n }, []);\n\n const [ownPinned, setOwnPinned] = useState<string | null>(defaultActiveKey);\n const pinned = activeKey !== undefined ? activeKey : ownPinned;\n const pin = (key: string | null) => {\n if (activeKey === undefined) setOwnPinned(key);\n onActiveChange?.(key);\n };\n const [reading, setReading] = useState<Reading>(null);\n const [preview, setPreview] = useState<string | null>(null);\n const focusKey = reading?.key ?? preview ?? pinned;\n const active = focusKey && data.some((item) => item.key === focusKey) ? focusKey : null;\n\n const rank = (key: string | null) => data.filter((entry) => entry.key !== accent).findIndex((entry) => entry.key === key);\n const colorOf = (key: string | null) => {\n const item = data.find((entry) => entry.key === key);\n if (!item) return \"transparent\";\n if (item.color) return item.color;\n if (key === accent) return \"var(--color-chart-1)\";\n const at = rank(key);\n return at < 3\n ? `var(--color-chart-${at + 2})`\n : `color-mix(in oklab, var(--color-foreground) ${SHADES[(at - 3) % SHADES.length]}%, var(--color-surface))`;\n };\n const shapeOf = (key: string | null) => (key === null || key === accent || rank(key) < 0 ? \"square\" : SHAPES[rank(key) % SHAPES.length]);\n\n // Column by column from the bottom-left corner. A cell's place is two numbers; CSS turns them into pixels.\n const place = (slot: number) => ({ x: Math.floor(slot / rows), y: rows - 1 - (slot % rows) });\n const filled = cells.filter((cell) => cell.key !== null).length;\n const bubble = reading ? (data.find((item) => item.key === reading.key) ?? null) : null;\n\n // The tooltip sits above the cell it describes and stays inside the chart; it glides between cells once showing.\n const shownBefore = useRef(false);\n useIsomorphicLayoutEffect(() => {\n const node = tip.current;\n const box = grid.current?.getBoundingClientRect();\n const host = board.current?.getBoundingClientRect();\n if (!reading || !node || !box?.width || !host) {\n shownBefore.current = false;\n return;\n }\n const side = (box.width - GAP * (columns - 1)) / columns;\n const { x, y } = place(reading.slot);\n const cx = box.left - host.left + x * (side + GAP) + side / 2;\n const cy = box.top - host.top + y * (side + GAP);\n const tw = node.offsetWidth;\n const th = node.offsetHeight;\n let top = cy - th - 10;\n if (top < 0) top = cy + side + 10;\n if (!shownBefore.current) node.style.transition = \"none\";\n node.style.translate = `${clamp(cx - tw / 2, 0, Math.max(0, host.width - tw))}px ${clamp(top, 0, Math.max(0, host.height - th))}px`;\n if (!shownBefore.current) {\n void node.offsetWidth;\n node.style.transition = \"\";\n }\n shownBefore.current = true;\n });\n\n const slotAt = (clientX: number, clientY: number) => {\n const box = grid.current?.getBoundingClientRect();\n if (!box?.width) return null;\n const side = (box.width - GAP * (columns - 1)) / columns;\n const col = clamp(Math.floor((clientX - box.left) / (side + GAP)), 0, columns - 1);\n const row = clamp(Math.floor((clientY - box.top) / (side + GAP)), 0, rows - 1);\n return col * rows + (rows - 1 - row);\n };\n const readingAt = (slot: number | null): Reading => {\n if (slot === null) return null;\n const cell = cells.find((item) => item.slot === slot);\n return cell?.key ? { key: cell.key, slot } : null;\n };\n const show = (next: Reading) => setReading((now) => (now?.slot === next?.slot ? now : next));\n const onPointerMove = (event: PointerEvent<HTMLDivElement>) => {\n if (event.pointerType === \"mouse\" || event.buttons) show(readingAt(slotAt(event.clientX, event.clientY)));\n };\n const onPointerDown = (event: PointerEvent<HTMLDivElement>) => {\n if (event.pointerType !== \"mouse\") event.currentTarget.setPointerCapture?.(event.pointerId);\n show(readingAt(slotAt(event.clientX, event.clientY)));\n };\n\n // Arrows walk the grid cell by cell; Page keys jump between categories.\n const onKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {\n if (empty) return;\n if (event.key === \"Escape\" && reading) {\n event.preventDefault();\n setReading(null);\n return;\n }\n const at = reading?.slot ?? 0;\n const col = Math.floor(at / rows);\n const row = at % rows;\n const blocks = counts\n .map((size, index) => ({ start: counts.slice(0, index).reduce((sum, value) => sum + value, 0), size }))\n .filter((block) => block.size > 0);\n const block = Math.max(0, blocks.findIndex((item) => at >= item.start && at < item.start + item.size));\n const moves: Record<string, () => number> = {\n ArrowUp: () => col * rows + Math.min(rows - 1, row + 1),\n ArrowDown: () => col * rows + Math.max(0, row - 1),\n ArrowRight: () => Math.min(columns - 1, col + 1) * rows + row,\n ArrowLeft: () => Math.max(0, col - 1) * rows + row,\n PageDown: () => blocks[Math.min(blocks.length - 1, block + 1)]!.start,\n PageUp: () => (at > blocks[block]!.start ? blocks[block]!.start : blocks[Math.max(0, block - 1)]!.start),\n Home: () => 0,\n End: () => filled - 1,\n };\n const move = moves[event.key];\n if (!move) return;\n event.preventDefault();\n show(readingAt(clamp(reading ? move() : 0, 0, Math.max(0, filled - 1))));\n };\n\n const suffix = unit ? ` ${unit}` : \"\";\n const format = (value: number, item: WaffleCategory) => `${formatValue ? formatValue(value, item) : GROUPED.format(value)}${suffix}`;\n const share = (item: WaffleCategory) => (total > 0 ? (Math.max(0, item.value) / total) * 100 : 0);\n const describe = (item: WaffleCategory) => `${item.label}, ${share(item).toFixed(decimals)}%, ${format(item.value, item)}`;\n const summary = empty\n ? `${label}. ${emptyLabel}.`\n : `${label}. ${data.map((item) => `${item.label} ${share(item).toFixed(decimals)}%`).join(\", \")}.`;\n\n return (\n <figure\n ref={refs}\n data-slot=\"waffle-chart\"\n aria-label={label}\n data-legend={legend || undefined}\n data-scrubbing={reading !== null || undefined}\n className={cn(\"waffle-chart box-border font-sans text-foreground\", className)}\n >\n <div ref={board} className=\"relative min-w-0\">\n <div\n ref={grid}\n data-slot=\"waffle-chart-grid\"\n data-active={active ? \"\" : undefined}\n role={empty ? undefined : \"slider\"}\n tabIndex={empty ? undefined : 0}\n aria-label={empty ? undefined : `${label}, explore cells`}\n aria-valuemin={empty ? undefined : 1}\n aria-valuemax={empty ? undefined : Math.max(1, filled)}\n aria-valuenow={empty ? undefined : (reading?.slot ?? 0) + 1}\n aria-valuetext={empty ? undefined : bubble ? describe(bubble) : summary}\n aria-hidden={empty || undefined}\n className=\"waffle-chart-grid\"\n style={{ \"--c\": columns, \"--r\": rows } as CSSProperties}\n onPointerMove={onPointerMove}\n onPointerDown={onPointerDown}\n onPointerUp={(event) => {\n if (event.pointerType !== \"mouse\") setReading(null);\n }}\n onPointerCancel={() => setReading(null)}\n onPointerLeave={(event) => {\n if (event.pointerType === \"mouse\") setReading(null);\n }}\n onKeyDown={onKeyDown}\n onBlur={() => setReading(null)}\n onFocus={(event) => {\n if (event.currentTarget.matches(\":focus-visible\") && !empty) setReading((now) => now ?? readingAt(0));\n }}\n >\n {cells.map((cell) => {\n const { x, y } = place(cell.slot);\n return (\n <span\n key={cell.id}\n aria-hidden=\"true\"\n data-cell={cell.slot}\n data-shown={shown || undefined}\n data-on={(cell.key !== null && cell.key === active) || undefined}\n data-cursor={reading?.slot === cell.slot || undefined}\n data-empty={cell.key === null || undefined}\n data-shape={shapeOf(cell.key)}\n data-hop={moved.has(cell.id) && shown ? (version % 2 ? \"a\" : \"b\") : undefined}\n className=\"waffle-chart-cell\"\n style={\n {\n \"--x\": x,\n \"--y\": y,\n \"--series\": colorOf(cell.key),\n \"--delay\": `${Math.round((cell.slot / count) * 320)}ms`,\n } as CSSProperties\n }\n />\n );\n })}\n </div>\n {empty ? null : (\n <div ref={tip} aria-hidden=\"true\" data-slot=\"waffle-chart-tooltip\" className=\"line-chart-tooltip\">\n <p className=\"m-0 flex min-w-0 items-center gap-2 text-sm\">\n <span data-shape={shapeOf(bubble?.key ?? null)} style={{ \"--series\": colorOf(bubble?.key ?? null) } as CSSProperties} className=\"chart-key-swatch\" />\n <span className=\"min-w-0 flex-1 truncate text-text-secondary\">{bubble?.label ?? \"\"}</span>\n <span className=\"ms-2 flex-none font-medium tabular-nums\">{bubble ? `${share(bubble).toFixed(decimals)}%` : \"\"}</span>\n </p>\n <p className=\"m-0 ms-4.5 whitespace-nowrap text-xs tabular-nums text-muted-foreground\">{bubble ? format(bubble.value, bubble) : \"\"}</p>\n </div>\n )}\n {empty ? <p className=\"m-0 grid min-h-40 place-items-center text-sm text-muted-foreground\">{emptyLabel}</p> : null}\n </div>\n {legend && !empty ? (\n <ul aria-label={`${label}, categories`} onPointerLeave={() => setPreview(null)} className=\"waffle-chart-legend\">\n {data.map((item) => (\n <li key={item.key}>\n <button\n type=\"button\"\n aria-pressed={pinned === item.key}\n data-on={active === item.key || undefined}\n data-dim={(active !== null && active !== item.key) || undefined}\n className=\"chart-key\"\n onClick={() => pin(pinned === item.key ? null : item.key)}\n onPointerEnter={(event) => {\n if (event.pointerType === \"mouse\") setPreview(item.key);\n }}\n onFocus={(event) => {\n if (event.currentTarget.matches(\":focus-visible\")) setPreview(item.key);\n }}\n onBlur={() => setPreview(null)}\n >\n <span aria-hidden=\"true\" data-shape={shapeOf(item.key)} style={{ \"--series\": colorOf(item.key) } as CSSProperties} className=\"chart-key-swatch\" />\n <span className=\"min-w-0 truncate\">{item.label}</span>\n <span aria-hidden=\"true\" className=\"min-w-13 text-end text-base font-medium tabular-nums text-foreground\">\n <Share value={share(item)} decimals={decimals} />\n </span>\n </button>\n </li>\n ))}\n </ul>\n ) : null}\n <p className=\"sr-only\">{summary}</p>\n <p className=\"sr-only\" aria-live=\"polite\" aria-atomic=\"true\">\n {bubble ? describe(bubble) : \"\"}\n </p>\n {empty ? null : (\n <div className=\"sr-only\">\n <table>\n <caption>{label}</caption>\n <thead>\n <tr>\n <th scope=\"col\">Category</th>\n <th scope=\"col\">Value</th>\n <th scope=\"col\">Share</th>\n <th scope=\"col\">Cells</th>\n </tr>\n </thead>\n <tbody>\n {data.map((item, index) => (\n <tr key={item.key}>\n <th scope=\"row\">{item.label}</th>\n <td>{format(item.value, item)}</td>\n <td>{share(item).toFixed(decimals)}%</td>\n <td>{counts[index]}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )}\n </figure>\n );\n});\n"],"mappings":"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"}