@tribepad/themis 1.0.0 → 1.0.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 (326) hide show
  1. package/dist/elements/Accordion/index.js +325 -66
  2. package/dist/elements/Accordion/index.js.map +1 -1
  3. package/dist/elements/Accordion/index.mjs +317 -3
  4. package/dist/elements/Accordion/index.mjs.map +1 -1
  5. package/dist/elements/Avatar/index.js +461 -45
  6. package/dist/elements/Avatar/index.js.map +1 -1
  7. package/dist/elements/Avatar/index.mjs +456 -3
  8. package/dist/elements/Avatar/index.mjs.map +1 -1
  9. package/dist/elements/Badge/index.js +238 -36
  10. package/dist/elements/Badge/index.js.map +1 -1
  11. package/dist/elements/Badge/index.mjs +234 -4
  12. package/dist/elements/Badge/index.mjs.map +1 -1
  13. package/dist/elements/Breadcrumbs/index.js +808 -39
  14. package/dist/elements/Breadcrumbs/index.js.map +1 -1
  15. package/dist/elements/Breadcrumbs/index.mjs +810 -7
  16. package/dist/elements/Breadcrumbs/index.mjs.map +1 -1
  17. package/dist/elements/Button/index.js +282 -19
  18. package/dist/elements/Button/index.js.map +1 -1
  19. package/dist/elements/Button/index.mjs +283 -4
  20. package/dist/elements/Button/index.mjs.map +1 -1
  21. package/dist/elements/ButtonGroup/index.js +229 -56
  22. package/dist/elements/ButtonGroup/index.js.map +1 -1
  23. package/dist/elements/ButtonGroup/index.mjs +222 -3
  24. package/dist/elements/ButtonGroup/index.mjs.map +1 -1
  25. package/dist/elements/Card/Card.d.ts.map +1 -1
  26. package/dist/elements/Card/index.js +563 -67
  27. package/dist/elements/Card/index.js.map +1 -1
  28. package/dist/elements/Card/index.mjs +560 -6
  29. package/dist/elements/Card/index.mjs.map +1 -1
  30. package/dist/elements/Carousel/index.js +782 -14
  31. package/dist/elements/Carousel/index.js.map +1 -1
  32. package/dist/elements/Carousel/index.mjs +786 -8
  33. package/dist/elements/Carousel/index.mjs.map +1 -1
  34. package/dist/elements/Chart/index.js +1833 -36
  35. package/dist/elements/Chart/index.js.map +1 -1
  36. package/dist/elements/Chart/index.mjs +1832 -4
  37. package/dist/elements/Chart/index.mjs.map +1 -1
  38. package/dist/elements/Checkbox/index.js +310 -39
  39. package/dist/elements/Checkbox/index.js.map +1 -1
  40. package/dist/elements/Checkbox/index.mjs +306 -4
  41. package/dist/elements/Checkbox/index.mjs.map +1 -1
  42. package/dist/elements/CheckboxGroup/index.js +445 -59
  43. package/dist/elements/CheckboxGroup/index.js.map +1 -1
  44. package/dist/elements/CheckboxGroup/index.mjs +439 -4
  45. package/dist/elements/CheckboxGroup/index.mjs.map +1 -1
  46. package/dist/elements/DatePicker/index.js +871 -89
  47. package/dist/elements/DatePicker/index.js.map +1 -1
  48. package/dist/elements/DatePicker/index.mjs +853 -4
  49. package/dist/elements/DatePicker/index.mjs.map +1 -1
  50. package/dist/elements/Dropdown/index.js +189 -35
  51. package/dist/elements/Dropdown/index.js.map +1 -1
  52. package/dist/elements/Dropdown/index.mjs +184 -2
  53. package/dist/elements/Dropdown/index.mjs.map +1 -1
  54. package/dist/elements/FileField/index.js +1532 -129
  55. package/dist/elements/FileField/index.js.map +1 -1
  56. package/dist/elements/FileField/index.mjs +1507 -7
  57. package/dist/elements/FileField/index.mjs.map +1 -1
  58. package/dist/elements/FormLayout/index.js +166 -11
  59. package/dist/elements/FormLayout/index.js.map +1 -1
  60. package/dist/elements/FormLayout/index.mjs +167 -2
  61. package/dist/elements/FormLayout/index.mjs.map +1 -1
  62. package/dist/elements/Modal/index.js +228 -46
  63. package/dist/elements/Modal/index.js.map +1 -1
  64. package/dist/elements/Modal/index.mjs +220 -1
  65. package/dist/elements/Modal/index.mjs.map +1 -1
  66. package/dist/elements/NumberField/index.js +659 -48
  67. package/dist/elements/NumberField/index.js.map +1 -1
  68. package/dist/elements/NumberField/index.mjs +654 -6
  69. package/dist/elements/NumberField/index.mjs.map +1 -1
  70. package/dist/elements/OTPInput/index.js +729 -6
  71. package/dist/elements/OTPInput/index.js.map +1 -1
  72. package/dist/elements/OTPInput/index.mjs +732 -2
  73. package/dist/elements/OTPInput/index.mjs.map +1 -1
  74. package/dist/elements/Panel/index.js +326 -27
  75. package/dist/elements/Panel/index.js.map +1 -1
  76. package/dist/elements/Panel/index.mjs +323 -2
  77. package/dist/elements/Panel/index.mjs.map +1 -1
  78. package/dist/elements/Progress/index.js +181 -22
  79. package/dist/elements/Progress/index.js.map +1 -1
  80. package/dist/elements/Progress/index.mjs +181 -3
  81. package/dist/elements/Progress/index.mjs.map +1 -1
  82. package/dist/elements/RadioGroup/index.js +358 -34
  83. package/dist/elements/RadioGroup/index.js.map +1 -1
  84. package/dist/elements/RadioGroup/index.mjs +359 -4
  85. package/dist/elements/RadioGroup/index.mjs.map +1 -1
  86. package/dist/elements/Resizable/components/ResizableHandle.d.ts +0 -8
  87. package/dist/elements/Resizable/components/ResizableHandle.d.ts.map +1 -1
  88. package/dist/elements/Resizable/components/ResizablePanel.d.ts +0 -8
  89. package/dist/elements/Resizable/components/ResizablePanel.d.ts.map +1 -1
  90. package/dist/elements/Resizable/components/ResizablePanelGroup.d.ts +0 -8
  91. package/dist/elements/Resizable/components/ResizablePanelGroup.d.ts.map +1 -1
  92. package/dist/elements/Resizable/components/ResizablePopover.d.ts +0 -8
  93. package/dist/elements/Resizable/components/ResizablePopover.d.ts.map +1 -1
  94. package/dist/elements/Resizable/index.js +1568 -51
  95. package/dist/elements/Resizable/index.js.map +1 -1
  96. package/dist/elements/Resizable/index.mjs +1566 -6
  97. package/dist/elements/Resizable/index.mjs.map +1 -1
  98. package/dist/elements/Select/index.js +580 -22
  99. package/dist/elements/Select/index.js.map +1 -1
  100. package/dist/elements/Select/index.mjs +582 -2
  101. package/dist/elements/Select/index.mjs.map +1 -1
  102. package/dist/elements/Skeleton/index.js +77 -15
  103. package/dist/elements/Skeleton/index.js.map +1 -1
  104. package/dist/elements/Skeleton/index.mjs +78 -3
  105. package/dist/elements/Skeleton/index.mjs.map +1 -1
  106. package/dist/elements/Switch/index.js +153 -21
  107. package/dist/elements/Switch/index.js.map +1 -1
  108. package/dist/elements/Switch/index.mjs +149 -5
  109. package/dist/elements/Switch/index.mjs.map +1 -1
  110. package/dist/elements/Table/index.js +589 -68
  111. package/dist/elements/Table/index.js.map +1 -1
  112. package/dist/elements/Table/index.mjs +578 -5
  113. package/dist/elements/Table/index.mjs.map +1 -1
  114. package/dist/elements/Tabs/index.js +328 -63
  115. package/dist/elements/Tabs/index.js.map +1 -1
  116. package/dist/elements/Tabs/index.mjs +320 -3
  117. package/dist/elements/Tabs/index.mjs.map +1 -1
  118. package/dist/elements/TextField/index.js +695 -51
  119. package/dist/elements/TextField/index.js.map +1 -1
  120. package/dist/elements/TextField/index.mjs +684 -7
  121. package/dist/elements/TextField/index.mjs.map +1 -1
  122. package/dist/elements/TimeField/index.js +244 -33
  123. package/dist/elements/TimeField/index.js.map +1 -1
  124. package/dist/elements/TimeField/index.mjs +238 -2
  125. package/dist/elements/TimeField/index.mjs.map +1 -1
  126. package/dist/elements/Toast/index.js +727 -48
  127. package/dist/elements/Toast/index.js.map +1 -1
  128. package/dist/elements/Toast/index.mjs +724 -5
  129. package/dist/elements/Toast/index.mjs.map +1 -1
  130. package/dist/elements/Tooltip/index.js +315 -49
  131. package/dist/elements/Tooltip/index.js.map +1 -1
  132. package/dist/elements/Tooltip/index.mjs +310 -4
  133. package/dist/elements/Tooltip/index.mjs.map +1 -1
  134. package/dist/elements/index.js +12417 -799
  135. package/dist/elements/index.js.map +1 -1
  136. package/dist/elements/index.mjs +12233 -40
  137. package/dist/elements/index.mjs.map +1 -1
  138. package/dist/index.js +12452 -825
  139. package/dist/index.js.map +1 -1
  140. package/dist/index.mjs +12262 -42
  141. package/dist/index.mjs.map +1 -1
  142. package/dist/schemas/index.js +47 -21
  143. package/dist/schemas/index.js.map +1 -1
  144. package/dist/schemas/index.mjs +47 -2
  145. package/dist/schemas/index.mjs.map +1 -1
  146. package/dist/styles/index.js +161 -147
  147. package/dist/styles/index.js.map +1 -1
  148. package/dist/styles/index.mjs +128 -2
  149. package/dist/styles/index.mjs.map +1 -1
  150. package/dist/utils/index.js +7 -7
  151. package/dist/utils/index.js.map +1 -1
  152. package/dist/utils/index.mjs +9 -2
  153. package/dist/utils/index.mjs.map +1 -1
  154. package/package.json +1 -1
  155. package/dist/Carousel-NTZX5TOW.js +0 -16
  156. package/dist/Carousel-NTZX5TOW.js.map +0 -1
  157. package/dist/Carousel-YH3DOQJU.mjs +0 -7
  158. package/dist/Carousel-YH3DOQJU.mjs.map +0 -1
  159. package/dist/chunk-2HIUTHMU.mjs +0 -234
  160. package/dist/chunk-2HIUTHMU.mjs.map +0 -1
  161. package/dist/chunk-34GTFTDO.js +0 -431
  162. package/dist/chunk-34GTFTDO.js.map +0 -1
  163. package/dist/chunk-3H7ASYR7.js +0 -250
  164. package/dist/chunk-3H7ASYR7.js.map +0 -1
  165. package/dist/chunk-3IEN7JOP.js +0 -316
  166. package/dist/chunk-3IEN7JOP.js.map +0 -1
  167. package/dist/chunk-3JHN4GAL.js +0 -326
  168. package/dist/chunk-3JHN4GAL.js.map +0 -1
  169. package/dist/chunk-3MJPASQU.js +0 -232
  170. package/dist/chunk-3MJPASQU.js.map +0 -1
  171. package/dist/chunk-3XD2JUL3.js +0 -572
  172. package/dist/chunk-3XD2JUL3.js.map +0 -1
  173. package/dist/chunk-3YOY2VJ6.js +0 -189
  174. package/dist/chunk-3YOY2VJ6.js.map +0 -1
  175. package/dist/chunk-4DU5JSXB.js +0 -408
  176. package/dist/chunk-4DU5JSXB.js.map +0 -1
  177. package/dist/chunk-4E4E2GSS.js +0 -352
  178. package/dist/chunk-4E4E2GSS.js.map +0 -1
  179. package/dist/chunk-4NHAP4AN.mjs +0 -3
  180. package/dist/chunk-4NHAP4AN.mjs.map +0 -1
  181. package/dist/chunk-4S33J5NY.mjs +0 -415
  182. package/dist/chunk-4S33J5NY.mjs.map +0 -1
  183. package/dist/chunk-5SMGRT3G.mjs +0 -354
  184. package/dist/chunk-5SMGRT3G.mjs.map +0 -1
  185. package/dist/chunk-5SVLJN2C.mjs +0 -22
  186. package/dist/chunk-5SVLJN2C.mjs.map +0 -1
  187. package/dist/chunk-66WTU4EB.mjs +0 -299
  188. package/dist/chunk-66WTU4EB.mjs.map +0 -1
  189. package/dist/chunk-6S25NMOT.mjs +0 -335
  190. package/dist/chunk-6S25NMOT.mjs.map +0 -1
  191. package/dist/chunk-6SP7UB3D.js +0 -4
  192. package/dist/chunk-6SP7UB3D.js.map +0 -1
  193. package/dist/chunk-6TYWWQHM.mjs +0 -565
  194. package/dist/chunk-6TYWWQHM.mjs.map +0 -1
  195. package/dist/chunk-A3YUJA6W.mjs +0 -384
  196. package/dist/chunk-A3YUJA6W.mjs.map +0 -1
  197. package/dist/chunk-A6KEDVUR.js +0 -61
  198. package/dist/chunk-A6KEDVUR.js.map +0 -1
  199. package/dist/chunk-A77RUEWL.js +0 -730
  200. package/dist/chunk-A77RUEWL.js.map +0 -1
  201. package/dist/chunk-AA4IKMPE.mjs +0 -3
  202. package/dist/chunk-AA4IKMPE.mjs.map +0 -1
  203. package/dist/chunk-AKIA6GW6.mjs +0 -163
  204. package/dist/chunk-AKIA6GW6.mjs.map +0 -1
  205. package/dist/chunk-AL6P275L.mjs +0 -435
  206. package/dist/chunk-AL6P275L.mjs.map +0 -1
  207. package/dist/chunk-AZ3RJYTB.js +0 -37
  208. package/dist/chunk-AZ3RJYTB.js.map +0 -1
  209. package/dist/chunk-B5Q4UPL6.js +0 -32
  210. package/dist/chunk-B5Q4UPL6.js.map +0 -1
  211. package/dist/chunk-B6DHPMDP.mjs +0 -335
  212. package/dist/chunk-B6DHPMDP.mjs.map +0 -1
  213. package/dist/chunk-BDXKKMBZ.mjs +0 -184
  214. package/dist/chunk-BDXKKMBZ.mjs.map +0 -1
  215. package/dist/chunk-BL6E2DLZ.mjs +0 -52
  216. package/dist/chunk-BL6E2DLZ.mjs.map +0 -1
  217. package/dist/chunk-CGFDS4XS.mjs +0 -121
  218. package/dist/chunk-CGFDS4XS.mjs.map +0 -1
  219. package/dist/chunk-CJIW5TKI.js +0 -139
  220. package/dist/chunk-CJIW5TKI.js.map +0 -1
  221. package/dist/chunk-CKNISJOQ.js +0 -314
  222. package/dist/chunk-CKNISJOQ.js.map +0 -1
  223. package/dist/chunk-D6CBOECS.mjs +0 -1757
  224. package/dist/chunk-D6CBOECS.mjs.map +0 -1
  225. package/dist/chunk-DDWEVC2S.js +0 -166
  226. package/dist/chunk-DDWEVC2S.js.map +0 -1
  227. package/dist/chunk-DZ556D2F.mjs +0 -176
  228. package/dist/chunk-DZ556D2F.mjs.map +0 -1
  229. package/dist/chunk-E2KQFV3O.mjs +0 -10
  230. package/dist/chunk-E2KQFV3O.mjs.map +0 -1
  231. package/dist/chunk-EMMLADSC.js +0 -126
  232. package/dist/chunk-EMMLADSC.js.map +0 -1
  233. package/dist/chunk-EP4WOI5D.mjs +0 -926
  234. package/dist/chunk-EP4WOI5D.mjs.map +0 -1
  235. package/dist/chunk-FJRXLJC2.mjs +0 -160
  236. package/dist/chunk-FJRXLJC2.mjs.map +0 -1
  237. package/dist/chunk-FKQI434R.js +0 -345
  238. package/dist/chunk-FKQI434R.js.map +0 -1
  239. package/dist/chunk-FPKEAJRZ.mjs +0 -100
  240. package/dist/chunk-FPKEAJRZ.mjs.map +0 -1
  241. package/dist/chunk-FWQYB22U.js +0 -183
  242. package/dist/chunk-FWQYB22U.js.map +0 -1
  243. package/dist/chunk-GD5GHTMA.js +0 -189
  244. package/dist/chunk-GD5GHTMA.js.map +0 -1
  245. package/dist/chunk-GE5XTSDZ.js +0 -447
  246. package/dist/chunk-GE5XTSDZ.js.map +0 -1
  247. package/dist/chunk-GVE47ZAX.mjs +0 -32
  248. package/dist/chunk-GVE47ZAX.mjs.map +0 -1
  249. package/dist/chunk-HK46BT5U.mjs +0 -18
  250. package/dist/chunk-HK46BT5U.mjs.map +0 -1
  251. package/dist/chunk-HQVRMR6N.js +0 -365
  252. package/dist/chunk-HQVRMR6N.js.map +0 -1
  253. package/dist/chunk-HSGBJPJO.mjs +0 -398
  254. package/dist/chunk-HSGBJPJO.mjs.map +0 -1
  255. package/dist/chunk-I3AUTOMZ.mjs +0 -125
  256. package/dist/chunk-I3AUTOMZ.mjs.map +0 -1
  257. package/dist/chunk-IEI5LD5C.mjs +0 -1161
  258. package/dist/chunk-IEI5LD5C.mjs.map +0 -1
  259. package/dist/chunk-IIPTC2X7.mjs +0 -118
  260. package/dist/chunk-IIPTC2X7.mjs.map +0 -1
  261. package/dist/chunk-J7TLHF2Q.js +0 -4
  262. package/dist/chunk-J7TLHF2Q.js.map +0 -1
  263. package/dist/chunk-JJOWXFXQ.mjs +0 -765
  264. package/dist/chunk-JJOWXFXQ.mjs.map +0 -1
  265. package/dist/chunk-JPTSS2OA.mjs +0 -3
  266. package/dist/chunk-JPTSS2OA.mjs.map +0 -1
  267. package/dist/chunk-KFXXRLTP.js +0 -396
  268. package/dist/chunk-KFXXRLTP.js.map +0 -1
  269. package/dist/chunk-KPRRBSG6.mjs +0 -272
  270. package/dist/chunk-KPRRBSG6.mjs.map +0 -1
  271. package/dist/chunk-NFSBGRDB.mjs +0 -57
  272. package/dist/chunk-NFSBGRDB.mjs.map +0 -1
  273. package/dist/chunk-NGJVCFTM.js +0 -219
  274. package/dist/chunk-NGJVCFTM.js.map +0 -1
  275. package/dist/chunk-NSQ6MZJ6.mjs +0 -728
  276. package/dist/chunk-NSQ6MZJ6.mjs.map +0 -1
  277. package/dist/chunk-NYQYHT76.mjs +0 -296
  278. package/dist/chunk-NYQYHT76.mjs.map +0 -1
  279. package/dist/chunk-OLJJGI5B.js +0 -1193
  280. package/dist/chunk-OLJJGI5B.js.map +0 -1
  281. package/dist/chunk-Q3572X2J.js +0 -292
  282. package/dist/chunk-Q3572X2J.js.map +0 -1
  283. package/dist/chunk-QH7N7D4I.mjs +0 -210
  284. package/dist/chunk-QH7N7D4I.mjs.map +0 -1
  285. package/dist/chunk-R7XUIV25.js +0 -466
  286. package/dist/chunk-R7XUIV25.js.map +0 -1
  287. package/dist/chunk-RFFO4KPM.js +0 -135
  288. package/dist/chunk-RFFO4KPM.js.map +0 -1
  289. package/dist/chunk-RFX7QKA7.mjs +0 -180
  290. package/dist/chunk-RFX7QKA7.mjs.map +0 -1
  291. package/dist/chunk-SN5LFAP3.js +0 -940
  292. package/dist/chunk-SN5LFAP3.js.map +0 -1
  293. package/dist/chunk-T4COXKQ3.js +0 -24
  294. package/dist/chunk-T4COXKQ3.js.map +0 -1
  295. package/dist/chunk-TS54QM27.js +0 -125
  296. package/dist/chunk-TS54QM27.js.map +0 -1
  297. package/dist/chunk-UE2S4PCX.mjs +0 -220
  298. package/dist/chunk-UE2S4PCX.mjs.map +0 -1
  299. package/dist/chunk-UTW3QX2A.mjs +0 -282
  300. package/dist/chunk-UTW3QX2A.mjs.map +0 -1
  301. package/dist/chunk-V74LGMAE.js +0 -1767
  302. package/dist/chunk-V74LGMAE.js.map +0 -1
  303. package/dist/chunk-VIREG536.js +0 -12
  304. package/dist/chunk-VIREG536.js.map +0 -1
  305. package/dist/chunk-VY7M7346.js +0 -4
  306. package/dist/chunk-VY7M7346.js.map +0 -1
  307. package/dist/chunk-W3TJOO7H.mjs +0 -319
  308. package/dist/chunk-W3TJOO7H.mjs.map +0 -1
  309. package/dist/chunk-WIUOB36M.js +0 -54
  310. package/dist/chunk-WIUOB36M.js.map +0 -1
  311. package/dist/chunk-WJGLM4CY.js +0 -291
  312. package/dist/chunk-WJGLM4CY.js.map +0 -1
  313. package/dist/chunk-WNURH5OO.mjs +0 -453
  314. package/dist/chunk-WNURH5OO.mjs.map +0 -1
  315. package/dist/chunk-X25TNRSD.mjs +0 -364
  316. package/dist/chunk-X25TNRSD.mjs.map +0 -1
  317. package/dist/chunk-Y3GT7ETK.js +0 -108
  318. package/dist/chunk-Y3GT7ETK.js.map +0 -1
  319. package/dist/chunk-Z4FRNOF6.mjs +0 -115
  320. package/dist/chunk-Z4FRNOF6.mjs.map +0 -1
  321. package/dist/chunk-ZMYLD3BN.js +0 -166
  322. package/dist/chunk-ZMYLD3BN.js.map +0 -1
  323. package/dist/chunk-ZP2KV6EX.js +0 -815
  324. package/dist/chunk-ZP2KV6EX.js.map +0 -1
  325. package/dist/chunk-ZVKXFELU.js +0 -366
  326. package/dist/chunk-ZVKXFELU.js.map +0 -1
@@ -1,6 +1,1834 @@
1
- export { Chart, ChartPropsSchema, ChartTypeSchema, ChartVariantSchema, DataPointSchema, DataSeriesSchema, LinePatternSchema, chartVariants, dataPointVariants } from '../../chunk-D6CBOECS.mjs';
2
- import '../../chunk-DZ556D2F.mjs';
3
- import '../../chunk-E2KQFV3O.mjs';
4
- import '../../chunk-5SVLJN2C.mjs';
1
+ "use client";
2
+ import { createContext, forwardRef, useRef, useId, memo, useState, useMemo, useCallback, useEffect, useContext } from 'react';
3
+ import { clsx } from 'clsx';
4
+ import { twMerge } from 'tailwind-merge';
5
+ import { cva } from 'class-variance-authority';
6
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
7
+ import { VisuallyHidden, useFocusRing } from 'react-aria';
8
+ import 'react-aria-components';
9
+ import { z } from 'zod';
10
+
11
+ // src/elements/Chart/Chart.tsx
12
+ function cn(...inputs) {
13
+ return twMerge(clsx(inputs));
14
+ }
15
+ var chartVariants = cva(
16
+ // Base classes
17
+ "relative w-full overflow-hidden rounded-lg border",
18
+ {
19
+ variants: {
20
+ variant: {
21
+ default: "border-[var(--border)] bg-[var(--content-background)]",
22
+ muted: "border-[var(--accent-background)] bg-[var(--accent-background)]/50"
23
+ }
24
+ },
25
+ defaultVariants: {
26
+ variant: "default"
27
+ }
28
+ }
29
+ );
30
+ var dataPointVariants = cva(
31
+ // Base classes
32
+ "cursor-pointer focus-visible:outline-none",
33
+ {
34
+ variants: {
35
+ type: {
36
+ bar: "",
37
+ // Fill color set dynamically per series
38
+ line: "stroke-2"
39
+ // Stroke color set dynamically per series
40
+ },
41
+ state: {
42
+ default: "",
43
+ focused: "",
44
+ // Focus ring provides visual feedback, no scale needed for data points
45
+ selected: "opacity-80"
46
+ }
47
+ },
48
+ defaultVariants: {
49
+ type: "bar",
50
+ state: "default"
51
+ }
52
+ }
53
+ );
54
+ cva(
55
+ // Base classes
56
+ "pointer-events-none",
57
+ {
58
+ variants: {
59
+ visible: {
60
+ true: "opacity-100",
61
+ false: "opacity-0"
62
+ }
63
+ },
64
+ defaultVariants: {
65
+ visible: false
66
+ }
67
+ }
68
+ );
69
+ var legendVariants = cva(
70
+ // Base classes - horizontal flex layout below chart
71
+ "flex flex-wrap items-center justify-center gap-4 px-4 py-2",
72
+ {
73
+ variants: {
74
+ visible: {
75
+ true: "",
76
+ false: "hidden"
77
+ }
78
+ },
79
+ defaultVariants: {
80
+ visible: true
81
+ }
82
+ }
83
+ );
84
+
85
+ // src/elements/Chart/chart.constants.ts
86
+ var CHART_ASPECT_RATIO = 9 / 16;
87
+ var CHART_PADDING = {
88
+ top: 20,
89
+ // Space for title overflow
90
+ right: 20,
91
+ // Space for y-axis label
92
+ bottom: 60,
93
+ // Space for x-axis labels + legend
94
+ left: 60
95
+ // Space for y-axis labels
96
+ };
97
+ var MIN_CHART_WIDTH = 300;
98
+ var MIN_CHART_HEIGHT = 169;
99
+ var BAR_GROUP_PADDING = 0.2;
100
+ var BAR_PADDING = 0.1;
101
+ var MIN_BAR_WIDTH = 20;
102
+ var BAR_RADIUS = 4;
103
+ var LINE_STROKE_WIDTH = 2;
104
+ var POINT_RADIUS = 6;
105
+ var LINE_PATTERNS = {
106
+ solid: "none",
107
+ dashed: "8 4",
108
+ // 8px dash, 4px gap
109
+ dotted: "2 4"
110
+ // 2px dot, 4px gap
111
+ };
112
+ var GRID_COLOR = "var(--border)";
113
+ var GRID_STROKE_WIDTH = 1;
114
+ var GRID_STROKE_OPACITY = 0.7;
115
+ var AXIS_COLOR = "var(--border)";
116
+ var AXIS_STROKE_WIDTH = 1;
117
+ var AXIS_TICK_SIZE = 6;
118
+ var AXIS_LABEL_FONT_SIZE = 12;
119
+ var MIN_TOUCH_TARGET = 44;
120
+ var FOCUS_RING_OFFSET = 4;
121
+ var FOCUS_RING_WIDTH = 2;
122
+ var DEFAULT_SERIES_COLORS = [
123
+ "var(--chart-1, hsl(221 83% 53%))",
124
+ // Series 0 - Blue
125
+ "var(--chart-2, hsl(142 71% 45%))",
126
+ // Series 1 - Green
127
+ "var(--chart-3, hsl(38 92% 50%))",
128
+ // Series 2 - Orange
129
+ "var(--chart-4, hsl(262 83% 58%))",
130
+ // Series 3 - Purple
131
+ "var(--chart-5, hsl(0 84% 60%))"
132
+ // Series 4 - Red
133
+ ];
134
+ function niceNum(range, round) {
135
+ const exponent = Math.floor(Math.log10(range));
136
+ const fraction = range / Math.pow(10, exponent);
137
+ let niceFraction;
138
+ if (round) {
139
+ if (fraction < 1.5) niceFraction = 1;
140
+ else if (fraction < 3) niceFraction = 2;
141
+ else if (fraction < 7) niceFraction = 5;
142
+ else niceFraction = 10;
143
+ } else {
144
+ if (fraction <= 1) niceFraction = 1;
145
+ else if (fraction <= 2) niceFraction = 2;
146
+ else if (fraction <= 5) niceFraction = 5;
147
+ else niceFraction = 10;
148
+ }
149
+ return niceFraction * Math.pow(10, exponent);
150
+ }
151
+ function calculateYAxisTicks(minValue, maxValue, tickCount = 5) {
152
+ if (minValue === maxValue) {
153
+ if (minValue === 0) {
154
+ return [0, 25, 50, 75, 100];
155
+ }
156
+ minValue = minValue * 0.9;
157
+ maxValue = maxValue * 1.1;
158
+ }
159
+ const range = niceNum(maxValue - minValue, false);
160
+ const tickSpacing = niceNum(range / (tickCount - 1), true);
161
+ const niceMin = Math.floor(minValue / tickSpacing) * tickSpacing;
162
+ const niceMax = Math.ceil(maxValue / tickSpacing) * tickSpacing;
163
+ const ticks = [];
164
+ for (let tick = niceMin; tick <= niceMax; tick += tickSpacing) {
165
+ ticks.push(tick);
166
+ }
167
+ return ticks;
168
+ }
169
+ function calculateBarLayout(plotWidth, labelCount, seriesCount, plotX = 0) {
170
+ const groupWidth = plotWidth / labelCount;
171
+ const availableWidth = groupWidth * (1 - BAR_GROUP_PADDING);
172
+ const barWidth = Math.max(
173
+ MIN_BAR_WIDTH,
174
+ availableWidth / seriesCount * (1 - BAR_PADDING)
175
+ );
176
+ const getBarX = (labelIdx, seriesIdx) => {
177
+ const groupCenter = plotX + groupWidth * labelIdx + groupWidth / 2;
178
+ const totalBarsWidth = seriesCount * barWidth + (seriesCount - 1) * (BAR_PADDING * barWidth);
179
+ const barsStart = groupCenter - totalBarsWidth / 2;
180
+ return barsStart + seriesIdx * (barWidth + BAR_PADDING * barWidth);
181
+ };
182
+ return { barWidth, groupWidth, getBarX };
183
+ }
184
+
185
+ // src/elements/Chart/useChartDimensions.ts
186
+ function useChartDimensions(containerRef) {
187
+ const [dimensions, setDimensions] = useState({
188
+ width: MIN_CHART_WIDTH,
189
+ height: MIN_CHART_HEIGHT,
190
+ padding: CHART_PADDING,
191
+ plotArea: {
192
+ x: CHART_PADDING.left,
193
+ y: CHART_PADDING.top,
194
+ width: MIN_CHART_WIDTH - CHART_PADDING.left - CHART_PADDING.right,
195
+ height: MIN_CHART_HEIGHT - CHART_PADDING.top - CHART_PADDING.bottom
196
+ }
197
+ });
198
+ useEffect(() => {
199
+ const container = containerRef.current;
200
+ if (!container) return;
201
+ const updateDimensions = (width) => {
202
+ const constrainedWidth = Math.max(width, MIN_CHART_WIDTH);
203
+ const height = Math.max(
204
+ constrainedWidth * CHART_ASPECT_RATIO,
205
+ MIN_CHART_HEIGHT
206
+ );
207
+ const plotArea = {
208
+ x: CHART_PADDING.left,
209
+ y: CHART_PADDING.top,
210
+ width: constrainedWidth - CHART_PADDING.left - CHART_PADDING.right,
211
+ height: height - CHART_PADDING.top - CHART_PADDING.bottom
212
+ };
213
+ setDimensions({
214
+ width: constrainedWidth,
215
+ height,
216
+ padding: CHART_PADDING,
217
+ plotArea
218
+ });
219
+ };
220
+ const observer = new ResizeObserver((entries) => {
221
+ const entry = entries[0];
222
+ if (!entry) return;
223
+ updateDimensions(entry.contentRect.width);
224
+ });
225
+ updateDimensions(container.getBoundingClientRect().width);
226
+ observer.observe(container);
227
+ return () => {
228
+ observer.disconnect();
229
+ };
230
+ }, [containerRef]);
231
+ return dimensions;
232
+ }
233
+ function useRovingTabIndex2D(options) {
234
+ const {
235
+ rows,
236
+ cols,
237
+ initialRow = 0,
238
+ initialCol = 0,
239
+ wrap = false,
240
+ onFocusChange
241
+ } = options;
242
+ const [focusedRow, setFocusedRow] = useState(initialRow);
243
+ const [focusedCol, setFocusedCol] = useState(initialCol);
244
+ const getColCount = useCallback(
245
+ (row) => {
246
+ return typeof cols === "function" ? cols(row) : cols;
247
+ },
248
+ [cols]
249
+ );
250
+ const notifyFocusChange = useCallback(
251
+ (row, col) => {
252
+ onFocusChange?.(row, col);
253
+ },
254
+ [onFocusChange]
255
+ );
256
+ const moveRight = useCallback(() => {
257
+ setFocusedCol((currentCol) => {
258
+ const maxCol = getColCount(focusedRow) - 1;
259
+ let newCol = currentCol + 1;
260
+ if (newCol > maxCol) {
261
+ newCol = wrap ? 0 : maxCol;
262
+ }
263
+ if (newCol !== currentCol) {
264
+ notifyFocusChange(focusedRow, newCol);
265
+ }
266
+ return newCol;
267
+ });
268
+ }, [focusedRow, getColCount, wrap, notifyFocusChange]);
269
+ const moveLeft = useCallback(() => {
270
+ setFocusedCol((currentCol) => {
271
+ let newCol = currentCol - 1;
272
+ const maxCol = getColCount(focusedRow) - 1;
273
+ if (newCol < 0) {
274
+ newCol = wrap ? maxCol : 0;
275
+ }
276
+ if (newCol !== currentCol) {
277
+ notifyFocusChange(focusedRow, newCol);
278
+ }
279
+ return newCol;
280
+ });
281
+ }, [focusedRow, getColCount, wrap, notifyFocusChange]);
282
+ const moveDown = useCallback(() => {
283
+ setFocusedRow((currentRow) => {
284
+ let newRow = currentRow + 1;
285
+ if (newRow >= rows) {
286
+ newRow = wrap ? 0 : rows - 1;
287
+ }
288
+ if (newRow !== currentRow) {
289
+ const newMaxCol = getColCount(newRow) - 1;
290
+ setFocusedCol((col) => {
291
+ const constrainedCol = Math.min(col, newMaxCol);
292
+ notifyFocusChange(newRow, constrainedCol);
293
+ return constrainedCol;
294
+ });
295
+ }
296
+ return newRow;
297
+ });
298
+ }, [rows, getColCount, wrap, notifyFocusChange]);
299
+ const moveUp = useCallback(() => {
300
+ setFocusedRow((currentRow) => {
301
+ let newRow = currentRow - 1;
302
+ if (newRow < 0) {
303
+ newRow = wrap ? rows - 1 : 0;
304
+ }
305
+ if (newRow !== currentRow) {
306
+ const newMaxCol = getColCount(newRow) - 1;
307
+ setFocusedCol((col) => {
308
+ const constrainedCol = Math.min(col, newMaxCol);
309
+ notifyFocusChange(newRow, constrainedCol);
310
+ return constrainedCol;
311
+ });
312
+ }
313
+ return newRow;
314
+ });
315
+ }, [rows, getColCount, wrap, notifyFocusChange]);
316
+ const moveToStart = useCallback(() => {
317
+ setFocusedCol(0);
318
+ notifyFocusChange(focusedRow, 0);
319
+ }, [focusedRow, notifyFocusChange]);
320
+ const moveToEnd = useCallback(() => {
321
+ const maxCol = getColCount(focusedRow) - 1;
322
+ setFocusedCol(maxCol);
323
+ notifyFocusChange(focusedRow, maxCol);
324
+ }, [focusedRow, getColCount, notifyFocusChange]);
325
+ const setFocus = useCallback(
326
+ (row, col) => {
327
+ const constrainedRow = Math.max(0, Math.min(row, rows - 1));
328
+ const maxCol = getColCount(constrainedRow) - 1;
329
+ const constrainedCol = Math.max(0, Math.min(col, maxCol));
330
+ setFocusedRow(constrainedRow);
331
+ setFocusedCol(constrainedCol);
332
+ notifyFocusChange(constrainedRow, constrainedCol);
333
+ },
334
+ [rows, getColCount, notifyFocusChange]
335
+ );
336
+ const getTabIndex = useCallback(
337
+ (row, col) => {
338
+ return row === focusedRow && col === focusedCol ? 0 : -1;
339
+ },
340
+ [focusedRow, focusedCol]
341
+ );
342
+ return useMemo(
343
+ () => ({
344
+ focusedRow,
345
+ focusedCol,
346
+ moveRight,
347
+ moveLeft,
348
+ moveDown,
349
+ moveUp,
350
+ moveToStart,
351
+ moveToEnd,
352
+ setFocus,
353
+ getTabIndex
354
+ }),
355
+ [
356
+ focusedRow,
357
+ focusedCol,
358
+ moveRight,
359
+ moveLeft,
360
+ moveDown,
361
+ moveUp,
362
+ moveToStart,
363
+ moveToEnd,
364
+ setFocus,
365
+ getTabIndex
366
+ ]
367
+ );
368
+ }
369
+ var ChartContext = createContext(null);
370
+ function useChartContext() {
371
+ const context = useContext(ChartContext);
372
+ if (!context) {
373
+ throw new Error("useChartContext must be used within a ChartProvider");
374
+ }
375
+ return context;
376
+ }
377
+ function getAllLabels(data) {
378
+ const labelSet = /* @__PURE__ */ new Set();
379
+ data.forEach((series) => {
380
+ series.data.forEach((point) => {
381
+ labelSet.add(point.label);
382
+ });
383
+ });
384
+ return Array.from(labelSet);
385
+ }
386
+ function calculateScales(data, dimensions, startAtZero) {
387
+ const allLabels = getAllLabels(data);
388
+ const { plotArea } = dimensions;
389
+ let dataMin = Infinity;
390
+ let dataMax = -Infinity;
391
+ data.forEach((series) => {
392
+ series.data.forEach((point) => {
393
+ dataMin = Math.min(dataMin, point.value);
394
+ dataMax = Math.max(dataMax, point.value);
395
+ });
396
+ });
397
+ if (!isFinite(dataMin) || !isFinite(dataMax)) {
398
+ dataMin = 0;
399
+ dataMax = 100;
400
+ }
401
+ const yMin = startAtZero ? Math.min(0, dataMin) : dataMin;
402
+ const yTicks = calculateYAxisTicks(yMin, dataMax);
403
+ const yMax = yTicks[yTicks.length - 1] || dataMax;
404
+ const adjustedYMin = yTicks[0] || yMin;
405
+ const xScale = (labelIndex) => {
406
+ if (allLabels.length <= 1) return plotArea.x + plotArea.width / 2;
407
+ const step = plotArea.width / allLabels.length;
408
+ return plotArea.x + step * labelIndex + step / 2;
409
+ };
410
+ const yRange = yMax - adjustedYMin || 1;
411
+ const yScale = (value) => {
412
+ const normalized = (value - adjustedYMin) / yRange;
413
+ return plotArea.y + plotArea.height * (1 - normalized);
414
+ };
415
+ return {
416
+ allLabels,
417
+ xScale,
418
+ yScale,
419
+ yMin: adjustedYMin,
420
+ yMax,
421
+ yTicks
422
+ };
423
+ }
424
+ function ChartProvider({
425
+ children,
426
+ containerRef,
427
+ data,
428
+ type,
429
+ title,
430
+ description,
431
+ xAxisLabel,
432
+ yAxisLabel,
433
+ showTooltip,
434
+ showGrid,
435
+ showLegend,
436
+ announceTrends,
437
+ trendUpFormat,
438
+ trendDownFormat,
439
+ startAtZero,
440
+ reducedMotion = true,
441
+ onPointFocus: onPointFocusProp,
442
+ onPointSelect: onPointSelectProp
443
+ }) {
444
+ const [selectedPoint, setSelectedPoint] = useState(null);
445
+ const [announcement, setAnnouncement] = useState("");
446
+ const [isTooltipVisible, setIsTooltipVisible] = useState(false);
447
+ const [tooltipPoint, setTooltipPoint] = useState(null);
448
+ const [currentTrend, setCurrentTrend] = useState(null);
449
+ const [currentTrendDirection, setCurrentTrendDirection] = useState(null);
450
+ const dimensions = useChartDimensions(containerRef);
451
+ const scales = useMemo(
452
+ () => calculateScales(data, dimensions, startAtZero),
453
+ [data, dimensions, startAtZero]
454
+ );
455
+ void useMemo(
456
+ () => Math.max(...data.map((s) => s.data.length)),
457
+ [data]
458
+ );
459
+ const formatTrendText = useCallback(
460
+ (percent, isUp) => {
461
+ const format = isUp ? trendUpFormat : trendDownFormat;
462
+ return format.replace("{percent}", String(percent));
463
+ },
464
+ [trendUpFormat, trendDownFormat]
465
+ );
466
+ const getTrendInfo = (seriesIndex, pointIndex) => {
467
+ if (!announceTrends || pointIndex <= 0) return null;
468
+ const series = data[seriesIndex];
469
+ if (!series) return null;
470
+ const currentPoint = series.data[pointIndex];
471
+ const previousPoint = series.data[pointIndex - 1];
472
+ if (!currentPoint || !previousPoint || previousPoint.value === 0) {
473
+ return null;
474
+ }
475
+ const change = (currentPoint.value - previousPoint.value) / Math.abs(previousPoint.value) * 100;
476
+ const isUp = change >= 0;
477
+ const percent = Math.abs(Math.round(change));
478
+ return {
479
+ text: formatTrendText(percent, isUp),
480
+ direction: isUp ? "up" : "down"
481
+ };
482
+ };
483
+ const roving = useRovingTabIndex2D({
484
+ rows: data.length,
485
+ cols: (row) => data[row]?.data.length || 0,
486
+ onFocusChange: (seriesIndex, pointIndex) => {
487
+ const series = data[seriesIndex];
488
+ const point = series?.data[pointIndex];
489
+ if (!series || !point) return;
490
+ const trendInfo = getTrendInfo(seriesIndex, pointIndex);
491
+ setCurrentTrend(trendInfo?.text ?? null);
492
+ setCurrentTrendDirection(trendInfo?.direction ?? null);
493
+ const position = `${pointIndex + 1} of ${series.data.length}`;
494
+ const seriesInfo = data.length > 1 ? `${series.name}: ` : "";
495
+ const formattedValue = formatValue(point.value);
496
+ const announcementTrend = trendInfo ? `, ${trendInfo.text}` : "";
497
+ setAnnouncement(
498
+ `${seriesInfo}${point.label}, ${formattedValue}${announcementTrend}. ${position}.`
499
+ );
500
+ if (showTooltip) {
501
+ setIsTooltipVisible(true);
502
+ setTooltipPoint({ series: seriesIndex, point: pointIndex });
503
+ }
504
+ onPointFocusProp?.(point, seriesIndex, pointIndex);
505
+ }
506
+ });
507
+ const formatValue = useCallback((value2) => {
508
+ const locale = typeof navigator !== "undefined" ? navigator.language : "en-US";
509
+ return new Intl.NumberFormat(locale).format(value2);
510
+ }, []);
511
+ const calculateTrendForPoint = useCallback(
512
+ (seriesIndex, pointIndex) => {
513
+ if (!announceTrends) return null;
514
+ if (pointIndex <= 0) return null;
515
+ const series = data[seriesIndex];
516
+ if (!series) return null;
517
+ const currentPoint = series.data[pointIndex];
518
+ const previousPoint = series.data[pointIndex - 1];
519
+ if (!currentPoint || !previousPoint || previousPoint.value === 0) {
520
+ return null;
521
+ }
522
+ const change = (currentPoint.value - previousPoint.value) / Math.abs(previousPoint.value) * 100;
523
+ const isUp = change >= 0;
524
+ const percent = Math.abs(Math.round(change));
525
+ return {
526
+ text: formatTrendText(percent, isUp),
527
+ direction: isUp ? "up" : "down"
528
+ };
529
+ },
530
+ [data, announceTrends, formatTrendText]
531
+ );
532
+ const getSeriesColor = useCallback((seriesIndex) => {
533
+ return DEFAULT_SERIES_COLORS[seriesIndex % DEFAULT_SERIES_COLORS.length] ?? "var(--chart-1)";
534
+ }, []);
535
+ const getPointCoordinates = useCallback(
536
+ (seriesIndex, pointIndex) => {
537
+ const series = data[seriesIndex];
538
+ const point = series?.data[pointIndex];
539
+ if (!series || !point) return null;
540
+ const labelIndex = scales.allLabels.indexOf(point.label);
541
+ if (labelIndex === -1) return null;
542
+ return {
543
+ x: scales.xScale(labelIndex),
544
+ y: scales.yScale(point.value)
545
+ };
546
+ },
547
+ [data, scales]
548
+ );
549
+ const setFocus = useCallback(
550
+ (series, point) => {
551
+ roving.setFocus(series, point);
552
+ },
553
+ [roving]
554
+ );
555
+ const selectPoint = useCallback(
556
+ (series, point) => {
557
+ setSelectedPoint({ series, point });
558
+ const seriesData = data[series];
559
+ const pointData = seriesData?.data[point];
560
+ if (seriesData && pointData) {
561
+ onPointSelectProp?.(pointData, series, point);
562
+ }
563
+ },
564
+ [data, onPointSelectProp]
565
+ );
566
+ const clearSelection = useCallback(() => {
567
+ setSelectedPoint(null);
568
+ }, []);
569
+ const announce = useCallback((message) => {
570
+ setAnnouncement(message);
571
+ }, []);
572
+ const showTooltipAt = useCallback(
573
+ (series, point) => {
574
+ if (showTooltip) {
575
+ setIsTooltipVisible(true);
576
+ setTooltipPoint({ series, point });
577
+ const trendResult = calculateTrendForPoint(series, point);
578
+ setCurrentTrend(trendResult?.text ?? null);
579
+ setCurrentTrendDirection(trendResult?.direction ?? null);
580
+ }
581
+ },
582
+ [showTooltip, calculateTrendForPoint]
583
+ );
584
+ const hideTooltip = useCallback(() => {
585
+ setIsTooltipVisible(false);
586
+ setTooltipPoint(null);
587
+ }, []);
588
+ const value = useMemo(
589
+ () => ({
590
+ // State
591
+ focusedSeries: roving.focusedRow,
592
+ focusedPoint: roving.focusedCol,
593
+ selectedPoint,
594
+ announcement,
595
+ isTooltipVisible,
596
+ tooltipPoint,
597
+ currentTrend,
598
+ currentTrendDirection,
599
+ // Computed
600
+ dimensions,
601
+ scales,
602
+ // Props passthrough
603
+ data,
604
+ type,
605
+ title,
606
+ description,
607
+ xAxisLabel,
608
+ yAxisLabel,
609
+ showTooltip,
610
+ showGrid,
611
+ showLegend,
612
+ announceTrends,
613
+ trendUpFormat,
614
+ trendDownFormat,
615
+ startAtZero,
616
+ reducedMotion,
617
+ // Actions
618
+ setFocus,
619
+ selectPoint,
620
+ clearSelection,
621
+ announce,
622
+ showTooltipAt,
623
+ hideTooltip,
624
+ // Helpers
625
+ getTabIndex: roving.getTabIndex,
626
+ formatValue,
627
+ getSeriesColor,
628
+ getPointCoordinates
629
+ }),
630
+ [
631
+ roving.focusedRow,
632
+ roving.focusedCol,
633
+ roving.getTabIndex,
634
+ selectedPoint,
635
+ announcement,
636
+ isTooltipVisible,
637
+ tooltipPoint,
638
+ currentTrend,
639
+ currentTrendDirection,
640
+ dimensions,
641
+ scales,
642
+ data,
643
+ type,
644
+ title,
645
+ description,
646
+ xAxisLabel,
647
+ yAxisLabel,
648
+ showTooltip,
649
+ showGrid,
650
+ showLegend,
651
+ announceTrends,
652
+ trendUpFormat,
653
+ trendDownFormat,
654
+ startAtZero,
655
+ reducedMotion,
656
+ setFocus,
657
+ selectPoint,
658
+ clearSelection,
659
+ announce,
660
+ showTooltipAt,
661
+ hideTooltip,
662
+ formatValue,
663
+ getSeriesColor,
664
+ getPointCoordinates
665
+ ]
666
+ );
667
+ return /* @__PURE__ */ jsx(ChartContext.Provider, { value, children });
668
+ }
669
+ function ChartGrid({
670
+ yTicks,
671
+ yScale,
672
+ plotX,
673
+ plotWidth,
674
+ visible
675
+ }) {
676
+ if (!visible) {
677
+ return null;
678
+ }
679
+ return /* @__PURE__ */ jsx("g", { className: "chart-grid", "aria-hidden": "true", "data-testid": "chart-grid", children: yTicks.map((tick) => {
680
+ const y = yScale(tick);
681
+ return /* @__PURE__ */ jsx(
682
+ "line",
683
+ {
684
+ x1: plotX,
685
+ y1: y,
686
+ x2: plotX + plotWidth,
687
+ y2: y,
688
+ stroke: GRID_COLOR,
689
+ strokeWidth: GRID_STROKE_WIDTH,
690
+ strokeOpacity: GRID_STROKE_OPACITY
691
+ },
692
+ tick
693
+ );
694
+ }) });
695
+ }
696
+ ChartGrid.displayName = "ChartGrid";
697
+ function ChartAxis({
698
+ allLabels,
699
+ yTicks,
700
+ xScale,
701
+ yScale,
702
+ plotArea,
703
+ xAxisLabel,
704
+ yAxisLabel,
705
+ formatValue
706
+ }) {
707
+ const { x: plotX, y: plotY, width: plotWidth, height: plotHeight } = plotArea;
708
+ return /* @__PURE__ */ jsxs("g", { className: "chart-axis", "aria-hidden": "true", "data-testid": "chart-axis", children: [
709
+ /* @__PURE__ */ jsx(
710
+ "line",
711
+ {
712
+ x1: plotX,
713
+ y1: plotY + plotHeight,
714
+ x2: plotX + plotWidth,
715
+ y2: plotY + plotHeight,
716
+ stroke: AXIS_COLOR,
717
+ strokeWidth: AXIS_STROKE_WIDTH
718
+ }
719
+ ),
720
+ /* @__PURE__ */ jsx(
721
+ "line",
722
+ {
723
+ x1: plotX,
724
+ y1: plotY,
725
+ x2: plotX,
726
+ y2: plotY + plotHeight,
727
+ stroke: AXIS_COLOR,
728
+ strokeWidth: AXIS_STROKE_WIDTH
729
+ }
730
+ ),
731
+ allLabels.map((label, index) => {
732
+ const x = xScale(index);
733
+ const y = plotY + plotHeight;
734
+ return /* @__PURE__ */ jsxs("g", { children: [
735
+ /* @__PURE__ */ jsx(
736
+ "line",
737
+ {
738
+ x1: x,
739
+ y1: y,
740
+ x2: x,
741
+ y2: y + AXIS_TICK_SIZE,
742
+ stroke: AXIS_COLOR,
743
+ strokeWidth: AXIS_STROKE_WIDTH
744
+ }
745
+ ),
746
+ /* @__PURE__ */ jsx(
747
+ "text",
748
+ {
749
+ x,
750
+ y: y + AXIS_TICK_SIZE + AXIS_LABEL_FONT_SIZE,
751
+ textAnchor: "middle",
752
+ fontSize: AXIS_LABEL_FONT_SIZE,
753
+ fill: "currentColor",
754
+ className: "text-[var(--content-foreground)]",
755
+ children: label
756
+ }
757
+ )
758
+ ] }, label);
759
+ }),
760
+ yTicks.map((tick) => {
761
+ const x = plotX;
762
+ const y = yScale(tick);
763
+ return /* @__PURE__ */ jsxs("g", { children: [
764
+ /* @__PURE__ */ jsx(
765
+ "line",
766
+ {
767
+ x1: x - AXIS_TICK_SIZE,
768
+ y1: y,
769
+ x2: x,
770
+ y2: y,
771
+ stroke: AXIS_COLOR,
772
+ strokeWidth: AXIS_STROKE_WIDTH
773
+ }
774
+ ),
775
+ /* @__PURE__ */ jsx(
776
+ "text",
777
+ {
778
+ x: x - AXIS_TICK_SIZE - 4,
779
+ y,
780
+ textAnchor: "end",
781
+ dominantBaseline: "middle",
782
+ fontSize: AXIS_LABEL_FONT_SIZE,
783
+ fill: "currentColor",
784
+ className: "text-[var(--content-foreground)]",
785
+ children: formatValue(tick)
786
+ }
787
+ )
788
+ ] }, tick);
789
+ }),
790
+ /* @__PURE__ */ jsx(
791
+ "text",
792
+ {
793
+ x: plotX + plotWidth / 2,
794
+ y: plotY + plotHeight + 45,
795
+ textAnchor: "middle",
796
+ fontSize: AXIS_LABEL_FONT_SIZE,
797
+ fill: "currentColor",
798
+ className: "text-[var(--content-foreground)] font-medium",
799
+ children: xAxisLabel
800
+ }
801
+ ),
802
+ /* @__PURE__ */ jsx(
803
+ "text",
804
+ {
805
+ x: 15,
806
+ y: plotY + plotHeight / 2,
807
+ textAnchor: "middle",
808
+ fontSize: AXIS_LABEL_FONT_SIZE,
809
+ fill: "currentColor",
810
+ className: "text-[var(--content-foreground)] font-medium",
811
+ transform: `rotate(-90, 15, ${plotY + plotHeight / 2})`,
812
+ children: yAxisLabel
813
+ }
814
+ )
815
+ ] });
816
+ }
817
+ ChartAxis.displayName = "ChartAxis";
818
+ function ChartDataPoint({
819
+ type,
820
+ x,
821
+ y,
822
+ width = POINT_RADIUS * 2,
823
+ height = POINT_RADIUS * 2,
824
+ color,
825
+ seriesIndex,
826
+ pointIndex,
827
+ tabIndex,
828
+ label,
829
+ isSelected = false,
830
+ onFocus,
831
+ onBlur,
832
+ onHover,
833
+ onHoverEnd,
834
+ onKeyDown
835
+ }) {
836
+ const ref = useRef(null);
837
+ const { isFocusVisible, focusProps } = useFocusRing();
838
+ useEffect(() => {
839
+ if (tabIndex === 0 && ref.current) {
840
+ const activeElement = document.activeElement;
841
+ const isChartFocused = activeElement?.closest('[role="graphics-document"]') !== null;
842
+ if (isChartFocused) {
843
+ ref.current.focus();
844
+ }
845
+ }
846
+ }, [tabIndex]);
847
+ const hitAreaSize = Math.max(MIN_TOUCH_TARGET, width, height);
848
+ const hitX = type === "bar" ? x - (hitAreaSize - width) / 2 : x - hitAreaSize / 2;
849
+ const hitY = type === "bar" ? y - (hitAreaSize - height) / 2 : y - hitAreaSize / 2;
850
+ const handleFocus = (event) => {
851
+ focusProps.onFocus?.(event);
852
+ onFocus?.(seriesIndex, pointIndex);
853
+ };
854
+ const handleBlur = (event) => {
855
+ focusProps.onBlur?.(event);
856
+ onBlur?.();
857
+ };
858
+ const handleMouseEnter = (_event) => {
859
+ onHover?.(seriesIndex, pointIndex);
860
+ };
861
+ const handleMouseLeave = (_event) => {
862
+ onHoverEnd?.();
863
+ };
864
+ const state = isSelected ? "selected" : isFocusVisible ? "focused" : "default";
865
+ return /* @__PURE__ */ jsxs(
866
+ "g",
867
+ {
868
+ ref,
869
+ role: "listitem",
870
+ tabIndex,
871
+ "aria-label": label,
872
+ className: dataPointVariants({ type, state }),
873
+ onFocus: handleFocus,
874
+ onBlur: handleBlur,
875
+ onMouseEnter: handleMouseEnter,
876
+ onMouseLeave: handleMouseLeave,
877
+ onKeyDown,
878
+ "data-testid": `chart-point-${seriesIndex}-${pointIndex}`,
879
+ children: [
880
+ /* @__PURE__ */ jsx(
881
+ "rect",
882
+ {
883
+ x: hitX,
884
+ y: hitY,
885
+ width: hitAreaSize,
886
+ height: hitAreaSize,
887
+ fill: "transparent",
888
+ className: "cursor-pointer"
889
+ }
890
+ ),
891
+ type === "bar" ? /* @__PURE__ */ jsx(
892
+ "rect",
893
+ {
894
+ x,
895
+ y,
896
+ width,
897
+ height,
898
+ rx: BAR_RADIUS,
899
+ ry: BAR_RADIUS,
900
+ fill: color,
901
+ className: "chart-bar"
902
+ }
903
+ ) : /* @__PURE__ */ jsx(
904
+ "circle",
905
+ {
906
+ cx: x,
907
+ cy: y,
908
+ r: POINT_RADIUS,
909
+ fill: color,
910
+ className: "chart-point"
911
+ }
912
+ ),
913
+ isFocusVisible && (type === "bar" ? /* @__PURE__ */ jsx(
914
+ "rect",
915
+ {
916
+ x: x - FOCUS_RING_OFFSET,
917
+ y: y - FOCUS_RING_OFFSET,
918
+ width: width + FOCUS_RING_OFFSET * 2,
919
+ height: height + FOCUS_RING_OFFSET * 2,
920
+ rx: BAR_RADIUS + FOCUS_RING_OFFSET,
921
+ ry: BAR_RADIUS + FOCUS_RING_OFFSET,
922
+ fill: "none",
923
+ stroke: "var(--ring)",
924
+ strokeWidth: FOCUS_RING_WIDTH,
925
+ className: "chart-focus-ring"
926
+ }
927
+ ) : /* @__PURE__ */ jsx(
928
+ "circle",
929
+ {
930
+ cx: x,
931
+ cy: y,
932
+ r: POINT_RADIUS + FOCUS_RING_OFFSET,
933
+ fill: "none",
934
+ stroke: "var(--ring)",
935
+ strokeWidth: FOCUS_RING_WIDTH,
936
+ className: "chart-focus-ring"
937
+ }
938
+ ))
939
+ ]
940
+ }
941
+ );
942
+ }
943
+ ChartDataPoint.displayName = "ChartDataPoint";
944
+ function ChartBarSeries({
945
+ series,
946
+ seriesIndex,
947
+ totalSeries,
948
+ dimensions,
949
+ scales,
950
+ color,
951
+ getTabIndex,
952
+ selectedPoint,
953
+ onPointFocus,
954
+ onPointBlur,
955
+ onPointHover,
956
+ onPointHoverEnd,
957
+ onKeyDown,
958
+ formatValue
959
+ }) {
960
+ const { plotArea } = dimensions;
961
+ const { allLabels, xScale: _xScale, yScale, yMin } = scales;
962
+ const { barWidth, getBarX } = calculateBarLayout(
963
+ plotArea.width,
964
+ allLabels.length,
965
+ totalSeries,
966
+ plotArea.x
967
+ );
968
+ const baselineY = yScale(Math.max(0, yMin));
969
+ return /* @__PURE__ */ jsx(
970
+ "g",
971
+ {
972
+ className: "chart-bar-series",
973
+ role: "list",
974
+ "aria-label": `${series.name} series`,
975
+ "data-testid": `chart-series-${seriesIndex}`,
976
+ children: series.data.map((point, pointIndex) => {
977
+ const labelIndex = allLabels.indexOf(point.label);
978
+ if (labelIndex === -1) return null;
979
+ const barX = getBarX(labelIndex, seriesIndex);
980
+ const barY = yScale(point.value);
981
+ const barHeight = Math.abs(baselineY - barY);
982
+ const finalBarY = point.value >= 0 ? barY : baselineY;
983
+ const isSelected = selectedPoint?.series === seriesIndex && selectedPoint?.point === pointIndex;
984
+ const accessibleLabel = `${series.name}: ${point.label}, ${formatValue(point.value)}`;
985
+ return /* @__PURE__ */ jsx(
986
+ ChartDataPoint,
987
+ {
988
+ type: "bar",
989
+ x: barX,
990
+ y: finalBarY,
991
+ width: barWidth,
992
+ height: barHeight,
993
+ color,
994
+ seriesIndex,
995
+ pointIndex,
996
+ tabIndex: getTabIndex(seriesIndex, pointIndex),
997
+ label: accessibleLabel,
998
+ isSelected,
999
+ onFocus: onPointFocus,
1000
+ onBlur: onPointBlur,
1001
+ onHover: onPointHover,
1002
+ onHoverEnd: onPointHoverEnd,
1003
+ onKeyDown
1004
+ },
1005
+ `${point.label}-${pointIndex}`
1006
+ );
1007
+ })
1008
+ }
1009
+ );
1010
+ }
1011
+ ChartBarSeries.displayName = "ChartBarSeries";
1012
+ function ChartLineSeries({
1013
+ series,
1014
+ seriesIndex,
1015
+ scales,
1016
+ color,
1017
+ pattern,
1018
+ getTabIndex,
1019
+ selectedPoint,
1020
+ onPointFocus,
1021
+ onPointBlur,
1022
+ onPointHover,
1023
+ onPointHoverEnd,
1024
+ onKeyDown,
1025
+ formatValue
1026
+ }) {
1027
+ const { allLabels, xScale, yScale } = scales;
1028
+ const points = useMemo(() => {
1029
+ return series.data.map((point, pointIndex) => {
1030
+ const labelIndex = allLabels.indexOf(point.label);
1031
+ if (labelIndex === -1) return null;
1032
+ return {
1033
+ x: xScale(labelIndex),
1034
+ y: yScale(point.value),
1035
+ point,
1036
+ pointIndex,
1037
+ labelIndex
1038
+ };
1039
+ }).filter((p) => p !== null).sort((a, b) => a.labelIndex - b.labelIndex);
1040
+ }, [series.data, allLabels, xScale, yScale]);
1041
+ const pathD = useMemo(() => {
1042
+ if (points.length === 0) return "";
1043
+ return points.map((p, i) => `${i === 0 ? "M" : "L"} ${p.x} ${p.y}`).join(" ");
1044
+ }, [points]);
1045
+ const strokeDasharray = LINE_PATTERNS[pattern] === "none" ? void 0 : LINE_PATTERNS[pattern];
1046
+ return /* @__PURE__ */ jsxs(
1047
+ "g",
1048
+ {
1049
+ className: "chart-line-series",
1050
+ role: "list",
1051
+ "aria-label": `${series.name} series`,
1052
+ "data-testid": `chart-series-${seriesIndex}`,
1053
+ children: [
1054
+ /* @__PURE__ */ jsx(
1055
+ "path",
1056
+ {
1057
+ d: pathD,
1058
+ fill: "none",
1059
+ stroke: color,
1060
+ strokeWidth: LINE_STROKE_WIDTH,
1061
+ strokeDasharray,
1062
+ strokeLinecap: "round",
1063
+ strokeLinejoin: "round",
1064
+ className: "chart-line",
1065
+ "aria-hidden": "true"
1066
+ }
1067
+ ),
1068
+ points.map(({ x, y, point, pointIndex }) => {
1069
+ const isSelected = selectedPoint?.series === seriesIndex && selectedPoint?.point === pointIndex;
1070
+ const accessibleLabel = `${series.name}: ${point.label}, ${formatValue(point.value)}`;
1071
+ return /* @__PURE__ */ jsx(
1072
+ ChartDataPoint,
1073
+ {
1074
+ type: "line",
1075
+ x,
1076
+ y,
1077
+ color,
1078
+ seriesIndex,
1079
+ pointIndex,
1080
+ tabIndex: getTabIndex(seriesIndex, pointIndex),
1081
+ label: accessibleLabel,
1082
+ isSelected,
1083
+ onFocus: onPointFocus,
1084
+ onBlur: onPointBlur,
1085
+ onHover: onPointHover,
1086
+ onHoverEnd: onPointHoverEnd,
1087
+ onKeyDown
1088
+ },
1089
+ `${point.label}-${pointIndex}`
1090
+ );
1091
+ })
1092
+ ]
1093
+ }
1094
+ );
1095
+ }
1096
+ ChartLineSeries.displayName = "ChartLineSeries";
1097
+ function ChartSVG({
1098
+ type,
1099
+ data,
1100
+ dimensions,
1101
+ scales,
1102
+ titleId,
1103
+ descId,
1104
+ showGrid,
1105
+ xAxisLabel,
1106
+ yAxisLabel,
1107
+ getSeriesColor,
1108
+ getTabIndex,
1109
+ selectedPoint,
1110
+ formatValue,
1111
+ onKeyDown,
1112
+ onPointFocus,
1113
+ onPointBlur,
1114
+ onPointHover,
1115
+ onPointHoverEnd
1116
+ }) {
1117
+ const { width, height, plotArea } = dimensions;
1118
+ const { allLabels, xScale, yScale, yTicks } = scales;
1119
+ return /* @__PURE__ */ jsxs(
1120
+ "svg",
1121
+ {
1122
+ role: "graphics-document",
1123
+ "aria-roledescription": `${type} chart`,
1124
+ "aria-labelledby": titleId,
1125
+ "aria-describedby": descId,
1126
+ viewBox: `0 0 ${width} ${height}`,
1127
+ className: "h-full w-full",
1128
+ style: { maxHeight: height },
1129
+ "data-testid": "chart-svg",
1130
+ children: [
1131
+ /* @__PURE__ */ jsx(
1132
+ ChartGrid,
1133
+ {
1134
+ yTicks,
1135
+ yScale,
1136
+ plotX: plotArea.x,
1137
+ plotWidth: plotArea.width,
1138
+ visible: showGrid
1139
+ }
1140
+ ),
1141
+ /* @__PURE__ */ jsx(
1142
+ ChartAxis,
1143
+ {
1144
+ allLabels,
1145
+ yTicks,
1146
+ xScale,
1147
+ yScale,
1148
+ plotArea,
1149
+ xAxisLabel,
1150
+ yAxisLabel,
1151
+ formatValue
1152
+ }
1153
+ ),
1154
+ data.map((series, seriesIndex) => {
1155
+ const color = series.color || getSeriesColor(seriesIndex);
1156
+ const pattern = series.pattern || "solid";
1157
+ if (type === "bar") {
1158
+ return /* @__PURE__ */ jsx(
1159
+ ChartBarSeries,
1160
+ {
1161
+ series,
1162
+ seriesIndex,
1163
+ totalSeries: data.length,
1164
+ dimensions,
1165
+ scales,
1166
+ color,
1167
+ getTabIndex,
1168
+ selectedPoint,
1169
+ onPointFocus,
1170
+ onPointBlur,
1171
+ onPointHover,
1172
+ onPointHoverEnd,
1173
+ onKeyDown,
1174
+ formatValue
1175
+ },
1176
+ series.name
1177
+ );
1178
+ }
1179
+ return /* @__PURE__ */ jsx(
1180
+ ChartLineSeries,
1181
+ {
1182
+ series,
1183
+ seriesIndex,
1184
+ scales,
1185
+ color,
1186
+ pattern,
1187
+ getTabIndex,
1188
+ selectedPoint,
1189
+ onPointFocus,
1190
+ onPointBlur,
1191
+ onPointHover,
1192
+ onPointHoverEnd,
1193
+ onKeyDown,
1194
+ formatValue
1195
+ },
1196
+ series.name
1197
+ );
1198
+ })
1199
+ ]
1200
+ }
1201
+ );
1202
+ }
1203
+ ChartSVG.displayName = "ChartSVG";
1204
+ var tooltipContentVariants = cva(
1205
+ // Base styles
1206
+ [
1207
+ "z-50 overflow-hidden rounded-md px-3 py-1.5 text-sm shadow-md",
1208
+ // Inverted colors for high contrast (7:1 ratio)
1209
+ "bg-[var(--content-foreground)] text-[var(--content-background)]",
1210
+ // Entry animation base
1211
+ "animate-in fade-in-0",
1212
+ // Exit animation
1213
+ "data-[exiting]:animate-out data-[exiting]:fade-out-0"
1214
+ ],
1215
+ {
1216
+ variants: {
1217
+ side: {
1218
+ top: [
1219
+ "motion-safe:slide-in-from-bottom-2",
1220
+ "data-[exiting]:motion-safe:slide-out-to-bottom-2"
1221
+ ],
1222
+ bottom: [
1223
+ "motion-safe:slide-in-from-top-2",
1224
+ "data-[exiting]:motion-safe:slide-out-to-top-2"
1225
+ ],
1226
+ left: [
1227
+ "motion-safe:slide-in-from-right-2",
1228
+ "data-[exiting]:motion-safe:slide-out-to-right-2"
1229
+ ],
1230
+ right: [
1231
+ "motion-safe:slide-in-from-left-2",
1232
+ "data-[exiting]:motion-safe:slide-out-to-left-2"
1233
+ ]
1234
+ }
1235
+ },
1236
+ defaultVariants: {
1237
+ side: "top"
1238
+ }
1239
+ }
1240
+ );
1241
+ cva(
1242
+ // Base arrow styles
1243
+ "fill-[var(--content-foreground)]",
1244
+ {
1245
+ variants: {
1246
+ side: {
1247
+ top: "rotate-180",
1248
+ bottom: "rotate-0",
1249
+ left: "rotate-90",
1250
+ right: "-rotate-90"
1251
+ }
1252
+ },
1253
+ defaultVariants: {
1254
+ side: "top"
1255
+ }
1256
+ }
1257
+ );
1258
+ var TOOLTIP_GAP = 8;
1259
+ function ChartTooltip({
1260
+ visible,
1261
+ x,
1262
+ y,
1263
+ seriesName,
1264
+ label,
1265
+ value,
1266
+ chartWidth,
1267
+ chartHeight,
1268
+ trend,
1269
+ trendDirection
1270
+ }) {
1271
+ const tooltipRef = useRef(null);
1272
+ const [position, setPosition] = useState({ left: 0, top: 0 });
1273
+ const [side, setSide] = useState("top");
1274
+ useEffect(() => {
1275
+ if (!visible || !tooltipRef.current) return;
1276
+ const tooltip = tooltipRef.current;
1277
+ const tooltipRect = tooltip.getBoundingClientRect();
1278
+ const tooltipWidth = tooltipRect.width || 120;
1279
+ const tooltipHeight = tooltipRect.height || 50;
1280
+ let left = x - tooltipWidth / 2;
1281
+ let top = y - tooltipHeight - TOOLTIP_GAP;
1282
+ let newSide = "top";
1283
+ if (top < 0) {
1284
+ top = y + TOOLTIP_GAP + 16;
1285
+ newSide = "bottom";
1286
+ }
1287
+ if (left < 0) {
1288
+ left = 0;
1289
+ }
1290
+ if (left + tooltipWidth > chartWidth) {
1291
+ left = chartWidth - tooltipWidth;
1292
+ }
1293
+ setPosition({ left, top });
1294
+ setSide(newSide);
1295
+ }, [visible, x, y, chartWidth]);
1296
+ if (!visible) {
1297
+ return null;
1298
+ }
1299
+ return /* @__PURE__ */ jsxs(
1300
+ "div",
1301
+ {
1302
+ ref: tooltipRef,
1303
+ "aria-hidden": "true",
1304
+ className: tooltipContentVariants({ side }),
1305
+ style: {
1306
+ position: "absolute",
1307
+ left: position.left,
1308
+ top: position.top,
1309
+ pointerEvents: "none"
1310
+ },
1311
+ "data-testid": "chart-tooltip",
1312
+ children: [
1313
+ /* @__PURE__ */ jsx("div", { className: "font-medium", children: seriesName }),
1314
+ /* @__PURE__ */ jsxs("div", { className: "opacity-80", children: [
1315
+ label,
1316
+ ": ",
1317
+ /* @__PURE__ */ jsx("span", { className: "font-semibold", children: value })
1318
+ ] }),
1319
+ trend && /* @__PURE__ */ jsxs("div", { className: "text-xs opacity-70 mt-1 flex items-center gap-1", children: [
1320
+ trendDirection === "up" ? /* @__PURE__ */ jsx(
1321
+ "svg",
1322
+ {
1323
+ "aria-hidden": "true",
1324
+ className: "h-3 w-3 text-green-500",
1325
+ viewBox: "0 0 20 20",
1326
+ fill: "currentColor",
1327
+ children: /* @__PURE__ */ jsx(
1328
+ "path",
1329
+ {
1330
+ fillRule: "evenodd",
1331
+ d: "M10 17a.75.75 0 01-.75-.75V5.612L5.29 9.77a.75.75 0 01-1.08-1.04l5.25-5.5a.75.75 0 011.08 0l5.25 5.5a.75.75 0 11-1.08 1.04l-3.96-4.158V16.25A.75.75 0 0110 17z",
1332
+ clipRule: "evenodd"
1333
+ }
1334
+ )
1335
+ }
1336
+ ) : trendDirection === "down" ? /* @__PURE__ */ jsx(
1337
+ "svg",
1338
+ {
1339
+ "aria-hidden": "true",
1340
+ className: "h-3 w-3 text-red-500",
1341
+ viewBox: "0 0 20 20",
1342
+ fill: "currentColor",
1343
+ children: /* @__PURE__ */ jsx(
1344
+ "path",
1345
+ {
1346
+ fillRule: "evenodd",
1347
+ d: "M10 3a.75.75 0 01.75.75v10.638l3.96-4.158a.75.75 0 111.08 1.04l-5.25 5.5a.75.75 0 01-1.08 0l-5.25-5.5a.75.75 0 111.08-1.04l3.96 4.158V3.75A.75.75 0 0110 3z",
1348
+ clipRule: "evenodd"
1349
+ }
1350
+ )
1351
+ }
1352
+ ) : null,
1353
+ /* @__PURE__ */ jsx("span", { children: trend })
1354
+ ] })
1355
+ ]
1356
+ }
1357
+ );
1358
+ }
1359
+ ChartTooltip.displayName = "ChartTooltip";
1360
+ function ChartLegend({
1361
+ data,
1362
+ visible,
1363
+ type,
1364
+ getSeriesColor
1365
+ }) {
1366
+ if (!visible) {
1367
+ return null;
1368
+ }
1369
+ return /* @__PURE__ */ jsx(
1370
+ "div",
1371
+ {
1372
+ className: legendVariants({ visible }),
1373
+ role: "list",
1374
+ "aria-label": "Chart legend",
1375
+ "data-testid": "chart-legend",
1376
+ children: data.map((series, index) => {
1377
+ const color = series.color || getSeriesColor(index);
1378
+ const pattern = series.pattern || "solid";
1379
+ return /* @__PURE__ */ jsxs(
1380
+ "div",
1381
+ {
1382
+ role: "listitem",
1383
+ className: "flex items-center gap-2",
1384
+ children: [
1385
+ type === "bar" ? /* @__PURE__ */ jsx(
1386
+ "div",
1387
+ {
1388
+ className: "h-3 w-3 rounded-sm",
1389
+ style: { backgroundColor: color },
1390
+ "data-testid": `legend-color-${index}`,
1391
+ "aria-hidden": "true"
1392
+ }
1393
+ ) : /* @__PURE__ */ jsx(
1394
+ "svg",
1395
+ {
1396
+ width: "24",
1397
+ height: "12",
1398
+ "aria-hidden": "true",
1399
+ "data-testid": `legend-color-${index}`,
1400
+ children: /* @__PURE__ */ jsx(
1401
+ "line",
1402
+ {
1403
+ x1: "0",
1404
+ y1: "6",
1405
+ x2: "24",
1406
+ y2: "6",
1407
+ stroke: color,
1408
+ strokeWidth: "2",
1409
+ strokeDasharray: LINE_PATTERNS[pattern] === "none" ? void 0 : LINE_PATTERNS[pattern]
1410
+ }
1411
+ )
1412
+ }
1413
+ ),
1414
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-[var(--content-foreground)]", children: series.name })
1415
+ ]
1416
+ },
1417
+ series.name
1418
+ );
1419
+ })
1420
+ }
1421
+ );
1422
+ }
1423
+ ChartLegend.displayName = "ChartLegend";
1424
+ function ChartAnnouncer({
1425
+ announcement
1426
+ }) {
1427
+ return /* @__PURE__ */ jsx(VisuallyHidden, { children: /* @__PURE__ */ jsx(
1428
+ "div",
1429
+ {
1430
+ role: "status",
1431
+ "aria-live": "polite",
1432
+ "aria-atomic": "true",
1433
+ "data-testid": "chart-announcer",
1434
+ children: announcement
1435
+ }
1436
+ ) });
1437
+ }
1438
+ ChartAnnouncer.displayName = "ChartAnnouncer";
1439
+ function ChartDataTable({
1440
+ data,
1441
+ title,
1442
+ xAxisLabel,
1443
+ yAxisLabel,
1444
+ formatValue,
1445
+ allLabels
1446
+ }) {
1447
+ const isSingleSeries = data.length === 1;
1448
+ return /* @__PURE__ */ jsx(VisuallyHidden, { children: /* @__PURE__ */ jsxs("table", { "data-testid": "chart-data-table", children: [
1449
+ /* @__PURE__ */ jsxs("caption", { children: [
1450
+ title,
1451
+ " data table"
1452
+ ] }),
1453
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
1454
+ /* @__PURE__ */ jsx("th", { scope: "col", children: xAxisLabel }),
1455
+ isSingleSeries ? /* @__PURE__ */ jsx("th", { scope: "col", children: yAxisLabel }) : data.map((series) => /* @__PURE__ */ jsxs("th", { scope: "col", children: [
1456
+ series.name,
1457
+ " (",
1458
+ yAxisLabel,
1459
+ ")"
1460
+ ] }, series.name))
1461
+ ] }) }),
1462
+ /* @__PURE__ */ jsx("tbody", { children: allLabels.map((label) => /* @__PURE__ */ jsxs("tr", { children: [
1463
+ /* @__PURE__ */ jsx("th", { scope: "row", children: label }),
1464
+ data.map((series) => {
1465
+ const point = series.data.find((p) => p.label === label);
1466
+ return /* @__PURE__ */ jsx("td", { children: point ? formatValue(point.value) : "\u2014" }, series.name);
1467
+ })
1468
+ ] }, label)) })
1469
+ ] }) });
1470
+ }
1471
+ ChartDataTable.displayName = "ChartDataTable";
1472
+ function useChartKeyboard(options) {
1473
+ const { roving, onSelect, onClear, disabled = false } = options;
1474
+ const onKeyDown = useCallback(
1475
+ (event) => {
1476
+ if (disabled) return;
1477
+ switch (event.key) {
1478
+ case "ArrowRight":
1479
+ event.preventDefault();
1480
+ roving.moveRight();
1481
+ break;
1482
+ case "ArrowLeft":
1483
+ event.preventDefault();
1484
+ roving.moveLeft();
1485
+ break;
1486
+ case "ArrowDown":
1487
+ event.preventDefault();
1488
+ roving.moveDown();
1489
+ break;
1490
+ case "ArrowUp":
1491
+ event.preventDefault();
1492
+ roving.moveUp();
1493
+ break;
1494
+ case "Home":
1495
+ event.preventDefault();
1496
+ roving.moveToStart();
1497
+ break;
1498
+ case "End":
1499
+ event.preventDefault();
1500
+ roving.moveToEnd();
1501
+ break;
1502
+ case "Enter":
1503
+ case " ":
1504
+ event.preventDefault();
1505
+ onSelect?.();
1506
+ break;
1507
+ case "Escape":
1508
+ event.preventDefault();
1509
+ onClear?.();
1510
+ break;
1511
+ }
1512
+ },
1513
+ [roving, onSelect, onClear, disabled]
1514
+ );
1515
+ return { onKeyDown };
1516
+ }
1517
+ function ChartInner({ chartId }) {
1518
+ const ctx = useChartContext();
1519
+ const {
1520
+ data,
1521
+ type,
1522
+ title,
1523
+ xAxisLabel,
1524
+ yAxisLabel,
1525
+ showTooltip,
1526
+ showGrid,
1527
+ showLegend,
1528
+ dimensions,
1529
+ scales,
1530
+ announcement,
1531
+ isTooltipVisible,
1532
+ tooltipPoint,
1533
+ currentTrend,
1534
+ currentTrendDirection,
1535
+ selectedPoint,
1536
+ getTabIndex,
1537
+ formatValue,
1538
+ getSeriesColor,
1539
+ getPointCoordinates,
1540
+ setFocus,
1541
+ selectPoint,
1542
+ clearSelection,
1543
+ showTooltipAt,
1544
+ hideTooltip
1545
+ } = ctx;
1546
+ const titleId = `${chartId}-title`;
1547
+ const descId = `${chartId}-desc`;
1548
+ const roving = useRovingTabIndex2D({
1549
+ rows: data.length,
1550
+ cols: (row) => data[row]?.data.length || 0,
1551
+ onFocusChange: (seriesIndex, pointIndex) => {
1552
+ setFocus(seriesIndex, pointIndex);
1553
+ }
1554
+ });
1555
+ const { onKeyDown } = useChartKeyboard({
1556
+ roving,
1557
+ onSelect: () => {
1558
+ selectPoint(roving.focusedRow, roving.focusedCol);
1559
+ },
1560
+ onClear: () => {
1561
+ clearSelection();
1562
+ }
1563
+ });
1564
+ const handlePointFocus = useCallback(
1565
+ (seriesIndex, pointIndex) => {
1566
+ roving.setFocus(seriesIndex, pointIndex);
1567
+ showTooltipAt(seriesIndex, pointIndex);
1568
+ },
1569
+ [roving, showTooltipAt]
1570
+ );
1571
+ const handlePointBlur = useCallback(() => {
1572
+ }, []);
1573
+ const handlePointHover = useCallback(
1574
+ (seriesIndex, pointIndex) => {
1575
+ showTooltipAt(seriesIndex, pointIndex);
1576
+ },
1577
+ [showTooltipAt]
1578
+ );
1579
+ const handlePointHoverEnd = useCallback(() => {
1580
+ hideTooltip();
1581
+ }, [hideTooltip]);
1582
+ const tooltipPosition = useMemo(() => {
1583
+ if (!tooltipPoint) return { x: 0, y: 0 };
1584
+ const coords = getPointCoordinates(tooltipPoint.series, tooltipPoint.point);
1585
+ return coords || { x: 0, y: 0 };
1586
+ }, [tooltipPoint, getPointCoordinates]);
1587
+ const tooltipSeries = useMemo(
1588
+ () => tooltipPoint ? data[tooltipPoint.series] : null,
1589
+ [tooltipPoint, data]
1590
+ );
1591
+ const tooltipPointData = useMemo(
1592
+ () => tooltipPoint ? tooltipSeries?.data[tooltipPoint.point] : null,
1593
+ [tooltipPoint, tooltipSeries]
1594
+ );
1595
+ const shouldShowLegend = useMemo(
1596
+ () => showLegend ?? data.length > 1,
1597
+ [showLegend, data.length]
1598
+ );
1599
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1600
+ /* @__PURE__ */ jsx(ChartAnnouncer, { announcement }),
1601
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
1602
+ /* @__PURE__ */ jsx("div", { className: "relative z-0", children: /* @__PURE__ */ jsx(
1603
+ ChartSVG,
1604
+ {
1605
+ type,
1606
+ data,
1607
+ dimensions,
1608
+ scales,
1609
+ titleId,
1610
+ descId,
1611
+ showGrid,
1612
+ xAxisLabel,
1613
+ yAxisLabel,
1614
+ getSeriesColor,
1615
+ getTabIndex,
1616
+ selectedPoint,
1617
+ formatValue,
1618
+ onKeyDown,
1619
+ onPointFocus: handlePointFocus,
1620
+ onPointBlur: handlePointBlur,
1621
+ onPointHover: handlePointHover,
1622
+ onPointHoverEnd: handlePointHoverEnd
1623
+ }
1624
+ ) }),
1625
+ showTooltip && tooltipPointData && tooltipSeries && /* @__PURE__ */ jsx(
1626
+ ChartTooltip,
1627
+ {
1628
+ visible: isTooltipVisible,
1629
+ x: tooltipPosition.x,
1630
+ y: tooltipPosition.y,
1631
+ seriesName: tooltipSeries.name,
1632
+ label: tooltipPointData.label,
1633
+ value: formatValue(tooltipPointData.value),
1634
+ chartWidth: dimensions.width,
1635
+ chartHeight: dimensions.height,
1636
+ trend: currentTrend,
1637
+ trendDirection: currentTrendDirection
1638
+ }
1639
+ )
1640
+ ] }),
1641
+ /* @__PURE__ */ jsx(
1642
+ ChartLegend,
1643
+ {
1644
+ data,
1645
+ visible: shouldShowLegend,
1646
+ type,
1647
+ getSeriesColor
1648
+ }
1649
+ ),
1650
+ /* @__PURE__ */ jsx(
1651
+ ChartDataTable,
1652
+ {
1653
+ data,
1654
+ title,
1655
+ xAxisLabel,
1656
+ yAxisLabel,
1657
+ formatValue,
1658
+ allLabels: scales.allLabels
1659
+ }
1660
+ )
1661
+ ] });
1662
+ }
1663
+ var ChartComponent = forwardRef(
1664
+ ({
1665
+ data,
1666
+ type,
1667
+ title,
1668
+ description,
1669
+ xAxisLabel,
1670
+ yAxisLabel,
1671
+ variant = "default",
1672
+ className,
1673
+ showTooltip = true,
1674
+ showGrid = true,
1675
+ showLegend,
1676
+ announceTrends = false,
1677
+ trendUpFormat = "up {percent}% from previous",
1678
+ trendDownFormat = "down {percent}% from previous",
1679
+ startAtZero = true,
1680
+ reducedMotion,
1681
+ onPointFocus,
1682
+ onPointSelect,
1683
+ id,
1684
+ "aria-label": ariaLabel,
1685
+ "aria-labelledby": ariaLabelledby,
1686
+ "aria-describedby": ariaDescribedby,
1687
+ "data-testid": dataTestId,
1688
+ ...rest
1689
+ }, ref) => {
1690
+ const containerRef = useRef(null);
1691
+ const generatedId = useId();
1692
+ const chartId = id || `chart-${generatedId}`;
1693
+ const titleId = `${chartId}-title`;
1694
+ const descId = `${chartId}-desc`;
1695
+ const shouldShowLegend = showLegend ?? data.length > 1;
1696
+ return /* @__PURE__ */ jsxs(
1697
+ "figure",
1698
+ {
1699
+ ref: (node) => {
1700
+ if (typeof ref === "function") {
1701
+ ref(node);
1702
+ } else if (ref) {
1703
+ ref.current = node;
1704
+ }
1705
+ containerRef.current = node;
1706
+ },
1707
+ id: chartId,
1708
+ role: "figure",
1709
+ "aria-label": ariaLabel,
1710
+ "aria-labelledby": ariaLabelledby || titleId,
1711
+ "aria-describedby": ariaDescribedby || descId,
1712
+ "data-testid": dataTestId,
1713
+ className: cn(chartVariants({ variant }), "p-4", className),
1714
+ ...rest,
1715
+ children: [
1716
+ /* @__PURE__ */ jsxs("figcaption", { className: "sr-only", children: [
1717
+ /* @__PURE__ */ jsx("span", { id: titleId, children: title }),
1718
+ /* @__PURE__ */ jsx("span", { id: descId, children: description })
1719
+ ] }),
1720
+ /* @__PURE__ */ jsx(
1721
+ ChartProvider,
1722
+ {
1723
+ containerRef,
1724
+ data,
1725
+ type,
1726
+ title,
1727
+ description,
1728
+ xAxisLabel,
1729
+ yAxisLabel,
1730
+ showTooltip,
1731
+ showGrid,
1732
+ showLegend: shouldShowLegend,
1733
+ announceTrends,
1734
+ trendUpFormat,
1735
+ trendDownFormat,
1736
+ startAtZero,
1737
+ reducedMotion,
1738
+ onPointFocus,
1739
+ onPointSelect,
1740
+ children: /* @__PURE__ */ jsx(ChartInner, { chartId })
1741
+ }
1742
+ )
1743
+ ]
1744
+ }
1745
+ );
1746
+ }
1747
+ );
1748
+ ChartComponent.displayName = "Chart";
1749
+ var Chart = memo(ChartComponent);
1750
+ Chart.displayName = "Chart";
1751
+ var BaseComponentPropsSchema = z.object({
1752
+ // Styling
1753
+ className: z.string().optional(),
1754
+ // React
1755
+ children: z.any().optional(),
1756
+ // ReactNode not directly supported by Zod
1757
+ id: z.string().optional(),
1758
+ // Accessibility (WCAG 2.2 AA requirements)
1759
+ "aria-label": z.string().optional(),
1760
+ "aria-labelledby": z.string().optional(),
1761
+ "aria-describedby": z.string().optional(),
1762
+ "aria-live": z.enum(["off", "polite", "assertive"]).optional(),
1763
+ "aria-hidden": z.boolean().optional(),
1764
+ // Testing & Development
1765
+ "data-testid": z.string().optional()
1766
+ });
1767
+
1768
+ // src/elements/Chart/Chart.types.ts
1769
+ var DataPointSchema = z.object({
1770
+ /** X-axis label (e.g., "January", "Q1 2024") */
1771
+ label: z.string().min(1, "Label is required"),
1772
+ /** Y-axis value */
1773
+ value: z.number(),
1774
+ /** Extended description for screen readers */
1775
+ description: z.string().optional()
1776
+ });
1777
+ var LinePatternSchema = z.enum(["solid", "dashed", "dotted"]);
1778
+ var DataSeriesSchema = z.object({
1779
+ /** Series name for legend and announcements */
1780
+ name: z.string().min(1, "Series name is required"),
1781
+ /** CSS color (defaults to --chart-1 through --chart-5) */
1782
+ color: z.string().optional(),
1783
+ /** Line pattern for accessibility (defaults to solid) */
1784
+ pattern: LinePatternSchema.optional().default("solid"),
1785
+ /** Data points in this series */
1786
+ data: z.array(DataPointSchema).min(1, "At least one data point required")
1787
+ });
1788
+ var ChartTypeSchema = z.enum(["bar", "line"]);
1789
+ var ChartVariantSchema = z.enum(["default", "muted"]);
1790
+ var ChartPropsSchema = BaseComponentPropsSchema.extend({
1791
+ // Required props
1792
+ /** Chart data - maximum 5 series (Decision 11) */
1793
+ data: z.array(DataSeriesSchema).min(1, "At least one data series required").max(5, "Maximum 5 series allowed"),
1794
+ /** Chart type: bar or line */
1795
+ type: ChartTypeSchema,
1796
+ /** Chart title (displayed and used for accessibility) */
1797
+ title: z.string().min(1, "Title is required"),
1798
+ /** Chart description (displayed and used for accessibility) */
1799
+ description: z.string().min(1, "Description is required"),
1800
+ /** X-axis label */
1801
+ xAxisLabel: z.string().min(1, "X-axis label is required"),
1802
+ /** Y-axis label */
1803
+ yAxisLabel: z.string().min(1, "Y-axis label is required"),
1804
+ // Optional - Styling
1805
+ /** Visual variant */
1806
+ variant: ChartVariantSchema.optional().default("default"),
1807
+ // Optional - Features
1808
+ /** Show tooltip on focus/hover (default: true) */
1809
+ showTooltip: z.boolean().optional().default(true),
1810
+ /** Show grid lines (default: true) */
1811
+ showGrid: z.boolean().optional().default(true),
1812
+ /** Show legend (auto: shown when >1 series) */
1813
+ showLegend: z.boolean().optional(),
1814
+ /** Announce trend percentages (default: false) */
1815
+ announceTrends: z.boolean().optional().default(false),
1816
+ /** ICU format string for upward trend (use {percent} placeholder) */
1817
+ trendUpFormat: z.string().optional().default("up {percent}% from previous"),
1818
+ /** ICU format string for downward trend (use {percent} placeholder) */
1819
+ trendDownFormat: z.string().optional().default("down {percent}% from previous"),
1820
+ /** Y-axis starts at zero (default: true) - Decision 6 */
1821
+ startAtZero: z.boolean().optional().default(true),
1822
+ // Optional - Accessibility
1823
+ /** Override reduced motion preference */
1824
+ reducedMotion: z.boolean().optional(),
1825
+ // Optional - Events (use z.unknown() since Zod can't validate function types at runtime)
1826
+ /** Called when a data point receives focus */
1827
+ onPointFocus: z.unknown().optional(),
1828
+ /** Called when a data point is selected (Enter/Space) */
1829
+ onPointSelect: z.unknown().optional()
1830
+ });
1831
+
1832
+ export { Chart, ChartPropsSchema, ChartTypeSchema, ChartVariantSchema, DataPointSchema, DataSeriesSchema, LinePatternSchema, chartVariants, dataPointVariants };
5
1833
  //# sourceMappingURL=index.mjs.map
6
1834
  //# sourceMappingURL=index.mjs.map