@eifi1/ui-kit 0.51.7 → 0.53.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (775) hide show
  1. package/README.md +178 -67
  2. package/dist/account/account-parts.d.ts +10 -8
  3. package/dist/account/account-parts.js +1 -18
  4. package/dist/account/account-parts.js.map +1 -1
  5. package/dist/account/data-export-setting-labels.js +1 -1
  6. package/dist/account/data-export-setting-labels.js.map +1 -1
  7. package/dist/account/delete-account-setting-labels.js +1 -1
  8. package/dist/account/delete-account-setting-labels.js.map +1 -1
  9. package/dist/account/email-change-setting-labels.js +1 -1
  10. package/dist/account/email-change-setting-labels.js.map +1 -1
  11. package/dist/account/passkeys-setting.d.ts +9 -2
  12. package/dist/account/passkeys-setting.js +41 -29
  13. package/dist/account/passkeys-setting.js.map +1 -1
  14. package/dist/account/sessions-setting.d.ts +3 -2
  15. package/dist/account/sessions-setting.js +10 -8
  16. package/dist/account/sessions-setting.js.map +1 -1
  17. package/dist/admin/admin-action-confirm.js +1 -3
  18. package/dist/admin/admin-action-confirm.js.map +1 -1
  19. package/dist/admin/admin-parts.d.ts +6 -3
  20. package/dist/admin/admin-parts.js +6 -21
  21. package/dist/admin/admin-parts.js.map +1 -1
  22. package/dist/admin/mail-test-card-labels.js +1 -1
  23. package/dist/admin/mail-test-card-labels.js.map +1 -1
  24. package/dist/admin/plan-change-labels.d.ts +2 -2
  25. package/dist/admin/plan-change-labels.js +1 -17
  26. package/dist/admin/plan-change-labels.js.map +1 -1
  27. package/dist/admin/plan-change.d.ts +3 -42
  28. package/dist/admin/plan-change.js +1 -1
  29. package/dist/admin/plan-change.js.map +1 -1
  30. package/dist/admin/reviewer-scope-editor.js +3 -3
  31. package/dist/admin/reviewer-scope-editor.js.map +1 -1
  32. package/dist/auth/auth-errors-labels.d.ts +11 -0
  33. package/dist/auth/auth-errors-labels.js +13 -0
  34. package/dist/auth/auth-errors-labels.js.map +1 -0
  35. package/dist/auth/auth-errors.d.ts +7 -23
  36. package/dist/auth/auth-errors.js +2 -15
  37. package/dist/auth/auth-errors.js.map +1 -1
  38. package/dist/auth/complete-name-dialog.js +2 -1
  39. package/dist/auth/complete-name-dialog.js.map +1 -1
  40. package/dist/auth/forgot-password-form-labels.js +1 -1
  41. package/dist/auth/forgot-password-form-labels.js.map +1 -1
  42. package/dist/auth/forgot-password-form.js +7 -2
  43. package/dist/auth/forgot-password-form.js.map +1 -1
  44. package/dist/auth/register-form-labels.js +1 -1
  45. package/dist/auth/register-form-labels.js.map +1 -1
  46. package/dist/auth/reset-password-form-labels.js +1 -1
  47. package/dist/auth/reset-password-form-labels.js.map +1 -1
  48. package/dist/auth/reset-password-form.js +5 -1
  49. package/dist/auth/reset-password-form.js.map +1 -1
  50. package/dist/auth/sign-in-form-labels.js +1 -1
  51. package/dist/auth/sign-in-form-labels.js.map +1 -1
  52. package/dist/auth/status-parts.d.ts +0 -3
  53. package/dist/auth/status-parts.js +0 -4
  54. package/dist/auth/status-parts.js.map +1 -1
  55. package/dist/auth/verify-email-labels.js +1 -1
  56. package/dist/auth/verify-email-labels.js.map +1 -1
  57. package/dist/auth/verify-email.js +7 -2
  58. package/dist/auth/verify-email.js.map +1 -1
  59. package/dist/billing/cancel-subscription.d.ts +10 -7
  60. package/dist/billing/cancel-subscription.js +6 -6
  61. package/dist/billing/cancel-subscription.js.map +1 -1
  62. package/dist/billing/plan-card.d.ts +5 -3
  63. package/dist/billing/plan-card.js +7 -8
  64. package/dist/billing/plan-card.js.map +1 -1
  65. package/dist/billing/plan-change-labels.d.ts +42 -0
  66. package/dist/billing/plan-change-labels.js +22 -0
  67. package/dist/billing/plan-change-labels.js.map +1 -0
  68. package/dist/billing/plan-switch.d.ts +9 -6
  69. package/dist/billing/plan-switch.js +7 -5
  70. package/dist/billing/plan-switch.js.map +1 -1
  71. package/dist/chart.d.ts +4 -3
  72. package/dist/chart.js +72 -4
  73. package/dist/chart.js.map +1 -1
  74. package/dist/components/account-chips.d.ts +5 -5
  75. package/dist/components/account-chips.js +11 -13
  76. package/dist/components/account-chips.js.map +1 -1
  77. package/dist/components/alert-banner.d.ts +16 -5
  78. package/dist/components/alert-banner.js +46 -1
  79. package/dist/components/alert-banner.js.map +1 -1
  80. package/dist/components/amount-input.d.ts +13 -0
  81. package/dist/components/amount-input.js +1 -0
  82. package/dist/components/amount-input.js.map +1 -1
  83. package/dist/components/autocomplete.js +57 -65
  84. package/dist/components/autocomplete.js.map +1 -1
  85. package/dist/components/bulk-action-bar.d.ts +12 -2
  86. package/dist/components/bulk-action-bar.js +65 -21
  87. package/dist/components/bulk-action-bar.js.map +1 -1
  88. package/dist/components/button-look.d.ts +5 -5
  89. package/dist/components/button-look.js.map +1 -1
  90. package/dist/components/button.d.ts +2 -2
  91. package/dist/components/button.js.map +1 -1
  92. package/dist/components/chart-edge.d.ts +16 -0
  93. package/dist/components/chart-edge.js +14 -1
  94. package/dist/components/chart-edge.js.map +1 -1
  95. package/dist/components/chart-figure.d.ts +13 -0
  96. package/dist/components/chart-figure.js +15 -0
  97. package/dist/components/chart-figure.js.map +1 -0
  98. package/dist/components/chart-zoom-core.js +1 -4
  99. package/dist/components/chart-zoom-core.js.map +1 -1
  100. package/dist/components/chart.d.ts +8 -1
  101. package/dist/components/chart.js +19 -10
  102. package/dist/components/chart.js.map +1 -1
  103. package/dist/components/combobox-core.d.ts +111 -2
  104. package/dist/components/combobox-core.js +148 -42
  105. package/dist/components/combobox-core.js.map +1 -1
  106. package/dist/components/combobox.d.ts +1 -1
  107. package/dist/components/combobox.js +141 -130
  108. package/dist/components/combobox.js.map +1 -1
  109. package/dist/components/copy-button.d.ts +6 -5
  110. package/dist/components/copy-button.js +5 -3
  111. package/dist/components/copy-button.js.map +1 -1
  112. package/dist/components/country-select.js +2 -2
  113. package/dist/components/country-select.js.map +1 -1
  114. package/dist/components/data-table-column-settings.d.ts +1 -1
  115. package/dist/components/data-table-column-settings.js +33 -7
  116. package/dist/components/data-table-column-settings.js.map +1 -1
  117. package/dist/components/data-table-filters.d.ts +4 -2
  118. package/dist/components/data-table-filters.js +2 -2
  119. package/dist/components/data-table-filters.js.map +1 -1
  120. package/dist/components/data-table-fit.d.ts +6 -4
  121. package/dist/components/data-table-fit.js +4 -1
  122. package/dist/components/data-table-fit.js.map +1 -1
  123. package/dist/components/data-table-groups.d.ts +1 -1
  124. package/dist/components/data-table-groups.js.map +1 -1
  125. package/dist/components/data-table-head.d.ts +1 -1
  126. package/dist/components/data-table-head.js +15 -12
  127. package/dist/components/data-table-head.js.map +1 -1
  128. package/dist/components/data-table-mobile.d.ts +12 -7
  129. package/dist/components/data-table-mobile.js +43 -30
  130. package/dist/components/data-table-mobile.js.map +1 -1
  131. package/dist/components/data-table-pagination.js +11 -6
  132. package/dist/components/data-table-pagination.js.map +1 -1
  133. package/dist/components/data-table-resize.d.ts +1 -1
  134. package/dist/components/data-table-resize.js.map +1 -1
  135. package/dist/components/data-table-row-actions.d.ts +2 -2
  136. package/dist/components/data-table-row-actions.js.map +1 -1
  137. package/dist/components/data-table-row.d.ts +6 -2
  138. package/dist/components/data-table-row.js +17 -10
  139. package/dist/components/data-table-row.js.map +1 -1
  140. package/dist/components/data-table-selection.d.ts +1 -1
  141. package/dist/components/data-table-selection.js.map +1 -1
  142. package/dist/components/data-table-shared.d.ts +10 -2
  143. package/dist/components/data-table-shared.js +18 -0
  144. package/dist/components/data-table-shared.js.map +1 -1
  145. package/dist/components/data-table-sort.d.ts +21 -2
  146. package/dist/components/data-table-sort.js +24 -0
  147. package/dist/components/data-table-sort.js.map +1 -1
  148. package/dist/components/data-table-virtual.d.ts +1 -1
  149. package/dist/components/data-table-virtual.js +18 -6
  150. package/dist/components/data-table-virtual.js.map +1 -1
  151. package/dist/components/data-table.d.ts +13 -4
  152. package/dist/components/data-table.js +139 -36
  153. package/dist/components/data-table.js.map +1 -1
  154. package/dist/components/date-field.d.ts +25 -3
  155. package/dist/components/date-field.js +33 -8
  156. package/dist/components/date-field.js.map +1 -1
  157. package/dist/components/date-picker.d.ts +16 -3
  158. package/dist/components/date-picker.js +55 -26
  159. package/dist/components/date-picker.js.map +1 -1
  160. package/dist/components/date-range-picker.d.ts +38 -24
  161. package/dist/components/date-range-picker.js +23 -7
  162. package/dist/components/date-range-picker.js.map +1 -1
  163. package/dist/components/error-boundary.js +37 -30
  164. package/dist/components/error-boundary.js.map +1 -1
  165. package/dist/components/{ui-labels.d.ts → field-base-labels.d.ts} +0 -2
  166. package/dist/components/{ui-labels.js → field-base-labels.js} +2 -9
  167. package/dist/components/field-base-labels.js.map +1 -0
  168. package/dist/components/field-base.d.ts +4 -3
  169. package/dist/components/field-base.js +3 -3
  170. package/dist/components/field-base.js.map +1 -1
  171. package/dist/components/form-actions.js +11 -5
  172. package/dist/components/form-actions.js.map +1 -1
  173. package/dist/components/hover-menu.d.ts +3 -0
  174. package/dist/components/hover-menu.js +5 -3
  175. package/dist/components/hover-menu.js.map +1 -1
  176. package/dist/components/icon-button.js +14 -9
  177. package/dist/components/icon-button.js.map +1 -1
  178. package/dist/components/inline-confirm.d.ts +43 -0
  179. package/dist/components/inline-confirm.js +67 -0
  180. package/dist/components/inline-confirm.js.map +1 -0
  181. package/dist/components/inline-entity-combobox.d.ts +4 -3
  182. package/dist/components/inline-entity-combobox.js +148 -140
  183. package/dist/components/inline-entity-combobox.js.map +1 -1
  184. package/dist/components/input.js +1 -1
  185. package/dist/components/input.js.map +1 -1
  186. package/dist/components/jump-calendar.js +28 -112
  187. package/dist/components/jump-calendar.js.map +1 -1
  188. package/dist/components/legal-page.d.ts +3 -1
  189. package/dist/components/legal-page.js +3 -2
  190. package/dist/components/legal-page.js.map +1 -1
  191. package/dist/components/legal.d.ts +4 -2
  192. package/dist/components/legal.js.map +1 -1
  193. package/dist/components/lightbox.d.ts +2 -2
  194. package/dist/components/lightbox.js +17 -6
  195. package/dist/components/lightbox.js.map +1 -1
  196. package/dist/components/list.d.ts +6 -2
  197. package/dist/components/list.js +3 -2
  198. package/dist/components/list.js.map +1 -1
  199. package/dist/components/measured-grid.d.ts +1 -1
  200. package/dist/components/measured-grid.js +2 -1
  201. package/dist/components/measured-grid.js.map +1 -1
  202. package/dist/components/mini-calendar.js +3 -5
  203. package/dist/components/mini-calendar.js.map +1 -1
  204. package/dist/components/modal.js +3 -1
  205. package/dist/components/modal.js.map +1 -1
  206. package/dist/components/month-grid.d.ts +55 -0
  207. package/dist/components/month-grid.js +148 -0
  208. package/dist/components/month-grid.js.map +1 -0
  209. package/dist/components/month-picker.d.ts +36 -4
  210. package/dist/components/month-picker.js +123 -146
  211. package/dist/components/month-picker.js.map +1 -1
  212. package/dist/components/multi-select.d.ts +2 -2
  213. package/dist/components/multi-select.js +11 -6
  214. package/dist/components/multi-select.js.map +1 -1
  215. package/dist/components/nav-pills.d.ts +1 -1
  216. package/dist/components/nav-pills.js.map +1 -1
  217. package/dist/components/picker-sheet.js +2 -1
  218. package/dist/components/picker-sheet.js.map +1 -1
  219. package/dist/components/pie-chart-labels.d.ts +7 -0
  220. package/dist/components/pie-chart-labels.js +1 -0
  221. package/dist/components/pie-chart-labels.js.map +1 -1
  222. package/dist/components/pie-chart.d.ts +5 -3
  223. package/dist/components/pie-chart.js +7 -22
  224. package/dist/components/pie-chart.js.map +1 -1
  225. package/dist/components/popover.d.ts +3 -0
  226. package/dist/components/popover.js +7 -1
  227. package/dist/components/popover.js.map +1 -1
  228. package/dist/components/row-actions.js +2 -1
  229. package/dist/components/row-actions.js.map +1 -1
  230. package/dist/components/series-chart-axes.d.ts +4 -4
  231. package/dist/components/series-chart-axes.js.map +1 -1
  232. package/dist/components/series-chart-labels.d.ts +7 -4
  233. package/dist/components/series-chart-labels.js.map +1 -1
  234. package/dist/components/series-chart.d.ts +92 -23
  235. package/dist/components/series-chart.js +74 -30
  236. package/dist/components/series-chart.js.map +1 -1
  237. package/dist/components/share-card.js +3 -3
  238. package/dist/components/share-card.js.map +1 -1
  239. package/dist/components/tabs-labels.d.ts +2 -0
  240. package/dist/components/tabs-labels.js +11 -0
  241. package/dist/components/tabs-labels.js.map +1 -0
  242. package/dist/components/tabs.d.ts +1 -1
  243. package/dist/components/tabs.js +1 -1
  244. package/dist/components/tabs.js.map +1 -1
  245. package/dist/components/toggle-legend.d.ts +2 -2
  246. package/dist/components/toggle-legend.js +6 -6
  247. package/dist/components/toggle-legend.js.map +1 -1
  248. package/dist/components/translation-review-editor.d.ts +4 -2
  249. package/dist/components/translation-review-editor.js +2 -3
  250. package/dist/components/translation-review-editor.js.map +1 -1
  251. package/dist/components/translation-review.d.ts +2 -1
  252. package/dist/components/translation-review.js +2 -2
  253. package/dist/components/translation-review.js.map +1 -1
  254. package/dist/components/treemap-labels.d.ts +31 -0
  255. package/dist/components/treemap-labels.js +10 -0
  256. package/dist/components/treemap-labels.js.map +1 -0
  257. package/dist/components/treemap-layout.d.ts +62 -0
  258. package/dist/components/treemap-layout.js +126 -0
  259. package/dist/components/treemap-layout.js.map +1 -0
  260. package/dist/components/treemap.d.ts +167 -19
  261. package/dist/components/treemap.js +340 -100
  262. package/dist/components/treemap.js.map +1 -1
  263. package/dist/components/use-table-state.d.ts +5 -5
  264. package/dist/components/use-table-state.js +4 -1
  265. package/dist/components/use-table-state.js.map +1 -1
  266. package/dist/data-table.d.ts +1 -2
  267. package/dist/data-table.js +1 -4
  268. package/dist/data-table.js.map +1 -1
  269. package/dist/demo/demo-countdown.d.ts +0 -5
  270. package/dist/demo/demo-countdown.js +0 -4
  271. package/dist/demo/demo-countdown.js.map +1 -1
  272. package/dist/eslint/index.js +1 -1
  273. package/dist/eslint/role-utilities.d.ts +36 -7
  274. package/dist/eslint/role-utilities.js +42 -9
  275. package/dist/eslint/role-utilities.js.map +1 -1
  276. package/dist/feedback/feedback-dialog.d.ts +25 -16
  277. package/dist/feedback/feedback-dialog.js +6 -2
  278. package/dist/feedback/feedback-dialog.js.map +1 -1
  279. package/dist/feedback/feedback-mutation.d.ts +18 -0
  280. package/dist/feedback/feedback-mutation.js +8 -0
  281. package/dist/feedback/feedback-mutation.js.map +1 -0
  282. package/dist/feedback/feedback-page-link.d.ts +53 -0
  283. package/dist/feedback/feedback-page-link.js +37 -0
  284. package/dist/feedback/feedback-page-link.js.map +1 -0
  285. package/dist/feedback/feedback-public-list.d.ts +8 -4
  286. package/dist/feedback/feedback-public-list.js +2 -2
  287. package/dist/feedback/feedback-public-list.js.map +1 -1
  288. package/dist/feedback/feedback-replies.d.ts +3 -1
  289. package/dist/feedback/feedback-replies.js +5 -4
  290. package/dist/feedback/feedback-replies.js.map +1 -1
  291. package/dist/feedback/feedback-row-detail-labels.js +0 -3
  292. package/dist/feedback/feedback-row-detail-labels.js.map +1 -1
  293. package/dist/feedback/feedback-row-detail.d.ts +33 -44
  294. package/dist/feedback/feedback-row-detail.js +28 -34
  295. package/dist/feedback/feedback-row-detail.js.map +1 -1
  296. package/dist/feedback/feedback-status-undo.d.ts +17 -14
  297. package/dist/feedback/feedback-status-undo.js +10 -15
  298. package/dist/feedback/feedback-status-undo.js.map +1 -1
  299. package/dist/feedback/feedback-submit-dialog.d.ts +1 -1
  300. package/dist/feedback/feedback-submit-dialog.js +1 -1
  301. package/dist/feedback/feedback-submit-dialog.js.map +1 -1
  302. package/dist/feedback/feedback-submit.js +9 -3
  303. package/dist/feedback/feedback-submit.js.map +1 -1
  304. package/dist/feedback/feedback-swipe.d.ts +1 -1
  305. package/dist/feedback/feedback-swipe.js.map +1 -1
  306. package/dist/feedback/feedback-table.d.ts +21 -9
  307. package/dist/feedback/feedback-table.js +45 -25
  308. package/dist/feedback/feedback-table.js.map +1 -1
  309. package/dist/feedback/feedback-thread-labels.d.ts +2 -2
  310. package/dist/feedback/feedback-thread-labels.js.map +1 -1
  311. package/dist/feedback/feedback-thread.d.ts +15 -23
  312. package/dist/feedback/feedback-thread.js +2 -1
  313. package/dist/feedback/feedback-thread.js.map +1 -1
  314. package/dist/feedback.d.ts +6 -6
  315. package/dist/feedback.js +90 -6
  316. package/dist/feedback.js.map +1 -1
  317. package/dist/hooks/use-focus-trap.d.ts +35 -0
  318. package/dist/hooks/use-focus-trap.js +6 -4
  319. package/dist/hooks/use-focus-trap.js.map +1 -1
  320. package/dist/hooks/use-hotkey.d.ts +5 -2
  321. package/dist/hooks/use-hotkey.js +5 -4
  322. package/dist/hooks/use-hotkey.js.map +1 -1
  323. package/dist/hooks/use-long-press.d.ts +7 -4
  324. package/dist/hooks/use-long-press.js +3 -0
  325. package/dist/hooks/use-long-press.js.map +1 -1
  326. package/dist/hooks/use-media-query.js +12 -4
  327. package/dist/hooks/use-media-query.js.map +1 -1
  328. package/dist/hooks/use-mounted.d.ts +22 -0
  329. package/dist/hooks/use-mounted.js +24 -0
  330. package/dist/hooks/use-mounted.js.map +1 -0
  331. package/dist/hooks/use-overlay-history.js +19 -3
  332. package/dist/hooks/use-overlay-history.js.map +1 -1
  333. package/dist/i18n/defaults.js +4 -9
  334. package/dist/i18n/defaults.js.map +1 -1
  335. package/dist/i18n/german.js +7 -18
  336. package/dist/i18n/german.js.map +1 -1
  337. package/dist/i18n/kit-labels.d.ts +9 -11
  338. package/dist/i18n/kit-labels.js.map +1 -1
  339. package/dist/i18n/locales/en.js +15 -64
  340. package/dist/i18n/locales/en.js.map +3 -3
  341. package/dist/i18n/locales/es.js +7 -18
  342. package/dist/i18n/locales/es.js.map +1 -1
  343. package/dist/i18n/locales/fr.js +20 -27
  344. package/dist/i18n/locales/fr.js.map +1 -1
  345. package/dist/i18n/locales/hu.js +9 -18
  346. package/dist/i18n/locales/hu.js.map +1 -1
  347. package/dist/i18n/locales/it.js +7 -19
  348. package/dist/i18n/locales/it.js.map +1 -1
  349. package/dist/i18n/locales/zh.js +8 -18
  350. package/dist/i18n/locales/zh.js.map +1 -1
  351. package/dist/i18n/review.js +12 -2
  352. package/dist/i18n/review.js.map +1 -1
  353. package/dist/i18n/shared-labels.js +0 -1
  354. package/dist/i18n/shared-labels.js.map +1 -1
  355. package/dist/index.d.ts +21 -23
  356. package/dist/index.js +50 -65
  357. package/dist/index.js.map +1 -1
  358. package/dist/landing/routing.d.ts +3 -1
  359. package/dist/landing/routing.js +4 -7
  360. package/dist/landing/routing.js.map +1 -1
  361. package/dist/lib/broadcast.d.ts +17 -0
  362. package/dist/lib/broadcast.js +30 -0
  363. package/dist/lib/broadcast.js.map +1 -0
  364. package/dist/lib/date-format.d.ts +37 -2
  365. package/dist/lib/date-format.js +34 -8
  366. package/dist/lib/date-format.js.map +1 -1
  367. package/dist/lib/day-format.d.ts +46 -0
  368. package/dist/lib/day-format.js +46 -0
  369. package/dist/lib/day-format.js.map +1 -0
  370. package/dist/lib/describe-failure.d.ts +14 -0
  371. package/dist/lib/describe-failure.js +9 -0
  372. package/dist/lib/describe-failure.js.map +1 -0
  373. package/dist/lib/direction.d.ts +16 -0
  374. package/dist/lib/direction.js +9 -1
  375. package/dist/lib/direction.js.map +1 -1
  376. package/dist/lib/focus-left.d.ts +63 -0
  377. package/dist/lib/focus-left.js +67 -0
  378. package/dist/lib/focus-left.js.map +1 -0
  379. package/dist/lib/format.js +5 -14
  380. package/dist/lib/format.js.map +1 -1
  381. package/dist/lib/http-status.d.ts +24 -0
  382. package/dist/lib/http-status.js +14 -0
  383. package/dist/lib/http-status.js.map +1 -0
  384. package/dist/lib/intl-cache.d.ts +14 -0
  385. package/dist/lib/intl-cache.js +15 -0
  386. package/dist/lib/intl-cache.js.map +1 -0
  387. package/dist/lib/iso-day.d.ts +11 -0
  388. package/dist/lib/iso-day.js +8 -0
  389. package/dist/lib/iso-day.js.map +1 -0
  390. package/dist/lib/lazy-with-fallback.d.ts +31 -0
  391. package/dist/lib/lazy-with-fallback.js +40 -0
  392. package/dist/lib/lazy-with-fallback.js.map +1 -0
  393. package/dist/lib/path-pattern.d.ts +34 -0
  394. package/dist/lib/path-pattern.js +24 -0
  395. package/dist/lib/path-pattern.js.map +1 -0
  396. package/dist/lib/reduced-motion.d.ts +2 -1
  397. package/dist/lib/reduced-motion.js.map +1 -1
  398. package/dist/lib/round.d.ts +2 -1
  399. package/dist/lib/round.js +2 -1
  400. package/dist/lib/round.js.map +1 -1
  401. package/dist/lib/safe-storage.d.ts +24 -5
  402. package/dist/lib/safe-storage.js +27 -2
  403. package/dist/lib/safe-storage.js.map +1 -1
  404. package/dist/lib/session-refresh.js +1 -1
  405. package/dist/lib/session-refresh.js.map +1 -1
  406. package/dist/lib/share-format.d.ts +13 -0
  407. package/dist/lib/share-format.js +10 -0
  408. package/dist/lib/share-format.js.map +1 -0
  409. package/dist/lib/text-case.d.ts +17 -0
  410. package/dist/lib/text-case.js +8 -0
  411. package/dist/lib/text-case.js.map +1 -0
  412. package/dist/lib/text-match.d.ts +29 -0
  413. package/dist/lib/text-match.js +20 -2
  414. package/dist/lib/text-match.js.map +1 -1
  415. package/dist/lib/thenable.d.ts +13 -0
  416. package/dist/lib/thenable.js +8 -0
  417. package/dist/lib/thenable.js.map +1 -0
  418. package/dist/lib/translation-review.d.ts +7 -2
  419. package/dist/lib/translation-review.js +4 -4
  420. package/dist/lib/translation-review.js.map +1 -1
  421. package/dist/notifications/device-zone-report.js +1 -1
  422. package/dist/notifications/device-zone-report.js.map +1 -1
  423. package/dist/notifications/notification-bell.d.ts +2 -1
  424. package/dist/notifications/notification-bell.js +3 -3
  425. package/dist/notifications/notification-bell.js.map +1 -1
  426. package/dist/notifications/notification-inbox.d.ts +10 -6
  427. package/dist/notifications/notification-inbox.js +26 -48
  428. package/dist/notifications/notification-inbox.js.map +1 -1
  429. package/dist/notifications/notification-parts.d.ts +3 -2
  430. package/dist/notifications/notification-parts.js +1 -3
  431. package/dist/notifications/notification-parts.js.map +1 -1
  432. package/dist/notifications/notification-types-setting.js +1 -1
  433. package/dist/notifications/notification-types-setting.js.map +1 -1
  434. package/dist/notifications/notifications-labels.d.ts +6 -0
  435. package/dist/notifications/notifications-labels.js +1 -0
  436. package/dist/notifications/notifications-labels.js.map +1 -1
  437. package/dist/notifications/push-device-setting.js +1 -1
  438. package/dist/notifications/push-device-setting.js.map +1 -1
  439. package/dist/notifications/push-devices-setting.d.ts +2 -1
  440. package/dist/notifications/push-devices-setting.js +27 -49
  441. package/dist/notifications/push-devices-setting.js.map +1 -1
  442. package/dist/notifications/quiet-hours-setting.d.ts +10 -3
  443. package/dist/notifications/quiet-hours-setting.js +65 -26
  444. package/dist/notifications/quiet-hours-setting.js.map +1 -1
  445. package/dist/notifications/unsubscribe-panel.js +4 -7
  446. package/dist/notifications/unsubscribe-panel.js.map +1 -1
  447. package/dist/notifications/use-push-device.d.ts +39 -1
  448. package/dist/notifications/use-push-device.js +28 -7
  449. package/dist/notifications/use-push-device.js.map +1 -1
  450. package/dist/offline/query-persistence.d.ts +3 -1
  451. package/dist/offline/query-persistence.js +19 -2
  452. package/dist/offline/query-persistence.js.map +1 -1
  453. package/dist/rhf/fields.d.ts +11 -4
  454. package/dist/rhf/fields.js.map +1 -1
  455. package/dist/search/command-palette.js +1 -1
  456. package/dist/search/command-palette.js.map +1 -1
  457. package/dist/search/global-search.js +19 -9
  458. package/dist/search/global-search.js.map +1 -1
  459. package/dist/search/search-index.d.ts +0 -6
  460. package/dist/search/search-index.js +0 -4
  461. package/dist/search/search-index.js.map +1 -1
  462. package/dist/search/use-command-key.d.ts +12 -1
  463. package/dist/search/use-command-key.js +3 -12
  464. package/dist/search/use-command-key.js.map +1 -1
  465. package/dist/search.d.ts +1 -1
  466. package/dist/search.js +6 -1
  467. package/dist/search.js.map +1 -1
  468. package/dist/settings/use-settings-focus.js +1 -0
  469. package/dist/settings/use-settings-focus.js.map +1 -1
  470. package/dist/shell/account-menu-language.d.ts +5 -3
  471. package/dist/shell/account-menu-language.js +2 -2
  472. package/dist/shell/account-menu-language.js.map +1 -1
  473. package/dist/shell/account-menu-text-size.d.ts +9 -6
  474. package/dist/shell/account-menu-text-size.js +2 -2
  475. package/dist/shell/account-menu-text-size.js.map +1 -1
  476. package/dist/shell/app-shell.d.ts +0 -10
  477. package/dist/shell/app-shell.js +1 -7
  478. package/dist/shell/app-shell.js.map +1 -1
  479. package/dist/shell/company-switcher.d.ts +1 -1
  480. package/dist/shell/company-switcher.js.map +1 -1
  481. package/dist/shell/dev-menu.d.ts +1 -1
  482. package/dist/shell/dev-menu.js +5 -18
  483. package/dist/shell/dev-menu.js.map +1 -1
  484. package/dist/shell/top-bar-actions.d.ts +1 -1
  485. package/dist/shell/top-bar-actions.js.map +1 -1
  486. package/dist/shell/top-bar-parts-labels.d.ts +1 -3
  487. package/dist/shell/top-bar-parts-labels.js +0 -1
  488. package/dist/shell/top-bar-parts-labels.js.map +1 -1
  489. package/dist/shell.d.ts +7 -10
  490. package/dist/shell.js +14 -23
  491. package/dist/shell.js.map +1 -1
  492. package/dist/sync/engine.d.ts +30 -4
  493. package/dist/sync/engine.js +172 -52
  494. package/dist/sync/engine.js.map +1 -1
  495. package/dist/sync.d.ts +2 -2
  496. package/dist/sync.js +2 -10
  497. package/dist/sync.js.map +1 -1
  498. package/dist/testing/css-variables.js +3 -8
  499. package/dist/testing/css-variables.js.map +1 -1
  500. package/dist/theme/chart-palette.d.ts +0 -5
  501. package/dist/theme/chart-palette.js +0 -2
  502. package/dist/theme/chart-palette.js.map +1 -1
  503. package/dist/theme/contrast.d.ts +2 -1
  504. package/dist/theme/contrast.js +3 -8
  505. package/dist/theme/contrast.js.map +1 -1
  506. package/dist/theme/palette-store.js +6 -6
  507. package/dist/theme/palette-store.js.map +1 -1
  508. package/dist/theme/text-size.js +3 -8
  509. package/dist/theme/text-size.js.map +1 -1
  510. package/dist/theme/theme-store.js +3 -6
  511. package/dist/theme/theme-store.js.map +1 -1
  512. package/dist/tour/tour-catalogue.d.ts +7 -4
  513. package/dist/tour/tour-catalogue.js +12 -10
  514. package/dist/tour/tour-catalogue.js.map +1 -1
  515. package/dist/tour/tour-dock.d.ts +0 -2
  516. package/dist/tour/tour-dock.js +0 -2
  517. package/dist/tour/tour-dock.js.map +1 -1
  518. package/dist/tour/tour-gates.d.ts +4 -0
  519. package/dist/tour/tour-gates.js +14 -0
  520. package/dist/tour/tour-gates.js.map +1 -1
  521. package/dist/tour/tour-overlay.d.ts +27 -0
  522. package/dist/tour/tour-overlay.js +741 -0
  523. package/dist/tour/tour-overlay.js.map +1 -0
  524. package/dist/tour/tour.d.ts +13 -3
  525. package/dist/tour/tour.js +59 -781
  526. package/dist/tour/tour.js.map +1 -1
  527. package/dist/tour/use-tour-progress.js +1 -1
  528. package/dist/tour/use-tour-progress.js.map +1 -1
  529. package/dist/wizard/stepper-nav.d.ts +5 -1
  530. package/dist/wizard/stepper-nav.js.map +1 -1
  531. package/dist/wizard/types.d.ts +6 -2
  532. package/dist/wizard/types.js.map +1 -1
  533. package/dist/wizard/use-wizard.d.ts +3 -1
  534. package/dist/wizard/use-wizard.js.map +1 -1
  535. package/dist/wizard/validation.d.ts +3 -1
  536. package/dist/wizard/validation.js.map +1 -1
  537. package/package.json +10 -6
  538. package/src/account/account-parts.ts +5 -25
  539. package/src/account/data-export-setting-labels.ts +1 -1
  540. package/src/account/delete-account-setting-labels.ts +1 -1
  541. package/src/account/email-change-setting-labels.ts +1 -1
  542. package/src/account/passkeys-setting.tsx +60 -38
  543. package/src/account/sessions-setting.tsx +18 -12
  544. package/src/admin/admin-action-confirm.tsx +1 -5
  545. package/src/admin/admin-parts.ts +11 -25
  546. package/src/admin/mail-test-card-labels.ts +1 -1
  547. package/src/admin/plan-change-labels.ts +5 -23
  548. package/src/admin/plan-change.tsx +7 -44
  549. package/src/admin/reviewer-scope-editor.tsx +3 -3
  550. package/src/auth/auth-errors-labels.ts +23 -0
  551. package/src/auth/auth-errors.ts +11 -38
  552. package/src/auth/complete-name-dialog.tsx +4 -1
  553. package/src/auth/forgot-password-form-labels.ts +1 -1
  554. package/src/auth/forgot-password-form.tsx +11 -6
  555. package/src/auth/register-form-labels.ts +1 -1
  556. package/src/auth/reset-password-form-labels.ts +1 -1
  557. package/src/auth/reset-password-form.tsx +8 -4
  558. package/src/auth/sign-in-form-labels.ts +1 -1
  559. package/src/auth/status-parts.tsx +0 -6
  560. package/src/auth/verify-email-labels.ts +1 -1
  561. package/src/auth/verify-email.tsx +11 -6
  562. package/src/billing/cancel-subscription.tsx +15 -12
  563. package/src/billing/plan-card.tsx +18 -10
  564. package/src/billing/plan-change-labels.ts +66 -0
  565. package/src/billing/plan-switch.tsx +15 -12
  566. package/src/chart.ts +73 -6
  567. package/src/components/account-chips.tsx +20 -23
  568. package/src/components/alert-banner.tsx +82 -5
  569. package/src/components/amount-input.tsx +18 -7
  570. package/src/components/autocomplete.tsx +64 -70
  571. package/src/components/bulk-action-bar.tsx +70 -2
  572. package/src/components/button-look.ts +5 -5
  573. package/src/components/button.tsx +2 -2
  574. package/src/components/chart-edge.ts +34 -1
  575. package/src/components/chart-figure.tsx +35 -0
  576. package/src/components/chart-zoom-core.tsx +6 -5
  577. package/src/components/chart.tsx +26 -5
  578. package/src/components/combobox-core.tsx +293 -62
  579. package/src/components/combobox.tsx +183 -153
  580. package/src/components/copy-button.tsx +34 -23
  581. package/src/components/country-select.tsx +16 -11
  582. package/src/components/data-table-column-settings.tsx +52 -10
  583. package/src/components/data-table-filters.ts +6 -4
  584. package/src/components/data-table-fit.ts +19 -5
  585. package/src/components/data-table-groups.tsx +1 -1
  586. package/src/components/data-table-head.tsx +32 -22
  587. package/src/components/data-table-mobile.tsx +61 -26
  588. package/src/components/data-table-pagination.tsx +21 -6
  589. package/src/components/data-table-resize.ts +1 -1
  590. package/src/components/data-table-row-actions.tsx +3 -3
  591. package/src/components/data-table-row.tsx +25 -14
  592. package/src/components/data-table-selection.ts +1 -1
  593. package/src/components/data-table-shared.tsx +33 -1
  594. package/src/components/data-table-sort.ts +57 -2
  595. package/src/components/data-table-virtual.ts +32 -8
  596. package/src/components/data-table.tsx +239 -68
  597. package/src/components/date-field.tsx +66 -9
  598. package/src/components/date-picker.tsx +125 -49
  599. package/src/components/date-range-picker.tsx +71 -36
  600. package/src/components/error-boundary.tsx +53 -39
  601. package/src/components/field-base-labels.ts +20 -0
  602. package/src/components/field-base.tsx +13 -7
  603. package/src/components/form-actions.tsx +20 -5
  604. package/src/components/hover-menu.tsx +18 -8
  605. package/src/components/icon-button.tsx +14 -9
  606. package/src/components/inline-confirm.tsx +116 -0
  607. package/src/components/inline-entity-combobox.tsx +197 -153
  608. package/src/components/input.tsx +1 -1
  609. package/src/components/jump-calendar.tsx +30 -146
  610. package/src/components/legal-page.tsx +10 -7
  611. package/src/components/legal.tsx +4 -2
  612. package/src/components/lightbox.tsx +21 -8
  613. package/src/components/list.tsx +10 -4
  614. package/src/components/measured-grid.tsx +8 -4
  615. package/src/components/mini-calendar.tsx +6 -5
  616. package/src/components/modal.tsx +6 -1
  617. package/src/components/month-grid.tsx +229 -0
  618. package/src/components/month-picker.tsx +192 -200
  619. package/src/components/multi-select.tsx +21 -9
  620. package/src/components/nav-pills.tsx +1 -1
  621. package/src/components/picker-sheet.tsx +12 -9
  622. package/src/components/pie-chart-labels.ts +8 -0
  623. package/src/components/pie-chart.tsx +22 -28
  624. package/src/components/popover.tsx +18 -1
  625. package/src/components/row-actions.tsx +2 -1
  626. package/src/components/series-chart-axes.ts +9 -4
  627. package/src/components/series-chart-labels.ts +7 -4
  628. package/src/components/series-chart.tsx +253 -87
  629. package/src/components/share-card.tsx +6 -6
  630. package/src/components/tabs-labels.ts +13 -0
  631. package/src/components/tabs.tsx +2 -2
  632. package/src/components/toggle-legend.tsx +16 -11
  633. package/src/components/translation-review-editor.tsx +6 -5
  634. package/src/components/translation-review.tsx +4 -3
  635. package/src/components/treemap-labels.ts +47 -0
  636. package/src/components/treemap-layout.ts +210 -0
  637. package/src/components/treemap.tsx +780 -138
  638. package/src/components/use-table-state.ts +12 -9
  639. package/src/data-table.ts +1 -4
  640. package/src/demo/demo-countdown.ts +3 -8
  641. package/src/eslint/role-utilities.ts +97 -19
  642. package/src/feedback/feedback-dialog.tsx +30 -19
  643. package/src/feedback/feedback-mutation.ts +20 -0
  644. package/src/feedback/feedback-page-link.ts +90 -0
  645. package/src/feedback/feedback-public-list.tsx +13 -7
  646. package/src/feedback/feedback-replies.tsx +13 -6
  647. package/src/feedback/feedback-row-detail-labels.ts +0 -3
  648. package/src/feedback/feedback-row-detail.tsx +70 -84
  649. package/src/feedback/feedback-status-undo.ts +28 -28
  650. package/src/feedback/feedback-submit-dialog.tsx +2 -2
  651. package/src/feedback/feedback-submit.tsx +21 -7
  652. package/src/feedback/feedback-swipe.tsx +1 -1
  653. package/src/feedback/feedback-table.tsx +86 -38
  654. package/src/feedback/feedback-thread-labels.ts +2 -2
  655. package/src/feedback/feedback-thread.tsx +18 -26
  656. package/src/feedback.ts +89 -6
  657. package/src/hooks/use-focus-trap.ts +8 -24
  658. package/src/hooks/use-hotkey.ts +24 -7
  659. package/src/hooks/use-long-press.ts +16 -2
  660. package/src/hooks/use-media-query.ts +20 -4
  661. package/src/hooks/use-mounted.ts +39 -0
  662. package/src/hooks/use-overlay-history.ts +32 -3
  663. package/src/i18n/defaults.ts +4 -9
  664. package/src/i18n/german.ts +8 -19
  665. package/src/i18n/kit-labels.tsx +9 -11
  666. package/src/i18n/locales/es.ts +8 -19
  667. package/src/i18n/locales/fr.ts +21 -30
  668. package/src/i18n/locales/hu.ts +10 -19
  669. package/src/i18n/locales/it.ts +8 -20
  670. package/src/i18n/locales/zh.ts +9 -19
  671. package/src/i18n/review.ts +12 -2
  672. package/src/i18n/shared-labels.ts +0 -1
  673. package/src/index.ts +57 -49
  674. package/src/landing/routing.tsx +14 -10
  675. package/src/lib/broadcast.ts +52 -0
  676. package/src/lib/date-format.ts +89 -13
  677. package/src/lib/day-format.ts +96 -0
  678. package/src/lib/describe-failure.ts +18 -0
  679. package/src/lib/direction.ts +39 -0
  680. package/src/lib/focus-left.ts +156 -0
  681. package/src/lib/format.ts +5 -17
  682. package/src/lib/http-status.ts +38 -0
  683. package/src/lib/intl-cache.ts +25 -0
  684. package/src/lib/iso-day.ts +13 -0
  685. package/src/lib/lazy-with-fallback.ts +82 -0
  686. package/src/lib/path-pattern.ts +55 -0
  687. package/src/lib/reduced-motion.ts +2 -1
  688. package/src/lib/round.ts +7 -2
  689. package/src/lib/safe-storage.ts +48 -6
  690. package/src/lib/session-refresh.ts +1 -1
  691. package/src/lib/share-format.ts +18 -0
  692. package/src/lib/text-case.ts +19 -0
  693. package/src/lib/text-match.ts +57 -2
  694. package/src/lib/thenable.ts +19 -0
  695. package/src/lib/translation-review.ts +13 -5
  696. package/src/notifications/device-zone-report.ts +1 -1
  697. package/src/notifications/notification-bell.tsx +7 -4
  698. package/src/notifications/notification-inbox.tsx +40 -58
  699. package/src/notifications/notification-parts.tsx +3 -4
  700. package/src/notifications/notification-types-setting.tsx +1 -1
  701. package/src/notifications/notifications-labels.ts +7 -0
  702. package/src/notifications/push-device-setting.tsx +1 -1
  703. package/src/notifications/push-devices-setting.tsx +30 -55
  704. package/src/notifications/quiet-hours-setting.tsx +93 -31
  705. package/src/notifications/unsubscribe-panel.tsx +4 -7
  706. package/src/notifications/use-push-device.ts +76 -10
  707. package/src/offline/query-persistence.ts +35 -3
  708. package/src/rhf/fields.tsx +11 -4
  709. package/src/search/command-palette.tsx +2 -4
  710. package/src/search/global-search.tsx +36 -15
  711. package/src/search/search-index.ts +0 -15
  712. package/src/search/use-command-key.ts +13 -12
  713. package/src/search.ts +10 -1
  714. package/src/settings/use-settings-focus.ts +3 -0
  715. package/src/shell/account-menu-language.tsx +6 -4
  716. package/src/shell/account-menu-text-size.tsx +10 -7
  717. package/src/shell/app-shell.tsx +0 -13
  718. package/src/shell/company-switcher.tsx +1 -1
  719. package/src/shell/dev-menu.tsx +8 -22
  720. package/src/shell/top-bar-actions.tsx +1 -1
  721. package/src/shell/top-bar-parts-labels.ts +1 -4
  722. package/src/shell.ts +21 -17
  723. package/src/sync/engine.ts +339 -61
  724. package/src/sync.ts +2 -8
  725. package/src/testing/css-variables.ts +8 -9
  726. package/src/theme/chart-palette.ts +0 -2
  727. package/src/theme/contrast.ts +8 -9
  728. package/src/theme/palette-store.ts +9 -9
  729. package/src/theme/text-size.ts +6 -8
  730. package/src/theme/theme-store.ts +7 -9
  731. package/src/tour/tour-catalogue.tsx +26 -15
  732. package/src/tour/tour-dock.ts +0 -2
  733. package/src/tour/tour-gates.ts +23 -0
  734. package/src/tour/tour-overlay.tsx +1170 -0
  735. package/src/tour/tour.tsx +108 -1202
  736. package/src/tour/use-tour-progress.ts +1 -1
  737. package/src/wizard/stepper-nav.tsx +3 -1
  738. package/src/wizard/types.ts +11 -2
  739. package/src/wizard/use-wizard.ts +2 -1
  740. package/src/wizard/validation.ts +8 -2
  741. package/tokens.css +6 -5
  742. package/dist/components/field-sync-labels.d.ts +0 -2
  743. package/dist/components/field-sync-labels.js +0 -12
  744. package/dist/components/field-sync-labels.js.map +0 -1
  745. package/dist/components/field-sync.d.ts +0 -164
  746. package/dist/components/field-sync.js +0 -269
  747. package/dist/components/field-sync.js.map +0 -1
  748. package/dist/components/section-nav.d.ts +0 -37
  749. package/dist/components/section-nav.js +0 -33
  750. package/dist/components/section-nav.js.map +0 -1
  751. package/dist/components/sign-chip-labels.d.ts +0 -2
  752. package/dist/components/sign-chip-labels.js +0 -11
  753. package/dist/components/sign-chip-labels.js.map +0 -1
  754. package/dist/components/sign-chip.d.ts +0 -68
  755. package/dist/components/sign-chip.js +0 -47
  756. package/dist/components/sign-chip.js.map +0 -1
  757. package/dist/components/ui-labels.js.map +0 -1
  758. package/dist/lib/isin.d.ts +0 -28
  759. package/dist/lib/isin.js +0 -36
  760. package/dist/lib/isin.js.map +0 -1
  761. package/dist/shell/dev-switchers.d.ts +0 -6
  762. package/dist/shell/dev-switchers.js +0 -12
  763. package/dist/shell/dev-switchers.js.map +0 -1
  764. package/dist/shell/role-switcher.d.ts +0 -21
  765. package/dist/shell/role-switcher.js +0 -31
  766. package/dist/shell/role-switcher.js.map +0 -1
  767. package/src/components/field-sync-labels.ts +0 -13
  768. package/src/components/field-sync.tsx +0 -482
  769. package/src/components/section-nav.tsx +0 -65
  770. package/src/components/sign-chip-labels.ts +0 -12
  771. package/src/components/sign-chip.tsx +0 -124
  772. package/src/components/ui-labels.ts +0 -24
  773. package/src/lib/isin.ts +0 -62
  774. package/src/shell/dev-switchers.tsx +0 -18
  775. package/src/shell/role-switcher.tsx +0 -46
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/chart-zoom-core.tsx"],"sourcesContent":["// Drag-to-zoom for cartesian charts — the HOC, the shared x window, and the arithmetic.\n// Lifted out of lenkbank, where every plot in the app is one `SeriesChart` and zoom is\n// therefore wired in exactly once.\n//\n// **Which axes a drag zooms is read off the drag itself.** A selection that is roughly\n// square is a box: the reader framed a REGION and means both axes. A long thin one is a\n// band, and its own direction says which: a wide flat drag means \"this stretch of the\n// abscissa\", a tall narrow one \"this range of values\". Asking with a modifier key\n// instead would be asking people to remember a convention for something they have\n// already drawn.\n//\n// Squareness is measured **in fractions of the plot**, not pixels. These charts are\n// three times wider than they are tall, so a pixel-square selection is a very wide\n// slice of the data and would read as a band to anybody looking at it.\n//\n// The zoom is VIEW state and lives here, never in a query or a store: it is where\n// somebody is looking, and a chart whose data reloads should not lose it. It is cleared\n// by the reset button, by a double click, and by nothing else.\n//\n// Everything with a decision in it is a pure exported function below (`zoomAxesFor`,\n// `selectionFromDrag`, `zoomAfter`, `zoomDomains`, `fitYToX`, `fitXToY`); the\n// components are plumbing between a pointer and those.\n//\n// The kit's own module (0.34, refactor audit 2026-10, API-6b): SeriesChart, its axes and\n// the facing pair import from here. ./chart-zoom is the public face — `SharedXZoom`, the\n// types, and until 0.35 the deprecated arithmetic and HOC, which no app imports.\nimport { createContext, useContext, useEffect, useRef, useState } from \"react\";\nimport type { ComponentType, Dispatch, ReactNode, SetStateAction } from \"react\";\nimport { usePlotArea, useXAxisInverseScale, useYAxisInverseScale, type InverseScaleFunction } from \"recharts\";\nimport { X } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { FOCUS_RING } from \"./focus-ring\";\nimport { useKitLabels, type LabelOverride } from \"../i18n/kit-labels\";\nimport { DEFAULT_SERIES_CHART_LABELS, type SeriesChartLabels } from \"./series-chart-labels\";\n\n/** The y axis a series lands on when it names none — the single shared scale most\n * charts have. `SeriesChart` and the fit below agree on it by importing this. */\nexport const DEFAULT_Y_AXIS = \"y\";\n\n/* ── The arithmetic ─────────────────────────────────────────────────────── */\n\n/**\n * How square a selection has to be before it counts as a box: the shorter side\n * against the longer, both as fractions of the plot. At 0.5 a selection twice as wide\n * as it is tall is still a box and one three times as wide is a band.\n */\nexport const ZOOM_SQUARE_ENOUGH = 0.5;\n\n/** Below this a drag is a click that moved, not a selection. In fractions of the plot,\n * so it is the same gesture on a phone and on a wall display. */\nexport const ZOOM_MIN_DRAG = 0.02;\n\nexport type ZoomAxes = \"both\" | \"x\" | \"y\";\n\n/** Which axes a drag zooms — or `null` for a drag too small to mean one, or a plot\n * that has not been laid out yet. Direction does not matter; proportion does. */\nexport function zoomAxesFor(\n dx: number,\n dy: number,\n width: number,\n height: number,\n threshold: number = ZOOM_SQUARE_ENOUGH,\n /** A chart that zooms ONE axis only (see {@link ZoomTarget.zoomAxes}): every drag is\n * a band along it, however it was drawn, and a drag with no extent along it is\n * nothing — a tall narrow drag over a bar chart names no stretch of the abscissa. */\n only?: \"x\" | \"y\",\n): ZoomAxes | null {\n if (!(width > 0) || !(height > 0)) return null;\n const across = Math.abs(dx) / width;\n const down = Math.abs(dy) / height;\n if (only === \"x\") return across < ZOOM_MIN_DRAG ? null : \"x\";\n if (only === \"y\") return down < ZOOM_MIN_DRAG ? null : \"y\";\n if (across < ZOOM_MIN_DRAG && down < ZOOM_MIN_DRAG) return null;\n const longer = Math.max(across, down);\n const shorter = Math.min(across, down);\n if (shorter / longer >= threshold) return \"both\";\n return across > down ? \"x\" : \"y\";\n}\n\n/** What a drag chose, in DATA coordinates, and which axes it actually named — the\n * caller needs the second to know which of the two it may derive from the other. */\nexport interface ZoomSelection {\n which: ZoomAxes;\n x?: [number, number];\n /** One window per y axis, keyed by axis id: they are in different units. */\n y: Record<string, [number, number]>;\n}\n\n/** A drag, in the chart's own pixel coordinates. */\nexport interface ZoomDrag {\n fromX: number;\n fromY: number;\n toX: number;\n toY: number;\n}\n\n/** A pair as a domain, smallest first. Recharts wants an ascending domain whichever\n * way the drag went, and a y axis counts downward in pixels while its values count\n * upward — so both ends are read and sorted rather than one assumed to be low. */\nfunction ordered(a: number, b: number): [number, number] {\n return a <= b ? [a, b] : [b, a];\n}\n\n/**\n * A finished drag as a selection, or `null` when it selects nothing.\n *\n * `xInverse` and `yInverse(axisId)` are recharts' pixel→value functions; an axis whose\n * function is missing, or whose ends come back non-finite, is left out rather than\n * handed a NaN window.\n */\nexport function selectionFromDrag(\n drag: ZoomDrag,\n plot: { width: number; height: number },\n xInverse: InverseScaleFunction | undefined,\n yInverse: (axisId: string) => InverseScaleFunction | undefined,\n axisIds: readonly string[],\n only?: \"x\" | \"y\",\n): ZoomSelection | null {\n const which = zoomAxesFor(\n drag.toX - drag.fromX,\n drag.toY - drag.fromY,\n plot.width,\n plot.height,\n ZOOM_SQUARE_ENOUGH,\n only,\n );\n if (!which) return null;\n const next: ZoomSelection = { which, y: {} };\n if (which !== \"y\" && xInverse) {\n const from = Number(xInverse(drag.fromX));\n const to = Number(xInverse(drag.toX));\n if (Number.isFinite(from) && Number.isFinite(to)) next.x = ordered(from, to);\n }\n if (which !== \"x\") {\n for (const id of axisIds) {\n const inverse = yInverse(id);\n if (!inverse) continue;\n const from = Number(inverse(drag.fromY));\n const to = Number(inverse(drag.toY));\n if (Number.isFinite(from) && Number.isFinite(to)) next.y[id] = ordered(from, to);\n }\n }\n return next.x || Object.keys(next.y).length ? next : null;\n}\n\n/** One row of a zoomable chart. Loose on purpose: a row also carries what the chart\n * does not plot — keksdose's `rawPeriod` for a drilldown, a label for a category axis —\n * and only the finite NUMBERS in it are ever read as samples. */\nexport type ZoomRow = Readonly<Record<string, unknown>>;\n\n/** A series as the fit sees it. `stack` groups bars/areas drawn on top of each other\n * (recharts' `stackId`), whose extent is their SUM, not any one of them. */\nexport interface ZoomFitSeries {\n key: string;\n axis?: string;\n stack?: string;\n /** Only `\"line\"` matters here: recharts cannot stack a line, so a line's `stack` is\n * ignored rather than summed into a height nothing on the chart reaches. */\n type?: string;\n}\n\n/** What a band refit needs to know about the chart — a subset of `SeriesChart`'s own\n * props, so a chart hands its data straight over. */\nexport interface ZoomFitSource {\n rows: readonly ZoomRow[];\n series: readonly ZoomFitSeries[];\n axes: readonly { id: string }[];\n xKey: string;\n}\n\n/** How much of a refitted span to leave as air, top and bottom. Without it the\n * highest sample sits exactly on the frame, where a line is half drawn and reads as\n * clipped rather than as the maximum. */\nconst FIT_AIR = 0.05;\n\n/** A span with air around it — and a real span for a flat channel, which would\n * otherwise be a zero-height window recharts cannot scale. */\nfunction airy(low: number, high: number): [number, number] {\n const span = high - low;\n const air = span > 0 ? span * FIT_AIR : Math.max(Math.abs(high), 1) * FIT_AIR;\n return [low - air, high + air];\n}\n\n/** A running low/high pair. A pair rather than an array to take `Math.min(...)` of:\n * the spread passes one argument per sample, and V8 faults past ~125,000 of them. */\ntype Span = [number, number] | undefined;\n\n/** A pair widened by one value. A non-finite one is not a value — one NaN would\n * otherwise take the whole window with it. */\nfunction widen(span: Span, value: unknown): Span {\n if (typeof value !== \"number\" || !Number.isFinite(value)) return span;\n if (!span) return [value, value];\n if (value < span[0]) span[0] = value;\n else if (value > span[1]) span[1] = value;\n return span;\n}\n\n/** Which series are measured on an axis. */\nfunction keysOn(source: ZoomFitSource, axisId: string): string[] {\n return source.series.filter((entry) => (entry.axis ?? DEFAULT_Y_AXIS) === axisId).map((entry) => entry.key);\n}\n\n/** A row's value as a sample, or 0 for a stack's missing layer — a stacked bar with no\n * value for one series draws the rest of the stack from where that one would end. */\nconst layer = (value: unknown) => (typeof value === \"number\" && Number.isFinite(value) ? value : 0);\n\n/**\n * The lowest and highest value an axis DRAWS over the rows whose abscissa is inside\n * `window` (every row when there is none) — `undefined` when it draws nothing finite.\n *\n * Stack-aware, and that is why it exists: a stack of three bars reaches the SUM of\n * them, and fitting the axis to the tallest single layer (what fitting on values did)\n * cuts the top two thirds of every stack off. Positive and negative layers are summed\n * apart, as recharts draws them under `stackOffset=\"sign\"` — a month with 900 income\n * and −300 expense stacks up to 900 and down to −300, not to 600. A stacked extent\n * therefore always contains the zero its layers stand on.\n *\n * `SeriesChart` fits its axes with this and the zoom refits them with it, so the two\n * cannot disagree about how tall a stack is.\n */\nexport function axisExtent(\n source: ZoomFitSource,\n axisId: string,\n window?: readonly [number, number],\n): [number, number] | undefined {\n const mine = source.series.filter((entry) => (entry.axis ?? DEFAULT_Y_AXIS) === axisId);\n const loose: string[] = [];\n const stacks = new Map<string, string[]>();\n for (const entry of mine) {\n if (entry.stack === undefined || entry.type === \"line\" || entry.type === undefined) {\n loose.push(entry.key);\n } else {\n stacks.set(entry.stack, [...(stacks.get(entry.stack) ?? []), entry.key]);\n }\n }\n let span: Span;\n for (const row of source.rows) {\n if (window) {\n const at = row[source.xKey];\n if (typeof at !== \"number\" || !(at >= window[0] && at <= window[1])) continue;\n }\n for (const key of loose) span = widen(span, row[key]);\n for (const keys of stacks.values()) {\n let up = 0;\n let down = 0;\n let any = false;\n for (const key of keys) {\n const value = layer(row[key]);\n any ||= typeof row[key] === \"number\" && Number.isFinite(row[key]);\n if (value >= 0) up += value;\n else down += value;\n }\n if (!any) continue;\n span = widen(widen(span, down), up);\n }\n }\n return span;\n}\n\n/**\n * Each y axis fitted to the samples inside an x window — what a band across x leaves\n * the axes nobody dragged showing. Zoom into a tenth of a sweep and a y axis still\n * scaled to the whole run draws that tenth as a flat line, which is the one thing the\n * zoom was for; so the unnamed axes are refitted, which is what \"zoom\" means everywhere\n * else.\n *\n * An axis with nothing inside the window is left OUT rather than collapsed: a window\n * between two samples is one where the chart should keep drawing the line across.\n */\nexport function fitYToX(source: ZoomFitSource, window: readonly [number, number]): Record<string, [number, number]> {\n const fitted: Record<string, [number, number]> = {};\n for (const axis of source.axes) {\n const span = axisExtent(source, axis.id, window);\n if (span) fitted[axis.id] = airy(span[0], span[1]);\n }\n return fitted;\n}\n\n/**\n * The x window that holds every sample inside a set of y windows — the other direction\n * of the same idea. A channel crossing its band once gives a tight window; one that\n * oscillates through it gives back most of the sweep, which is the truthful answer.\n * `undefined` when no sample qualifies, rather than a guess.\n */\nexport function fitXToY(\n source: ZoomFitSource,\n windows: Readonly<Record<string, readonly [number, number]>>,\n): [number, number] | undefined {\n let hits: Span;\n for (const [axisId, [low, high]] of Object.entries(windows)) {\n for (const key of keysOn(source, axisId)) {\n for (const row of source.rows) {\n const value = row[key];\n if (typeof value === \"number\" && Number.isFinite(value) && value >= low && value <= high) {\n hits = widen(hits, row[source.xKey]);\n }\n }\n }\n }\n return hits ? airy(hits[0], hits[1]) : undefined;\n}\n\n/**\n * What a chart remembers about its zoom: the x window, and the y windows the reader\n * DRAGGED. `y: null` means \"derive y from x\" — the split is the whole design: what was\n * dragged is kept, and what follows from it is recomputed on every render, so a band\n * across x refits y here and in every chart sharing the window.\n */\nexport interface ZoomState {\n x?: [number, number];\n y: Record<string, [number, number]> | null;\n}\n\nexport const NO_ZOOM: ZoomState = { x: undefined, y: null };\n\n/**\n * The state after a selection. Two band drags COMPOSE rather than the second undoing\n * the first: a band down y keeps the x window when the band holds no samples, and\n * otherwise pulls x in to where they are.\n */\nexport function zoomAfter(state: ZoomState, selection: ZoomSelection, source: ZoomFitSource): ZoomState {\n if (selection.which === \"both\") return { x: selection.x, y: selection.y };\n // Leave y derived, so it fits itself to what is inside the new window.\n if (selection.which === \"x\") return { x: selection.x, y: null };\n return { x: fitXToY(source, selection.y) ?? state.x, y: selection.y };\n}\n\n/** The domains a chart draws for a state: the x window as is, and each y axis either\n * as dragged or fitted to the x window. `{}` means \"fit your own data\". */\nexport function zoomDomains(\n state: ZoomState,\n source: ZoomFitSource,\n): { xDomain?: [number, number]; yDomains: Record<string, [number, number]> } {\n return {\n xDomain: state.x,\n yDomains: state.y ?? (state.x ? fitYToX(source, state.x) : {}),\n };\n}\n\n/* ── The components ─────────────────────────────────────────────────────── */\n\n/** What the wrapped chart is handed. It applies the domains to its own axes and renders\n * `layer` INSIDE the recharts chart, where recharts' hooks answer. */\nexport interface ZoomBinding {\n xDomain?: [number, number];\n yDomains: Record<string, [number, number]>;\n layer: ReactNode;\n}\n\n/**\n * Which axes a chart lets a drag zoom. `\"none\"` draws no drag layer and no reset.\n * A drag on a one-axis chart is always a band along that axis (see {@link zoomAxesFor}).\n */\nexport type ZoomAxesSetting = ZoomAxes | \"none\";\n\n/**\n * The zoom a chart gets when it does not say, read off what its series ARE.\n *\n * - Only lines: `\"both\"` — lenkbank's measurement plots, unchanged.\n * - Any area: `\"x\"`. An area's height is a length from zero, and a y window that cuts\n * the baseline off draws a band of 40 as three times one of 20 — so its y axis refits\n * to the x window (still from zero), and a drag never names a y range.\n * - Any bar: `\"none\"`. A bar chart is a handful of periods read one bar at a time, and\n * in keksdose every one of them is CLICKED — to drill into a payee, a month, a budget\n * line — which a drag layer over the plot would swallow. A chart of many bars opts\n * back in with `zoomAxes: \"x\"`; a click then reports the period, not the bar.\n */\nexport function defaultZoomAxes(series: readonly { type?: string }[] | undefined): ZoomAxesSetting {\n if (series?.some((entry) => entry.type === \"bar\")) return \"none\";\n if (series?.some((entry) => entry.type === \"area\")) return \"x\";\n return \"both\";\n}\n\n/** The props `withChartZoom` reads off the chart it wraps. */\nexport interface ZoomTarget {\n /** Default: the single axis {@link DEFAULT_Y_AXIS}. */\n axes?: readonly { id: string; hide?: boolean }[];\n /** The chart's own data, so a band can refit the axis it did not name. Optional: a\n * chart with nothing plotted has nothing to fit to. */\n rows?: readonly ZoomRow[];\n series?: readonly ZoomFitSeries[];\n x?: { key?: string };\n zoom?: ZoomBinding;\n labels?: LabelOverride<SeriesChartLabels>;\n /** Which axes a drag may zoom. Default: {@link defaultZoomAxes} of `series`. */\n zoomAxes?: ZoomAxesSetting;\n}\n\nconst SharedX = createContext<{\n window?: [number, number];\n set: Dispatch<SetStateAction<[number, number] | undefined>>;\n} | null>(null);\n\n/**\n * One x window for several charts.\n *\n * A column of charts over one abscissa is one picture, and zooming one of them and not\n * the rest turns it into several pictures of different things. Every zoomable chart\n * inside this shares the x window: any drag in any of them moves all of them.\n *\n * **Only x.** The y axes stay each chart's own — they are in different units — and\n * each refits its own y to what is inside the shared window.\n */\nexport function SharedXZoom({ children }: { children: ReactNode }) {\n const [window, set] = useState<[number, number] | undefined>(undefined);\n return <SharedX.Provider value={{ window, set }}>{children}</SharedX.Provider>;\n}\n\n/**\n * Wrap a chart in drag-to-zoom.\n *\n * The chart receives a `zoom` binding: it applies `xDomain`/`yDomains` to its axes\n * (with `allowDataOverflow`, so recharts clips to the window instead of widening it\n * back out) and renders `layer` as a child of its recharts chart. A reset button\n * appears over the chart's top end corner while it is zoomed — a real button, so the\n * keyboard can undo what the pointer did.\n */\nexport function withChartZoom<P extends ZoomTarget>(Chart: ComponentType<P>) {\n function Zoomable(props: P) {\n const labels = useKitLabels(\"seriesChart\", DEFAULT_SERIES_CHART_LABELS, props.labels);\n const group = useContext(SharedX);\n const [ownX, setOwnX] = useState<[number, number] | undefined>(undefined);\n const [ownY, setOwnY] = useState<Record<string, [number, number]> | null>(null);\n const xWindow = group ? group.window : ownX;\n const setXWindow = group ? group.set : setOwnX;\n const axes = props.axes ?? [{ id: DEFAULT_Y_AXIS }];\n const setting = props.zoomAxes ?? defaultZoomAxes(props.series);\n const only = setting === \"x\" || setting === \"y\" ? setting : undefined;\n\n // Every axis, hidden ones included: a hidden axis still scales its lines, and a\n // line whose axis did not refit is drawn as a flat streak inside the window.\n const source: ZoomFitSource = {\n rows: props.rows ?? [],\n series: [...(props.series ?? [])],\n axes: [...axes],\n xKey: props.x?.key ?? \"x\",\n };\n const state: ZoomState = { x: xWindow, y: ownY };\n const zoomed = xWindow !== undefined || ownY !== null;\n // A chart handed no rows or no series draws its empty state, not a plot — and a\n // \"Reset zoom\" floating over \"No data\" (every line toggled off while zoomed) offers\n // to undo something the reader cannot see. The zoom itself is KEPT: it is where they\n // were looking, and it applies again the moment a line comes back. Only an array\n // that is there and empty counts; a chart that does not pass its data is not known\n // to be empty.\n const empty = props.rows?.length === 0 || props.series?.length === 0;\n\n const clear = () => {\n setXWindow(undefined);\n setOwnY(null);\n };\n\n // Switched off, the chart still gets a binding — with no window and no layer — so a\n // chart whose series change type mid-life is not remounted by a different tree.\n if (setting === \"none\") {\n return (\n <div className=\"relative\">\n <Chart {...props} zoom={{ yDomains: {}, layer: null }} />\n </div>\n );\n }\n\n const binding: ZoomBinding = {\n ...zoomDomains(state, source),\n layer: (\n <ZoomLayer\n axisIds={axes.map((axis) => axis.id)}\n only={only}\n label={labels.zoomHint}\n onZoom={(selection) => {\n const next = zoomAfter(state, selection, source);\n if (next.x !== xWindow) setXWindow(next.x);\n setOwnY(next.y);\n }}\n onReset={clear}\n />\n ),\n };\n\n return (\n <div className=\"relative\">\n <Chart {...props} zoom={binding} />\n {zoomed && !empty && (\n <button\n type=\"button\"\n onClick={clear}\n className={cn(\n \"absolute end-1 top-1 z-10 inline-flex items-center gap-1 rounded border border-subtle bg-surface/90 px-1.5 py-0.5 text-micro text-muted backdrop-blur transition-colors hover:text-primary\",\n FOCUS_RING,\n )}\n >\n <X aria-hidden className=\"size-3\" /> {labels.resetZoom}\n </button>\n )}\n </div>\n );\n }\n Zoomable.displayName = `withChartZoom(${Chart.displayName ?? Chart.name ?? \"Chart\"})`;\n return Zoomable;\n}\n\n/**\n * The part that lives INSIDE the recharts chart, where recharts will say where the plot\n * is and what value a pixel is.\n *\n * A transparent rectangle over the plot area that reads the drag. It does NOT stop the\n * event: recharts drives its tooltip from the same pointer move bubbling up to its own\n * wrapper, so swallowing it here would trade the tooltip for the zoom.\n */\nfunction ZoomLayer({\n axisIds,\n only,\n label,\n onZoom,\n onReset,\n}: {\n axisIds: string[];\n only?: \"x\" | \"y\";\n label: string;\n onZoom: (selection: ZoomSelection) => void;\n onReset: () => void;\n}) {\n const plot = usePlotArea();\n const xInverse = useXAxisInverseScale();\n // One probe COMPONENT per axis rather than a hook in a loop: the number of axes\n // changes as series are toggled, and a hook count that changes with the data is the\n // one thing React will not forgive.\n const [scales] = useState(() => new Map<string, InverseScaleFunction | undefined>());\n const [drag, setDrag] = useState<ZoomDrag | null>(null);\n // A finished drag is followed by a click on the same element, and a chart with an\n // `onClick` (keksdose opens a drilldown on one) would read the zoom as a pick.\n const draggedRef = useRef(false);\n\n if (!plot || plot.width <= 0 || plot.height <= 0) return null;\n\n const at = (event: { clientX: number; clientY: number; currentTarget: Element }) => {\n // Against the overlay's own screen box, so padding around the container cannot\n // shift every reading — plus the plot's offset, because the scales are in the\n // chart's coordinates rather than the plot's.\n const box = event.currentTarget.getBoundingClientRect();\n return { x: event.clientX - box.left + plot.x, y: event.clientY - box.top + plot.y };\n };\n\n const finish = () => {\n if (!drag) return;\n setDrag(null);\n const selection = selectionFromDrag(drag, plot, xInverse, (id) => scales.get(id), axisIds, only);\n draggedRef.current = selection !== null;\n if (selection) onZoom(selection);\n };\n\n // What the drag would do, shown while it is being made: a band paints the full width\n // or height, so the reader sees it is about to let the other axis refit.\n const which = drag\n ? zoomAxesFor(drag.toX - drag.fromX, drag.toY - drag.fromY, plot.width, plot.height, ZOOM_SQUARE_ENOUGH, only)\n : null;\n\n return (\n <g>\n {axisIds.map((id) => (\n <AxisProbe key={id} axisId={id} scales={scales} />\n ))}\n {drag && which && (\n <rect\n x={which === \"y\" ? plot.x : Math.min(drag.fromX, drag.toX)}\n y={which === \"x\" ? plot.y : Math.min(drag.fromY, drag.toY)}\n width={which === \"y\" ? plot.width : Math.abs(drag.toX - drag.fromX)}\n height={which === \"x\" ? plot.height : Math.abs(drag.toY - drag.fromY)}\n className=\"fill-[var(--brand)]/15 stroke-[var(--brand)]\"\n strokeDasharray=\"4 3\"\n pointerEvents=\"none\"\n />\n )}\n <rect\n x={plot.x}\n y={plot.y}\n width={plot.width}\n height={plot.height}\n // `transparent`, not `none`: an unpainted shape takes no pointer events.\n fill=\"transparent\"\n role=\"application\"\n aria-label={label}\n style={{ cursor: \"crosshair\" }}\n onPointerDown={(event) => {\n if (event.button !== 0) return;\n draggedRef.current = false;\n const point = at(event);\n // Best effort: capture keeps a drag alive past the plot's edge, but a\n // pointer id the environment does not know throws — jsdom refuses them all —\n // and a zoom that threw on pointer-down would be a chart nobody could click.\n try {\n event.currentTarget.setPointerCapture(event.pointerId);\n } catch {\n /* the drag still works; it just stops at the edge of the plot */\n }\n setDrag({ fromX: point.x, fromY: point.y, toX: point.x, toY: point.y });\n }}\n onPointerMove={(event) => {\n if (!drag) return;\n const point = at(event);\n setDrag({ ...drag, toX: point.x, toY: point.y });\n }}\n onPointerUp={finish}\n onPointerCancel={() => setDrag(null)}\n onClick={(event) => {\n if (!draggedRef.current) return;\n draggedRef.current = false;\n event.stopPropagation();\n }}\n onDoubleClick={onReset}\n />\n </g>\n );\n}\n\n/** One axis's pixel→value function, parked where the pointer handler can reach it. */\nfunction AxisProbe({ axisId, scales }: { axisId: string; scales: Map<string, InverseScaleFunction | undefined> }) {\n const inverse = useYAxisInverseScale(axisId);\n useEffect(() => {\n scales.set(axisId, inverse);\n return () => {\n scales.delete(axisId);\n };\n }, [axisId, inverse, scales]);\n return null;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/components/chart-zoom-core.tsx"],"sourcesContent":["// Drag-to-zoom for cartesian charts — the HOC, the shared x window, and the arithmetic.\n// Lifted out of lenkbank, where every plot in the app is one `SeriesChart` and zoom is\n// therefore wired in exactly once.\n//\n// **Which axes a drag zooms is read off the drag itself.** A selection that is roughly\n// square is a box: the reader framed a REGION and means both axes. A long thin one is a\n// band, and its own direction says which: a wide flat drag means \"this stretch of the\n// abscissa\", a tall narrow one \"this range of values\". Asking with a modifier key\n// instead would be asking people to remember a convention for something they have\n// already drawn.\n//\n// Squareness is measured **in fractions of the plot**, not pixels. These charts are\n// three times wider than they are tall, so a pixel-square selection is a very wide\n// slice of the data and would read as a band to anybody looking at it.\n//\n// The zoom is VIEW state and lives here, never in a query or a store: it is where\n// somebody is looking, and a chart whose data reloads should not lose it. It is cleared\n// by the reset button, by a double click, and by nothing else.\n//\n// Everything with a decision in it is a pure exported function below (`zoomAxesFor`,\n// `selectionFromDrag`, `zoomAfter`, `zoomDomains`, `fitYToX`, `fitXToY`); the\n// components are plumbing between a pointer and those.\n//\n// The kit's own module (0.34, refactor audit 2026-10, API-6b): SeriesChart, its axes and\n// the facing pair import from here. ./chart-zoom is the public face — `SharedXZoom`, the\n// types, and until 0.35 the deprecated arithmetic and HOC, which no app imports.\nimport { createContext, useContext, useEffect, useRef, useState } from \"react\";\nimport type { ComponentType, Dispatch, ReactNode, SetStateAction } from \"react\";\nimport { usePlotArea, useXAxisInverseScale, useYAxisInverseScale, type InverseScaleFunction } from \"recharts\";\nimport { X } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { FOCUS_RING } from \"./focus-ring\";\nimport { useKitLabels, type LabelOverride } from \"../i18n/kit-labels\";\nimport { DEFAULT_SERIES_CHART_LABELS, type SeriesChartLabels } from \"./series-chart-labels\";\n\n/** The y axis a series lands on when it names none — the single shared scale most\n * charts have. `SeriesChart` and the fit below agree on it by importing this. */\nexport const DEFAULT_Y_AXIS = \"y\";\n\n/* ── The arithmetic ─────────────────────────────────────────────────────── */\n\n/**\n * How square a selection has to be before it counts as a box: the shorter side\n * against the longer, both as fractions of the plot. At 0.5 a selection twice as wide\n * as it is tall is still a box and one three times as wide is a band.\n */\nexport const ZOOM_SQUARE_ENOUGH = 0.5;\n\n/** Below this a drag is a click that moved, not a selection. In fractions of the plot,\n * so it is the same gesture on a phone and on a wall display. */\nexport const ZOOM_MIN_DRAG = 0.02;\n\nexport type ZoomAxes = \"both\" | \"x\" | \"y\";\n\n/** Which axes a drag zooms — or `null` for a drag too small to mean one, or a plot\n * that has not been laid out yet. Direction does not matter; proportion does. */\nexport function zoomAxesFor(\n dx: number,\n dy: number,\n width: number,\n height: number,\n threshold: number = ZOOM_SQUARE_ENOUGH,\n /** A chart that zooms ONE axis only (see {@link ZoomTarget.zoomAxes}): every drag is\n * a band along it, however it was drawn, and a drag with no extent along it is\n * nothing — a tall narrow drag over a bar chart names no stretch of the abscissa. */\n only?: \"x\" | \"y\",\n): ZoomAxes | null {\n if (!(width > 0) || !(height > 0)) return null;\n const across = Math.abs(dx) / width;\n const down = Math.abs(dy) / height;\n if (only === \"x\") return across < ZOOM_MIN_DRAG ? null : \"x\";\n if (only === \"y\") return down < ZOOM_MIN_DRAG ? null : \"y\";\n if (across < ZOOM_MIN_DRAG && down < ZOOM_MIN_DRAG) return null;\n const longer = Math.max(across, down);\n const shorter = Math.min(across, down);\n if (shorter / longer >= threshold) return \"both\";\n return across > down ? \"x\" : \"y\";\n}\n\n/** What a drag chose, in DATA coordinates, and which axes it actually named — the\n * caller needs the second to know which of the two it may derive from the other. */\nexport interface ZoomSelection {\n which: ZoomAxes;\n x?: [number, number];\n /** One window per y axis, keyed by axis id: they are in different units. */\n y: Record<string, [number, number]>;\n}\n\n/** A drag, in the chart's own pixel coordinates. */\nexport interface ZoomDrag {\n fromX: number;\n fromY: number;\n toX: number;\n toY: number;\n}\n\n/** A pair as a domain, smallest first. Recharts wants an ascending domain whichever\n * way the drag went, and a y axis counts downward in pixels while its values count\n * upward — so both ends are read and sorted rather than one assumed to be low. */\nfunction ordered(a: number, b: number): [number, number] {\n return a <= b ? [a, b] : [b, a];\n}\n\n/**\n * A finished drag as a selection, or `null` when it selects nothing.\n *\n * `xInverse` and `yInverse(axisId)` are recharts' pixel→value functions; an axis whose\n * function is missing, or whose ends come back non-finite, is left out rather than\n * handed a NaN window.\n */\nexport function selectionFromDrag(\n drag: ZoomDrag,\n plot: { width: number; height: number },\n xInverse: InverseScaleFunction | undefined,\n yInverse: (axisId: string) => InverseScaleFunction | undefined,\n axisIds: readonly string[],\n only?: \"x\" | \"y\",\n): ZoomSelection | null {\n const which = zoomAxesFor(\n drag.toX - drag.fromX,\n drag.toY - drag.fromY,\n plot.width,\n plot.height,\n ZOOM_SQUARE_ENOUGH,\n only,\n );\n if (!which) return null;\n const next: ZoomSelection = { which, y: {} };\n if (which !== \"y\" && xInverse) {\n const from = Number(xInverse(drag.fromX));\n const to = Number(xInverse(drag.toX));\n if (Number.isFinite(from) && Number.isFinite(to)) next.x = ordered(from, to);\n }\n if (which !== \"x\") {\n for (const id of axisIds) {\n const inverse = yInverse(id);\n if (!inverse) continue;\n const from = Number(inverse(drag.fromY));\n const to = Number(inverse(drag.toY));\n if (Number.isFinite(from) && Number.isFinite(to)) next.y[id] = ordered(from, to);\n }\n }\n return next.x || Object.keys(next.y).length ? next : null;\n}\n\n/** One row of a zoomable chart. Loose on purpose: a row also carries what the chart\n * does not plot — keksdose's `rawPeriod` for a drilldown, a label for a category axis —\n * and only the finite NUMBERS in it are ever read as samples. */\nexport type ZoomRow = Readonly<Record<string, unknown>>;\n\n/** A series as the fit sees it. `stack` groups bars/areas drawn on top of each other\n * (recharts' `stackId`), whose extent is their SUM, not any one of them. */\nexport interface ZoomFitSeries {\n key: string;\n axis?: string;\n stack?: string;\n /** Only `\"line\"` matters here: recharts cannot stack a line, so a line's `stack` is\n * ignored rather than summed into a height nothing on the chart reaches. */\n type?: string;\n}\n\n/** What a band refit needs to know about the chart — a subset of `SeriesChart`'s own\n * props, so a chart hands its data straight over. */\nexport interface ZoomFitSource {\n rows: readonly ZoomRow[];\n series: readonly ZoomFitSeries[];\n axes: readonly { id: string }[];\n xKey: string;\n}\n\n/** How much of a refitted span to leave as air, top and bottom. Without it the\n * highest sample sits exactly on the frame, where a line is half drawn and reads as\n * clipped rather than as the maximum. */\nconst FIT_AIR = 0.05;\n\n/** A span with air around it — and a real span for a flat channel, which would\n * otherwise be a zero-height window recharts cannot scale. */\nfunction airy(low: number, high: number): [number, number] {\n const span = high - low;\n const air = span > 0 ? span * FIT_AIR : Math.max(Math.abs(high), 1) * FIT_AIR;\n return [low - air, high + air];\n}\n\n/** A running low/high pair. A pair rather than an array to take `Math.min(...)` of:\n * the spread passes one argument per sample, and V8 faults past ~125,000 of them. */\ntype Span = [number, number] | undefined;\n\n/** A pair widened by one value. A non-finite one is not a value — one NaN would\n * otherwise take the whole window with it. */\nfunction widen(span: Span, value: unknown): Span {\n if (typeof value !== \"number\" || !Number.isFinite(value)) return span;\n if (!span) return [value, value];\n if (value < span[0]) span[0] = value;\n else if (value > span[1]) span[1] = value;\n return span;\n}\n\n/** Which series are measured on an axis. */\nfunction keysOn(source: ZoomFitSource, axisId: string): string[] {\n return source.series.filter((entry) => (entry.axis ?? DEFAULT_Y_AXIS) === axisId).map((entry) => entry.key);\n}\n\n/** A row's value as a sample, or 0 for a stack's missing layer — a stacked bar with no\n * value for one series draws the rest of the stack from where that one would end. */\nconst layer = (value: unknown) => (typeof value === \"number\" && Number.isFinite(value) ? value : 0);\n\n/**\n * The lowest and highest value an axis DRAWS over the rows whose abscissa is inside\n * `window` (every row when there is none) — `undefined` when it draws nothing finite.\n *\n * Stack-aware, and that is why it exists: a stack of three bars reaches the SUM of\n * them, and fitting the axis to the tallest single layer (what fitting on values did)\n * cuts the top two thirds of every stack off. Positive and negative layers are summed\n * apart, as recharts draws them under `stackOffset=\"sign\"` — a month with 900 income\n * and −300 expense stacks up to 900 and down to −300, not to 600. A stacked extent\n * therefore always contains the zero its layers stand on.\n *\n * `SeriesChart` fits its axes with this and the zoom refits them with it, so the two\n * cannot disagree about how tall a stack is.\n */\nexport function axisExtent(\n source: ZoomFitSource,\n axisId: string,\n window?: readonly [number, number],\n): [number, number] | undefined {\n const mine = source.series.filter((entry) => (entry.axis ?? DEFAULT_Y_AXIS) === axisId);\n const loose: string[] = [];\n const stacks = new Map<string, string[]>();\n for (const entry of mine) {\n if (entry.stack === undefined || entry.type === \"line\" || entry.type === undefined) {\n loose.push(entry.key);\n } else {\n stacks.set(entry.stack, [...(stacks.get(entry.stack) ?? []), entry.key]);\n }\n }\n let span: Span;\n for (const row of source.rows) {\n if (window) {\n const at = row[source.xKey];\n if (typeof at !== \"number\" || !(at >= window[0] && at <= window[1])) continue;\n }\n for (const key of loose) span = widen(span, row[key]);\n for (const keys of stacks.values()) {\n let up = 0;\n let down = 0;\n let any = false;\n for (const key of keys) {\n const value = layer(row[key]);\n any ||= typeof row[key] === \"number\" && Number.isFinite(row[key]);\n if (value >= 0) up += value;\n else down += value;\n }\n if (!any) continue;\n span = widen(widen(span, down), up);\n }\n }\n return span;\n}\n\n/**\n * Each y axis fitted to the samples inside an x window — what a band across x leaves\n * the axes nobody dragged showing. Zoom into a tenth of a sweep and a y axis still\n * scaled to the whole run draws that tenth as a flat line, which is the one thing the\n * zoom was for; so the unnamed axes are refitted, which is what \"zoom\" means everywhere\n * else.\n *\n * An axis with nothing inside the window is left OUT rather than collapsed: a window\n * between two samples is one where the chart should keep drawing the line across.\n */\nexport function fitYToX(source: ZoomFitSource, window: readonly [number, number]): Record<string, [number, number]> {\n const fitted: Record<string, [number, number]> = {};\n for (const axis of source.axes) {\n const span = axisExtent(source, axis.id, window);\n if (span) fitted[axis.id] = airy(span[0], span[1]);\n }\n return fitted;\n}\n\n/**\n * The x window that holds every sample inside a set of y windows — the other direction\n * of the same idea. A channel crossing its band once gives a tight window; one that\n * oscillates through it gives back most of the sweep, which is the truthful answer.\n * `undefined` when no sample qualifies, rather than a guess.\n */\nexport function fitXToY(\n source: ZoomFitSource,\n windows: Readonly<Record<string, readonly [number, number]>>,\n): [number, number] | undefined {\n let hits: Span;\n for (const [axisId, [low, high]] of Object.entries(windows)) {\n for (const key of keysOn(source, axisId)) {\n for (const row of source.rows) {\n const value = row[key];\n if (typeof value === \"number\" && Number.isFinite(value) && value >= low && value <= high) {\n hits = widen(hits, row[source.xKey]);\n }\n }\n }\n }\n return hits ? airy(hits[0], hits[1]) : undefined;\n}\n\n/**\n * What a chart remembers about its zoom: the x window, and the y windows the reader\n * DRAGGED. `y: null` means \"derive y from x\" — the split is the whole design: what was\n * dragged is kept, and what follows from it is recomputed on every render, so a band\n * across x refits y here and in every chart sharing the window.\n */\nexport interface ZoomState {\n x?: [number, number];\n y: Record<string, [number, number]> | null;\n}\n\nexport const NO_ZOOM: ZoomState = { x: undefined, y: null };\n\n/**\n * The state after a selection. Two band drags COMPOSE rather than the second undoing\n * the first: a band down y keeps the x window when the band holds no samples, and\n * otherwise pulls x in to where they are.\n */\nexport function zoomAfter(state: ZoomState, selection: ZoomSelection, source: ZoomFitSource): ZoomState {\n if (selection.which === \"both\") return { x: selection.x, y: selection.y };\n // Leave y derived, so it fits itself to what is inside the new window.\n if (selection.which === \"x\") return { x: selection.x, y: null };\n return { x: fitXToY(source, selection.y) ?? state.x, y: selection.y };\n}\n\n/** The domains a chart draws for a state: the x window as is, and each y axis either\n * as dragged or fitted to the x window. `{}` means \"fit your own data\". */\nexport function zoomDomains(\n state: ZoomState,\n source: ZoomFitSource,\n): { xDomain?: [number, number]; yDomains: Record<string, [number, number]> } {\n return {\n xDomain: state.x,\n yDomains: state.y ?? (state.x ? fitYToX(source, state.x) : {}),\n };\n}\n\n/* ── The components ─────────────────────────────────────────────────────── */\n\n/** What the wrapped chart is handed. It applies the domains to its own axes and renders\n * `layer` INSIDE the recharts chart, where recharts' hooks answer. */\nexport interface ZoomBinding {\n xDomain?: [number, number];\n yDomains: Record<string, [number, number]>;\n layer: ReactNode;\n}\n\n/**\n * Which axes a chart lets a drag zoom. `\"none\"` draws no drag layer and no reset.\n * A drag on a one-axis chart is always a band along that axis (see {@link zoomAxesFor}).\n */\nexport type ZoomAxesSetting = ZoomAxes | \"none\";\n\n/**\n * The zoom a chart gets when it does not say, read off what its series ARE.\n *\n * - Only lines: `\"both\"` — lenkbank's measurement plots, unchanged.\n * - Any area: `\"x\"`. An area's height is a length from zero, and a y window that cuts\n * the baseline off draws a band of 40 as three times one of 20 — so its y axis refits\n * to the x window (still from zero), and a drag never names a y range.\n * - Any bar: `\"none\"`. A bar chart is a handful of periods read one bar at a time, and\n * in keksdose every one of them is CLICKED — to drill into a payee, a month, a budget\n * line — which a drag layer over the plot would swallow. A chart of many bars opts\n * back in with `zoomAxes: \"x\"`; a click then reports the period, not the bar.\n */\nexport function defaultZoomAxes(series: readonly { type?: string }[] | undefined): ZoomAxesSetting {\n if (series?.some((entry) => entry.type === \"bar\")) return \"none\";\n if (series?.some((entry) => entry.type === \"area\")) return \"x\";\n return \"both\";\n}\n\n/** The props `withChartZoom` reads off the chart it wraps. */\nexport interface ZoomTarget {\n /** Default: the single axis {@link DEFAULT_Y_AXIS}. */\n axes?: readonly { id: string; hide?: boolean }[];\n /** The chart's own data, so a band can refit the axis it did not name. Optional: a\n * chart with nothing plotted has nothing to fit to. */\n rows?: readonly ZoomRow[];\n series?: readonly ZoomFitSeries[];\n x?: { key?: string };\n zoom?: ZoomBinding;\n labels?: LabelOverride<SeriesChartLabels>;\n /** Which axes a drag may zoom. Default: {@link defaultZoomAxes} of `series`. */\n zoomAxes?: ZoomAxesSetting;\n}\n\nconst SharedX = createContext<{\n window?: [number, number];\n set: Dispatch<SetStateAction<[number, number] | undefined>>;\n} | null>(null);\n\n/**\n * One x window for several charts.\n *\n * A column of charts over one abscissa is one picture, and zooming one of them and not\n * the rest turns it into several pictures of different things. Every zoomable chart\n * inside this shares the x window: any drag in any of them moves all of them.\n *\n * **Only x.** The y axes stay each chart's own — they are in different units — and\n * each refits its own y to what is inside the shared window.\n */\nexport function SharedXZoom({ children }: { children: ReactNode }) {\n const [window, set] = useState<[number, number] | undefined>(undefined);\n return <SharedX.Provider value={{ window, set }}>{children}</SharedX.Provider>;\n}\n\n/**\n * Wrap a chart in drag-to-zoom.\n *\n * The chart receives a `zoom` binding: it applies `xDomain`/`yDomains` to its axes\n * (with `allowDataOverflow`, so recharts clips to the window instead of widening it\n * back out) and renders `layer` as a child of its recharts chart. A reset button\n * appears over the chart's top end corner while it is zoomed — a real button, so the\n * keyboard can undo what the pointer did.\n */\nexport function withChartZoom<P extends ZoomTarget>(Chart: ComponentType<P>) {\n function Zoomable(props: P) {\n const labels = useKitLabels(\"seriesChart\", DEFAULT_SERIES_CHART_LABELS, props.labels);\n const group = useContext(SharedX);\n const [ownX, setOwnX] = useState<[number, number] | undefined>(undefined);\n const [ownY, setOwnY] = useState<Record<string, [number, number]> | null>(null);\n const xWindow = group ? group.window : ownX;\n const setXWindow = group ? group.set : setOwnX;\n const axes = props.axes ?? [{ id: DEFAULT_Y_AXIS }];\n const setting = props.zoomAxes ?? defaultZoomAxes(props.series);\n const only = setting === \"x\" || setting === \"y\" ? setting : undefined;\n\n // Every axis, hidden ones included: a hidden axis still scales its lines, and a\n // line whose axis did not refit is drawn as a flat streak inside the window.\n const source: ZoomFitSource = {\n rows: props.rows ?? [],\n series: [...(props.series ?? [])],\n axes: [...axes],\n xKey: props.x?.key ?? \"x\",\n };\n const state: ZoomState = { x: xWindow, y: ownY };\n const zoomed = xWindow !== undefined || ownY !== null;\n // A chart handed no rows or no series draws its empty state, not a plot — and a\n // \"Reset zoom\" floating over \"No data\" (every line toggled off while zoomed) offers\n // to undo something the reader cannot see. The zoom itself is KEPT: it is where they\n // were looking, and it applies again the moment a line comes back. Only an array\n // that is there and empty counts; a chart that does not pass its data is not known\n // to be empty.\n const empty = props.rows?.length === 0 || props.series?.length === 0;\n\n const clear = () => {\n setXWindow(undefined);\n setOwnY(null);\n };\n\n // Switched off, the chart still gets a binding — with no window and no layer — so a\n // chart whose series change type mid-life is not remounted by a different tree.\n if (setting === \"none\") {\n return (\n <div className=\"relative\">\n <Chart {...props} zoom={{ yDomains: {}, layer: null }} />\n </div>\n );\n }\n\n const binding: ZoomBinding = {\n ...zoomDomains(state, source),\n layer: (\n <ZoomLayer\n axisIds={axes.map((axis) => axis.id)}\n only={only}\n onZoom={(selection) => {\n const next = zoomAfter(state, selection, source);\n if (next.x !== xWindow) setXWindow(next.x);\n setOwnY(next.y);\n }}\n onReset={clear}\n />\n ),\n };\n\n return (\n <div className=\"relative\">\n <Chart {...props} zoom={binding} />\n {zoomed && !empty && (\n <button\n type=\"button\"\n onClick={clear}\n className={cn(\n \"absolute end-1 top-1 z-10 inline-flex items-center gap-1 rounded border border-subtle bg-surface/90 px-1.5 py-0.5 text-micro text-muted backdrop-blur transition-colors hover:text-primary\",\n FOCUS_RING,\n )}\n >\n <X aria-hidden className=\"size-3\" /> {labels.resetZoom}\n </button>\n )}\n </div>\n );\n }\n Zoomable.displayName = `withChartZoom(${Chart.displayName ?? Chart.name ?? \"Chart\"})`;\n return Zoomable;\n}\n\n/**\n * The part that lives INSIDE the recharts chart, where recharts will say where the plot\n * is and what value a pixel is.\n *\n * A transparent rectangle over the plot area that reads the drag. It does NOT stop the\n * event: recharts drives its tooltip from the same pointer move bubbling up to its own\n * wrapper, so swallowing it here would trade the tooltip for the zoom.\n *\n * Nothing to assistive technology (0.52, refactor audit 2026-10-30, DSP-5): it was a\n * `role=\"application\"` named by the drag hint, which could not take focus — the one name\n * on an otherwise unnamed chart. The hint is in the chart's description now\n * (`SeriesChart`'s figure), and `data-zoom-surface` is how a test finds the rect.\n */\nfunction ZoomLayer({\n axisIds,\n only,\n onZoom,\n onReset,\n}: {\n axisIds: string[];\n only?: \"x\" | \"y\";\n onZoom: (selection: ZoomSelection) => void;\n onReset: () => void;\n}) {\n const plot = usePlotArea();\n const xInverse = useXAxisInverseScale();\n // One probe COMPONENT per axis rather than a hook in a loop: the number of axes\n // changes as series are toggled, and a hook count that changes with the data is the\n // one thing React will not forgive.\n const [scales] = useState(() => new Map<string, InverseScaleFunction | undefined>());\n const [drag, setDrag] = useState<ZoomDrag | null>(null);\n // A finished drag is followed by a click on the same element, and a chart with an\n // `onClick` (keksdose opens a drilldown on one) would read the zoom as a pick.\n const draggedRef = useRef(false);\n\n if (!plot || plot.width <= 0 || plot.height <= 0) return null;\n\n const at = (event: { clientX: number; clientY: number; currentTarget: Element }) => {\n // Against the overlay's own screen box, so padding around the container cannot\n // shift every reading — plus the plot's offset, because the scales are in the\n // chart's coordinates rather than the plot's.\n const box = event.currentTarget.getBoundingClientRect();\n return { x: event.clientX - box.left + plot.x, y: event.clientY - box.top + plot.y };\n };\n\n const finish = () => {\n if (!drag) return;\n setDrag(null);\n const selection = selectionFromDrag(drag, plot, xInverse, (id) => scales.get(id), axisIds, only);\n draggedRef.current = selection !== null;\n if (selection) onZoom(selection);\n };\n\n // What the drag would do, shown while it is being made: a band paints the full width\n // or height, so the reader sees it is about to let the other axis refit.\n const which = drag\n ? zoomAxesFor(drag.toX - drag.fromX, drag.toY - drag.fromY, plot.width, plot.height, ZOOM_SQUARE_ENOUGH, only)\n : null;\n\n return (\n <g>\n {axisIds.map((id) => (\n <AxisProbe key={id} axisId={id} scales={scales} />\n ))}\n {drag && which && (\n <rect\n x={which === \"y\" ? plot.x : Math.min(drag.fromX, drag.toX)}\n y={which === \"x\" ? plot.y : Math.min(drag.fromY, drag.toY)}\n width={which === \"y\" ? plot.width : Math.abs(drag.toX - drag.fromX)}\n height={which === \"x\" ? plot.height : Math.abs(drag.toY - drag.fromY)}\n className=\"fill-[var(--brand)]/15 stroke-[var(--brand)]\"\n strokeDasharray=\"4 3\"\n pointerEvents=\"none\"\n />\n )}\n <rect\n x={plot.x}\n y={plot.y}\n width={plot.width}\n height={plot.height}\n // `transparent`, not `none`: an unpainted shape takes no pointer events.\n fill=\"transparent\"\n data-zoom-surface=\"\"\n style={{ cursor: \"crosshair\" }}\n onPointerDown={(event) => {\n if (event.button !== 0) return;\n draggedRef.current = false;\n const point = at(event);\n // Best effort: capture keeps a drag alive past the plot's edge, but a\n // pointer id the environment does not know throws — jsdom refuses them all —\n // and a zoom that threw on pointer-down would be a chart nobody could click.\n try {\n event.currentTarget.setPointerCapture(event.pointerId);\n } catch {\n /* the drag still works; it just stops at the edge of the plot */\n }\n setDrag({ fromX: point.x, fromY: point.y, toX: point.x, toY: point.y });\n }}\n onPointerMove={(event) => {\n if (!drag) return;\n const point = at(event);\n setDrag({ ...drag, toX: point.x, toY: point.y });\n }}\n onPointerUp={finish}\n onPointerCancel={() => setDrag(null)}\n onClick={(event) => {\n if (!draggedRef.current) return;\n draggedRef.current = false;\n event.stopPropagation();\n }}\n onDoubleClick={onReset}\n />\n </g>\n );\n}\n\n/** One axis's pixel→value function, parked where the pointer handler can reach it. */\nfunction AxisProbe({ axisId, scales }: { axisId: string; scales: Map<string, InverseScaleFunction | undefined> }) {\n const inverse = useYAxisInverseScale(axisId);\n useEffect(() => {\n scales.set(axisId, inverse);\n return () => {\n scales.delete(axisId);\n };\n }, [axisId, inverse, scales]);\n return null;\n}\n"],"mappings":"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","names":[]}
@@ -9,6 +9,13 @@ export declare function useChart(): ChartConfig;
9
9
  interface ChartContainerProps extends ComponentProps<"div"> {
10
10
  config: ChartConfig;
11
11
  children: ComponentProps<typeof ResponsiveContainer>["children"];
12
+ /**
13
+ * What the chart SHOWS, in a sentence, as its accessible description (0.52): read through
14
+ * `aria-describedby`, after a caller's own ids, and drawn nowhere. Inside the root, so
15
+ * the chart stays the one element its parent lays out. Pair it with `role="figure"` and
16
+ * an `aria-label`, as `Treemap` and `SeriesChart` do.
17
+ */
18
+ description?: string;
12
19
  }
13
20
  /**
14
21
  * The shell every chart in the kit is drawn inside.
@@ -24,7 +31,7 @@ interface ChartContainerProps extends ComponentProps<"div"> {
24
31
  * page's direction like any other markup. A consumer who does want a mirrored
25
32
  * category axis passes `reversed` on its own `XAxis`.
26
33
  */
27
- export declare function ChartContainer({ id, className, children, config, ...props }: ChartContainerProps): import("react").JSX.Element;
34
+ export declare function ChartContainer({ id, className, children, config, description, ...props }: ChartContainerProps): import("react").JSX.Element;
28
35
  /**
29
36
  * recharts' `Tooltip`, with `filterNull` defaulting to **false**.
30
37
  *
@@ -4,6 +4,7 @@ import { createContext, useContext, useEffect, useId, useLayoutEffect, useRef, u
4
4
  import { Legend as RechartsLegend, ResponsiveContainer, Tooltip as RechartsTooltip } from "recharts";
5
5
  import { cn } from "../lib/cn.js";
6
6
  import { isRtl } from "../lib/direction.js";
7
+ import { useFigureDescription } from "./chart-figure.js";
7
8
  import { TYPOGRAPHIC_MINUS } from "../lib/minus.js";
8
9
  const ChartContext = createContext(null);
9
10
  function useChart() {
@@ -11,8 +12,9 @@ function useChart() {
11
12
  if (!ctx) throw new Error("useChart must be used within a <ChartContainer />");
12
13
  return ctx;
13
14
  }
14
- function ChartContainer({ id, className, children, config, ...props }) {
15
+ function ChartContainer({ id, className, children, config, description, ...props }) {
15
16
  const uid = useId();
17
+ const about = useFigureDescription(description, props["aria-describedby"]);
16
18
  const chartId = `chart-${(id ?? uid).replace(/[^A-Za-z0-9_-]/g, "")}`;
17
19
  return /* @__PURE__ */ jsx(ChartContext.Provider, { value: config, children: /* @__PURE__ */ jsxs(
18
20
  "div",
@@ -38,9 +40,11 @@ function ChartContainer({ id, className, children, config, ...props }) {
38
40
  className
39
41
  ),
40
42
  ...props,
43
+ "aria-describedby": about.describedBy,
41
44
  children: [
42
45
  /* @__PURE__ */ jsx(ChartStyle, { id: chartId, config }),
43
- /* @__PURE__ */ jsx(ResponsiveContainer, { width: "100%", height: "100%", children })
46
+ /* @__PURE__ */ jsx(ResponsiveContainer, { width: "100%", height: "100%", children }),
47
+ about.node
44
48
  ]
45
49
  }
46
50
  ) });
@@ -226,16 +230,16 @@ function ChartLegendContent({ payload, onItemClick, hiddenKeys, activeKey }) {
226
230
  const label = series?.label ?? vv ?? cfgKey;
227
231
  const off = hidden?.has(cfgKey) ?? false;
228
232
  const dimmed = off || activeKey != null && activeKey !== cfgKey;
233
+ const cue = off ? "line-through" : dimmed ? "text-muted" : void 0;
229
234
  const className = cn(
230
235
  "flex items-center gap-1.5 text-secondary transition-opacity",
231
- onItemClick ? "cursor-pointer hover:opacity-80" : "cursor-default",
232
- dimmed && "opacity-40"
236
+ onItemClick ? "cursor-pointer hover:opacity-80" : "cursor-default"
233
237
  );
234
238
  const swatch = /* @__PURE__ */ jsx(
235
239
  "span",
236
240
  {
237
241
  "aria-hidden": true,
238
- className: "h-2.5 w-2.5 shrink-0 rounded-[3px]",
242
+ className: cn("h-2.5 w-2.5 shrink-0 rounded-[3px]", dimmed && "opacity-40"),
239
243
  style: { backgroundColor: color }
240
244
  }
241
245
  );
@@ -248,14 +252,19 @@ function ChartLegendContent({ payload, onItemClick, hiddenKeys, activeKey }) {
248
252
  className,
249
253
  children: [
250
254
  swatch,
251
- /* @__PURE__ */ jsx("span", { className: cn(off && "line-through"), children: label })
255
+ /* @__PURE__ */ jsx("span", { className: cue, children: label })
252
256
  ]
253
257
  },
254
258
  `${cfgKey}-${i}`
255
- ) : /* @__PURE__ */ jsxs("span", { className, children: [
256
- swatch,
257
- label
258
- ] }, `${cfgKey}-${i}`);
259
+ ) : (
260
+ // On the entry, not round the label: the text is its own, so the key's DOM is the
261
+ // same with a cue as without. A line-through reaches the text only (the swatch
262
+ // holds none), and the ink colours no swatch.
263
+ /* @__PURE__ */ jsxs("span", { className: cn(className, cue), children: [
264
+ swatch,
265
+ label
266
+ ] }, `${cfgKey}-${i}`)
267
+ );
259
268
  }) });
260
269
  }
261
270
  export {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/chart.tsx"],"sourcesContent":["// shadcn/ui chart kit — vendored (MIT) and adapted for this app:\n// - Recharts 3 instead of 2\n// - the original keys its styling off shadcn theme tokens (--muted-foreground,\n// --border, bg-background …). This app has no shadcn token layer, so those\n// are mapped onto this kit's own tokens (--text-muted, --border, --bg-surface\n// …), which follow the active palette preset and flip with the theme by\n// themselves — hence no `dark:` variants anywhere below.\n// The value on show is the *structure*: config-driven colors injected as CSS\n// vars, muted axis/grid styling, and a polished tooltip/legend — the things\n// that make shadcn charts read as \"designed\" rather than \"default Recharts\".\nimport { createContext, useContext, useEffect, useId, useLayoutEffect, useRef, useState } from \"react\";\nimport type { ComponentProps, CSSProperties, ReactNode, RefObject } from \"react\";\nimport { Legend as RechartsLegend, ResponsiveContainer, Tooltip as RechartsTooltip } from \"recharts\";\nimport { cn } from \"../lib/cn\";\nimport { isRtl } from \"../lib/direction\";\nimport { TYPOGRAPHIC_MINUS } from \"../lib/minus\";\n\nexport interface ChartSeriesConfig {\n label: ReactNode;\n color: string;\n}\n\nexport type ChartConfig = Record<string, ChartSeriesConfig>;\n\nconst ChartContext = createContext<ChartConfig | null>(null);\n\nexport function useChart(): ChartConfig {\n const ctx = useContext(ChartContext);\n if (!ctx) throw new Error(\"useChart must be used within a <ChartContainer />\");\n return ctx;\n}\n\ninterface ChartContainerProps extends ComponentProps<\"div\"> {\n config: ChartConfig;\n children: ComponentProps<typeof ResponsiveContainer>[\"children\"];\n}\n\n/**\n * The shell every chart in the kit is drawn inside.\n *\n * **The plot is not mirrored in RTL.** An x axis is a number line or a timeline, and\n * both run left-to-right in Hebrew and Arabic charts as much as in English ones; a\n * time axis flipped to run right-to-left is the surprise, not the courtesy. So no\n * `reversed` on any axis, and the SVG is pinned to `direction: ltr` — without that it\n * inherits the page's `rtl`, which flips what `text-anchor: end` means, and the tick\n * labels of a left-hand y axis are drawn INTO the plot instead of beside it. Text\n * inside the SVG still shapes by its own script; only the anchoring is physical.\n * The HTML around the plot — legend, tooltip, the zoom reset button — follows the\n * page's direction like any other markup. A consumer who does want a mirrored\n * category axis passes `reversed` on its own `XAxis`.\n */\nexport function ChartContainer({ id, className, children, config, ...props }: ChartContainerProps) {\n const uid = useId();\n // The id is pasted into an UNQUOTED `[data-chart=…]` selector below, so it has to be\n // a CSS identifier: one stray space or `#` and the browser discards the whole rule,\n // taking every series colour with it — silently, because an invalid rule is not an\n // error anywhere. `useId()` gives `:r0:`, and a consumer-supplied id gives whatever\n // they typed, so both are filtered rather than just the colons.\n const chartId = `chart-${(id ?? uid).replace(/[^A-Za-z0-9_-]/g, \"\")}`;\n return (\n <ChartContext.Provider value={config}>\n <div\n data-chart={chartId}\n className={cn(\n // touch-pan-y lets a vertical finger drag still scroll the page while\n // a horizontal drag is handed to recharts for tooltip scrubbing,\n // instead of the page scrolling underneath the gesture (feedback #292).\n \"flex w-full touch-pan-y justify-center text-xs\",\n // physical anchoring for the unmirrored plot (see the doc comment above)\n \"[&_.recharts-surface]:[direction:ltr]\",\n // muted axis labels + gridlines; one class each, since the tokens\n // already carry the light/dark values\n \"[&_.recharts-cartesian-axis-tick_text]:fill-muted\",\n \"[&_.recharts-cartesian-grid_line]:stroke-subtle/70\",\n // hairline hover cursor instead of the heavy default\n \"[&_.recharts-curve.recharts-tooltip-cursor]:stroke-strong\",\n \"[&_.recharts-rectangle.recharts-tooltip-cursor]:fill-[var(--bg-hover)]\",\n // kill the focus outlines & white sector strokes Recharts adds\n \"[&_.recharts-sector]:outline-none [&_.recharts-sector[stroke='#fff']]:stroke-transparent\",\n \"[&_.recharts-layer]:outline-none [&_.recharts-surface]:outline-none [&_.recharts-dot[stroke='#fff']]:stroke-transparent\",\n className,\n )}\n {...props}\n >\n <ChartStyle id={chartId} config={config} />\n <ResponsiveContainer width=\"100%\" height=\"100%\">\n {children}\n </ResponsiveContainer>\n </div>\n </ChartContext.Provider>\n );\n}\n\n/**\n * A config key, which becomes the tail of a custom-property NAME.\n *\n * `dangerouslySetInnerHTML` means what it says: nothing below is escaped by React, and\n * a `ChartConfig` is not necessarily written by hand — kastlan builds one from category\n * rows in its database, so these keys are user data. A key containing `}` closes this\n * rule, and whatever follows it in that key becomes a NEW rule — one that applies to\n * the whole document rather than to this one chart.\n */\nconst CSS_IDENT = /^[A-Za-z_][A-Za-z0-9_-]*$/;\n\n/**\n * A config colour, which becomes a declaration VALUE.\n *\n * Every form a palette actually produces: both hex spellings, the functional notations\n * — with the argument list restricted to the characters those functions can legally\n * contain, so no `;`, no `}` and no nested `url(` — and `var(--token)`, which is what\n * this kit's own `paletteFor`/`CHART_COLORS` return. `var()`'s optional FALLBACK is\n * deliberately not accepted: its second argument is an arbitrary value, i.e. the same\n * hole in a shape that looks safe.\n */\nconst CSS_COLOR = /^(?:#[0-9a-fA-F]{3}|#[0-9a-fA-F]{6}|(?:rgba?|hsla?)\\([0-9A-Za-z.,%/ -]*\\)|var\\(--[A-Za-z0-9_-]+\\))$/;\n\n/**\n * The colour an entry is painted with, or `undefined` when the style block refuses it.\n *\n * The one gate both sides go through: {@link ChartStyle} emits exactly these, and the\n * legend and tooltip swatches show exactly these. They used to read `config[key].color`\n * directly, so an entry the style block had skipped still showed its configured colour\n * in the key — a swatch promising a colour the series was never painted in.\n */\nfunction paintedColor(key: string, entry: ChartSeriesConfig | undefined): string | undefined {\n if (!entry?.color) return undefined;\n return CSS_IDENT.test(key) && CSS_COLOR.test(entry.color) ? entry.color : undefined;\n}\n\n// Inject each series colour as a `--color-<key>` custom property scoped to this\n// chart, so chart JSX can refer to `var(--color-total)` and the legend/tooltip\n// stay in sync from one source of truth.\nfunction ChartStyle({ id, config }: { id: string; config: ChartConfig }) {\n const entries = Object.entries(config).filter(([key, c]) => {\n if (!c.color) return false;\n // Skipping the entry costs that one series its `var(--color-…)` and leaves the rest\n // of the chart painted; emitting it unchecked costs the page. The warning is so the\n // consumer finds out from their console rather than from a colourless bar.\n if (!CSS_IDENT.test(key)) {\n console.warn(`[ui-kit] chart config key is not a CSS identifier, skipped: ${key}`);\n return false;\n }\n if (!CSS_COLOR.test(c.color)) {\n console.warn(`[ui-kit] chart config colour for \"${key}\" is not a colour, skipped: ${c.color}`);\n return false;\n }\n return true;\n });\n if (!entries.length) return null;\n const css = `[data-chart=${id}] {\\n${entries.map(([key, c]) => ` --color-${key}: ${c.color};`).join(\"\\n\")}\\n}`;\n return <style dangerouslySetInnerHTML={{ __html: css }} />;\n}\n\n/**\n * recharts' `Tooltip`, with `filterNull` defaulting to **false**.\n *\n * recharts drops every payload entry whose value is `null`/`undefined` before the\n * content sees it, so the missing-value path {@link ChartTooltipContent} documents —\n * the em dash, `formatValue(undefined)` — never ran under the default: a series with a\n * gap at the cursor simply vanished from the tooltip, which reads as \"this series does\n * not exist here\" rather than \"it has no value here\". Kept, the entry prints \"—\".\n *\n * The other half of that filter — dropping series drawn with `hide` — is redone by\n * `ChartTooltipContent` itself (honouring `includeHidden`), so a legend toggle still\n * takes a series out of the tooltip. Pass `filterNull` to get recharts' behaviour back.\n */\nexport function ChartTooltip(props: ComponentProps<typeof RechartsTooltip>) {\n return <RechartsTooltip filterNull={false} {...props} />;\n}\nexport const ChartLegend = RechartsLegend;\n\ninterface TooltipPayloadItem {\n dataKey?: string | number;\n name?: string | number;\n value?: number | string;\n color?: string;\n /** A series drawn with `hide` — dropped here, see {@link ChartTooltip}. */\n hide?: boolean;\n payload?: Record<string, unknown>;\n}\n\n/**\n * What a series has no value for at this point: a gap in a history, a projection that\n * has not happened yet, a field that did not exist at an earlier capture.\n *\n * An em dash rather than an empty cell, so the row still lines up with its siblings and\n * the reader can see that the series WAS asked and had nothing, rather than wondering\n * whether it was dropped. It is a mark, not a word, so there is nothing here to\n * translate and no label prop to resolve; a caller who wants words passes\n * `formatValue`, which is handed the `undefined` and decides.\n */\nconst NO_VALUE = \"—\";\n\n/**\n * The number this payload item carries, or `undefined` when it carries none.\n *\n * This was `item.value ?? 0`, so every one of the absences above printed a confident\n * \"0\" that appears nowhere in the data — a fabricated figure in a tooltip, which is the\n * one place a reader goes to check an exact number. keksdose found it and wrapped the\n * component rather than trust it. `\"\"` and a non-numeric string are the same absence\n * spelled differently (recharts hands a string value straight through from the data),\n * and are answered the same way.\n */\nfunction tooltipValue(value: number | string | undefined): number | undefined {\n if (value == null || value === \"\") return undefined;\n const n = typeof value === \"string\" ? Number(value) : value;\n return Number.isFinite(n) ? n : undefined;\n}\n\ninterface ChartTooltipContentProps {\n active?: boolean;\n payload?: TooltipPayloadItem[];\n label?: string | number;\n indicator?: \"dot\" | \"line\";\n hideLabel?: boolean;\n labelFormatter?: (label: string | number) => ReactNode;\n /** Formats one series' value for display. NOT called when the series has no value at\n * this point — {@link ChartTooltipContentProps.formatValue} is the one that is. */\n valueFormatter?: (value: number) => ReactNode;\n /** Formats one series' value INCLUDING its absence: handed `undefined` for a gap, a\n * projection that has not happened yet, or a field that did not exist at an earlier\n * capture, and whatever it returns is rendered as-is. Only the caller knows whether\n * that absence reads as a dash, a \"not yet\" or a genuine zero, so this is how it\n * says. Supplying it wins over `valueFormatter` for every value.\n *\n * It sits NEXT to `valueFormatter` rather than widening it because that prop's\n * parameter is contextually typed at every call site there is — `(v) => money(v)` —\n * so widening it to `number | undefined` is a compile error in all three consuming\n * apps, which is a breaking change this module does not get to make by itself. */\n formatValue?: (value: number | undefined) => ReactNode;\n /** Cursor position within the chart, injected by recharts. Used with\n * {@link boundaryRef} to decide whether to flip the tooltip. */\n coordinate?: { x?: number; y?: number };\n /** When set, the tooltip flips to the LEFT of the cursor if it would otherwise\n * spill past the right edge of this (usually horizontally-scrolling) container\n * — e.g. a wide chart whose rightmost bars sat off-screen (feedback #77). */\n boundaryRef?: RefObject<HTMLElement | null>;\n /** Injected by recharts: keep series drawn with `hide` in the tooltip. */\n includeHidden?: boolean;\n /**\n * `\"floating\"` (the default): the box recharts moves with the cursor. `\"readout\"`: the\n * same heading and values as a flat row for a FIXED place outside the plot — a\n * `SeriesChart` whose `tooltip.placement` is `\"above\"`/`\"below\"` portals it there, so\n * a finger on a phone no longer sits on the box it is trying to read (keksdose #359).\n * No border, shadow, width cap or viewport nudge; the values flow in a grid whose\n * column count depends on the row's width alone, so the row is the same height\n * whatever is being pointed at — and, idle, it shows {@link idleKeys} with \"—\".\n */\n variant?: \"floating\" | \"readout\";\n /** Readout only: the series to list while nothing is active, so the row keeps the\n * height it will have once something is. */\n idleKeys?: readonly string[];\n /** Readout only: the heading while nothing is active. Default \"—\". */\n idleLabel?: ReactNode;\n}\n\nexport function ChartTooltipContent({\n active,\n payload,\n label,\n indicator = \"dot\",\n hideLabel = false,\n labelFormatter,\n valueFormatter,\n formatValue,\n coordinate,\n boundaryRef,\n includeHidden = false,\n variant = \"floating\",\n idleKeys,\n idleLabel,\n}: ChartTooltipContentProps) {\n const config = useChart();\n const tipRef = useRef<HTMLDivElement>(null);\n const readout = variant === \"readout\";\n // `ChartTooltip` keeps null values (so they can print \"—\"), which also keeps the\n // series a legend toggle switched off with `hide`; those go here instead.\n const items = includeHidden ? payload : payload?.filter((item) => item.hide !== true);\n const shown = Boolean(active && items?.length);\n // Everything below that measures and moves the box is about a FLOATING one.\n const visible = shown && !readout;\n // Recharts anchors the tooltip at coordinate.x inside the full (scrolled) chart\n // width; subtract the container's scrollLeft to get its on-screen x, then flip\n // left if the tooltip's own width would run past the visible right edge.\n //\n // Both the cursor and the flip are PHYSICAL (the plot is never mirrored, see\n // `ChartContainer`), but an RTL scroller counts `scrollLeft` from its right edge —\n // 0 at the start, negative going left — so there the content's left edge sits\n // `scrollWidth - clientWidth + scrollLeft` px to the left of the visible one.\n //\n // Measured in a layout effect (the DOM is not readable during render), so it commits\n // before paint. `payload` and `label` are deps because they change the tooltip's own\n // width, and recharts hands a fresh `payload` on every tooltip render, so a scroll\n // followed by any re-render is re-measured just as it was when this ran in render.\n const [flip, setFlip] = useState(false);\n const x = coordinate?.x;\n useLayoutEffect(() => {\n const boundary = boundaryRef?.current;\n if (!visible || !boundary || x == null) {\n setFlip(false);\n return;\n }\n const offset = isRtl(boundary)\n ? boundary.scrollWidth - boundary.clientWidth + boundary.scrollLeft\n : boundary.scrollLeft;\n const visibleX = x - offset;\n const tipWidth = tipRef.current?.offsetWidth ?? 160;\n setFlip(visibleX + tipWidth + 12 > boundary.clientWidth);\n }, [visible, x, boundaryRef, payload, label]);\n\n // Whatever placed it — recharts' own rule or the boundary flip above — the box then\n // clamps itself into the viewport, 8px off each edge. The width cap alone did not\n // keep it on screen: on a 390px phone recharts opened a near-full-width tooltip to\n // the RIGHT of a pointer 35% into the plot, and the values ran off the edge\n // (lenkbank, on 0.15.2). Measured after placement, minus the shift it already has,\n // so a re-measure never compounds; an unmeasured box (no layout) is left alone.\n const [nudge, setNudge] = useState(0);\n const nudgeRef = useRef(0);\n // recharts slides its wrapper to each new position with a CSS transition, so the box\n // measured on commit is still where it WAS; measure again once the slide ends.\n const [settled, setSettled] = useState(0);\n useEffect(() => {\n const wrapper = tipRef.current?.closest<HTMLElement>(\".recharts-tooltip-wrapper\");\n if (!visible || !wrapper) return;\n const onEnd = () => setSettled((n) => n + 1);\n wrapper.addEventListener(\"transitionend\", onEnd);\n return () => wrapper.removeEventListener(\"transitionend\", onEnd);\n }, [visible]);\n useLayoutEffect(() => {\n const el = tipRef.current;\n if (!visible || !el) {\n nudgeRef.current = 0;\n setNudge(0);\n return;\n }\n const rect = el.getBoundingClientRect();\n if (rect.width === 0) return;\n const margin = 8;\n const viewport = document.documentElement.clientWidth || window.innerWidth;\n const left = rect.left - nudgeRef.current;\n const right = left + rect.width;\n let next = 0;\n if (right > viewport - margin) next = viewport - margin - right;\n if (left + next < margin) next = margin - left;\n if (next !== nudgeRef.current) {\n nudgeRef.current = next;\n setNudge(next);\n }\n }, [visible, x, payload, label, flip, settled]);\n // One series' line — the floating box stacks them; the readout lays them in a grid,\n // where a long name is cut rather than wrapped so every cell stays one line high.\n const tooltipRow = (item: TooltipPayloadItem, i: number, inRow: boolean) => {\n // Bars/lines/areas identify a series by dataKey; pies/treemaps key off\n // `name` (the nameKey value). Prefer whichever the config knows.\n const dk = item.dataKey != null ? String(item.dataKey) : undefined;\n const nm = item.name != null ? String(item.name) : undefined;\n const cfgKey = dk && config[dk] ? dk : nm && config[nm] ? nm : (dk ?? nm ?? String(i));\n const series = config[cfgKey];\n const fill = typeof item.payload?.fill === \"string\" ? item.payload.fill : undefined;\n // The data point's own `fill` before recharts' `item.color`: a treemap tile,\n // or a bar with a per-cell colour, IS the colour the reader is pointing at,\n // while `item.color` is the series-wide one — every tile's swatch came out\n // `--chart-1` whatever colour the tile was.\n const color = paintedColor(cfgKey, series) ?? fill ?? item.color ?? \"var(--text-muted)\";\n const name = series?.label ?? nm ?? cfgKey;\n const raw = tooltipValue(item.value);\n let text: ReactNode;\n // An idle readout cell has asked nothing yet, so there is no absence for\n // `formatValue` to word — it is the bare mark.\n if (inRow && !shown) text = NO_VALUE;\n else if (formatValue) text = formatValue(raw);\n else if (raw == null) text = NO_VALUE;\n // The bare number's minus is U+2212, as every kit figure's (0.49, KIT-1).\n else text = valueFormatter ? valueFormatter(raw) : String(raw).replace(/-/g, TYPOGRAPHIC_MINUS);\n return (\n <div\n key={`${cfgKey}-${i}`}\n // A readout cell is capped so a wide row does not push its value a column away\n // from its name.\n className={cn(\"flex items-center\", inRow ? \"min-w-0 max-w-[12rem] gap-1.5\" : \"gap-2\")}\n >\n <span\n className={cn(\"shrink-0 rounded-[2px]\", indicator === \"dot\" ? \"h-2.5 w-2.5 rounded-full\" : \"h-2.5 w-1\")}\n style={{ backgroundColor: color }}\n />\n <span className={cn(\"min-w-0 text-muted\", inRow ? \"truncate\" : \"break-words\")}>{name}</span>\n <span\n data-private\n // Isolated, so an RTL page does not turn \"-26.6\" into \"26.6-\": a figure has no\n // strong direction of its own, and one formatted with an RTL mark keeps it.\n dir=\"auto\"\n className=\"ms-auto shrink-0 whitespace-nowrap font-mono font-medium tabular-nums text-primary\"\n >\n {text}\n </span>\n </div>\n );\n };\n\n if (readout) {\n // Idle, the row lists every series with no value — the same cells it will have, so\n // nothing under it moves on the first tap.\n const rows: TooltipPayloadItem[] = shown\n ? items!\n : (idleKeys ?? Object.keys(config)).map((key) => ({ dataKey: key }));\n return (\n <div className=\"text-xs\" data-chart-readout={shown ? \"active\" : \"idle\"}>\n {/* A full 24 px line: the zoom's reset button sits at the top corner of a chart\n whose row is above it, and has to fit beside the heading, not over a value. */}\n <div data-readout-heading className=\"truncate font-medium leading-6 text-primary\">\n {shown && !hideLabel && label != null\n ? labelFormatter\n ? labelFormatter(label)\n : label\n : (idleLabel ?? NO_VALUE)}\n </div>\n <div className=\"grid grid-cols-[repeat(auto-fill,minmax(9rem,1fr))] gap-x-4 gap-y-1\">\n {rows.map((item, i) => tooltipRow(item, i, true))}\n </div>\n </div>\n );\n }\n if (!visible || !items) return null;\n return (\n <div\n ref={tipRef}\n style={\n boundaryRef || nudge\n ? {\n transform: [\n boundaryRef ? (flip ? \"translateX(calc(-100% - 12px))\" : \"translateX(12px)\") : \"\",\n nudge ? `translateX(${nudge}px)` : \"\",\n ]\n .filter(Boolean)\n .join(\" \"),\n }\n : undefined\n }\n // Capped at the viewport: six long series names (lenkbank's German motion\n // labels) made the box wider than a 390px chart, and recharts, which keeps a box\n // inside the plot only when it fits, let it run off the edge with the values cut.\n // Names wrap inside the cap; the values never do.\n className=\"min-w-[9rem] max-w-[min(20rem,calc(100vw-2rem))] rounded-lg border border-subtle bg-surface px-2.5 py-2 text-xs shadow-xl\"\n >\n {!hideLabel && label != null && (\n <div className=\"mb-1.5 font-medium text-primary\">{labelFormatter ? labelFormatter(label) : label}</div>\n )}\n <div className=\"grid gap-1.5\">{items.map((item, i) => tooltipRow(item, i, false))}</div>\n </div>\n );\n}\n\ninterface LegendPayloadItem {\n value?: string;\n dataKey?: string | number;\n color?: string;\n}\n\ninterface ChartLegendContentProps {\n payload?: LegendPayloadItem[];\n onItemClick?: (key: string) => void;\n /**\n * TOGGLE mode (recommended): the series currently switched off. Each entry is a\n * toggle button — pressed while its series is shown — and a hidden one is drawn\n * faded with its label struck through. Several can be off at once; pair with\n * `toggleHidden(hidden, key)` in `onItemClick` and `hide` on the chart's series.\n */\n hiddenKeys?: ReadonlySet<string> | readonly string[];\n /**\n * ISOLATE mode: the one series shown in full, every other entry dimmed. Kept for\n * the charts built on it; a legend where each entry switches its own series is\n * what readers expect from a legend, so prefer `hiddenKeys`.\n */\n activeKey?: string | null;\n}\n\nexport function ChartLegendContent({ payload, onItemClick, hiddenKeys, activeKey }: ChartLegendContentProps) {\n const hidden = hiddenKeys ? new Set(hiddenKeys) : null;\n const config = useChart();\n if (!payload?.length) return null;\n return (\n <div className=\"flex flex-wrap items-center justify-center gap-x-4 gap-y-1 pt-3\">\n {payload.map((item, i) => {\n const dk = item.dataKey != null ? String(item.dataKey) : undefined;\n const vv = item.value != null ? String(item.value) : undefined;\n const cfgKey = dk && config[dk] ? dk : vv && config[vv] ? vv : (dk ?? vv ?? String(i));\n const series = config[cfgKey];\n const color = paintedColor(cfgKey, series) ?? item.color ?? \"var(--text-muted)\";\n const label = series?.label ?? vv ?? cfgKey;\n const off = hidden?.has(cfgKey) ?? false;\n const dimmed = off || (activeKey != null && activeKey !== cfgKey);\n const className = cn(\n \"flex items-center gap-1.5 text-secondary transition-opacity\",\n onItemClick ? \"cursor-pointer hover:opacity-80\" : \"cursor-default\",\n dimmed && \"opacity-40\",\n );\n const swatch = (\n <span\n aria-hidden\n className=\"h-2.5 w-2.5 shrink-0 rounded-[3px]\"\n style={{ backgroundColor: color } as CSSProperties}\n />\n );\n // A legend with no `onItemClick` is a KEY, not a control — and that is every\n // legend in kastlan, where this rendered N focusable buttons per chart that did\n // nothing when activated. A keyboard user paid the tab stops for all of them,\n // and a screen reader announced each one as a button, promising an action there\n // was none of. The interactive branch is unchanged.\n return onItemClick ? (\n <button\n key={`${cfgKey}-${i}`}\n type=\"button\"\n onClick={() => onItemClick(cfgKey)}\n // Toggles report their state: in toggle mode pressed = the series is shown;\n // in isolate mode pressed = the isolated one. With neither supplied the\n // legend knows nothing about state, and a pressed=\"false\" it cannot vouch\n // for would lie — so it says nothing.\n aria-pressed={hidden ? !off : activeKey === undefined ? undefined : activeKey === cfgKey}\n className={className}\n >\n {swatch}\n <span className={cn(off && \"line-through\")}>{label}</span>\n </button>\n ) : (\n <span key={`${cfgKey}-${i}`} className={className}>\n {swatch}\n {label}\n </span>\n );\n })}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/components/chart.tsx"],"sourcesContent":["// shadcn/ui chart kit — vendored (MIT) and adapted for this app:\n// - Recharts 3 instead of 2\n// - the original keys its styling off shadcn theme tokens (--muted-foreground,\n// --border, bg-background …). This app has no shadcn token layer, so those\n// are mapped onto this kit's own tokens (--text-muted, --border, --bg-surface\n// …), which follow the active palette preset and flip with the theme by\n// themselves — hence no `dark:` variants anywhere below.\n// The value on show is the *structure*: config-driven colors injected as CSS\n// vars, muted axis/grid styling, and a polished tooltip/legend — the things\n// that make shadcn charts read as \"designed\" rather than \"default Recharts\".\nimport { createContext, useContext, useEffect, useId, useLayoutEffect, useRef, useState } from \"react\";\nimport type { ComponentProps, CSSProperties, ReactNode, RefObject } from \"react\";\nimport { Legend as RechartsLegend, ResponsiveContainer, Tooltip as RechartsTooltip } from \"recharts\";\nimport { cn } from \"../lib/cn\";\nimport { isRtl } from \"../lib/direction\";\nimport { useFigureDescription } from \"./chart-figure\";\nimport { TYPOGRAPHIC_MINUS } from \"../lib/minus\";\n\nexport interface ChartSeriesConfig {\n label: ReactNode;\n color: string;\n}\n\nexport type ChartConfig = Record<string, ChartSeriesConfig>;\n\nconst ChartContext = createContext<ChartConfig | null>(null);\n\nexport function useChart(): ChartConfig {\n const ctx = useContext(ChartContext);\n if (!ctx) throw new Error(\"useChart must be used within a <ChartContainer />\");\n return ctx;\n}\n\ninterface ChartContainerProps extends ComponentProps<\"div\"> {\n config: ChartConfig;\n children: ComponentProps<typeof ResponsiveContainer>[\"children\"];\n /**\n * What the chart SHOWS, in a sentence, as its accessible description (0.52): read through\n * `aria-describedby`, after a caller's own ids, and drawn nowhere. Inside the root, so\n * the chart stays the one element its parent lays out. Pair it with `role=\"figure\"` and\n * an `aria-label`, as `Treemap` and `SeriesChart` do.\n */\n description?: string;\n}\n\n/**\n * The shell every chart in the kit is drawn inside.\n *\n * **The plot is not mirrored in RTL.** An x axis is a number line or a timeline, and\n * both run left-to-right in Hebrew and Arabic charts as much as in English ones; a\n * time axis flipped to run right-to-left is the surprise, not the courtesy. So no\n * `reversed` on any axis, and the SVG is pinned to `direction: ltr` — without that it\n * inherits the page's `rtl`, which flips what `text-anchor: end` means, and the tick\n * labels of a left-hand y axis are drawn INTO the plot instead of beside it. Text\n * inside the SVG still shapes by its own script; only the anchoring is physical.\n * The HTML around the plot — legend, tooltip, the zoom reset button — follows the\n * page's direction like any other markup. A consumer who does want a mirrored\n * category axis passes `reversed` on its own `XAxis`.\n */\nexport function ChartContainer({ id, className, children, config, description, ...props }: ChartContainerProps) {\n const uid = useId();\n const about = useFigureDescription(description, props[\"aria-describedby\"]);\n // The id is pasted into an UNQUOTED `[data-chart=…]` selector below, so it has to be\n // a CSS identifier: one stray space or `#` and the browser discards the whole rule,\n // taking every series colour with it — silently, because an invalid rule is not an\n // error anywhere. `useId()` gives `:r0:`, and a consumer-supplied id gives whatever\n // they typed, so both are filtered rather than just the colons.\n const chartId = `chart-${(id ?? uid).replace(/[^A-Za-z0-9_-]/g, \"\")}`;\n return (\n <ChartContext.Provider value={config}>\n <div\n data-chart={chartId}\n className={cn(\n // touch-pan-y lets a vertical finger drag still scroll the page while\n // a horizontal drag is handed to recharts for tooltip scrubbing,\n // instead of the page scrolling underneath the gesture (feedback #292).\n \"flex w-full touch-pan-y justify-center text-xs\",\n // physical anchoring for the unmirrored plot (see the doc comment above)\n \"[&_.recharts-surface]:[direction:ltr]\",\n // muted axis labels + gridlines; one class each, since the tokens\n // already carry the light/dark values\n \"[&_.recharts-cartesian-axis-tick_text]:fill-muted\",\n \"[&_.recharts-cartesian-grid_line]:stroke-subtle/70\",\n // hairline hover cursor instead of the heavy default\n \"[&_.recharts-curve.recharts-tooltip-cursor]:stroke-strong\",\n \"[&_.recharts-rectangle.recharts-tooltip-cursor]:fill-[var(--bg-hover)]\",\n // kill the focus outlines & white sector strokes Recharts adds\n \"[&_.recharts-sector]:outline-none [&_.recharts-sector[stroke='#fff']]:stroke-transparent\",\n \"[&_.recharts-layer]:outline-none [&_.recharts-surface]:outline-none [&_.recharts-dot[stroke='#fff']]:stroke-transparent\",\n className,\n )}\n {...props}\n aria-describedby={about.describedBy}\n >\n <ChartStyle id={chartId} config={config} />\n <ResponsiveContainer width=\"100%\" height=\"100%\">\n {children}\n </ResponsiveContainer>\n {about.node}\n </div>\n </ChartContext.Provider>\n );\n}\n\n/**\n * A config key, which becomes the tail of a custom-property NAME.\n *\n * `dangerouslySetInnerHTML` means what it says: nothing below is escaped by React, and\n * a `ChartConfig` is not necessarily written by hand — kastlan builds one from category\n * rows in its database, so these keys are user data. A key containing `}` closes this\n * rule, and whatever follows it in that key becomes a NEW rule — one that applies to\n * the whole document rather than to this one chart.\n */\nconst CSS_IDENT = /^[A-Za-z_][A-Za-z0-9_-]*$/;\n\n/**\n * A config colour, which becomes a declaration VALUE.\n *\n * Every form a palette actually produces: both hex spellings, the functional notations\n * — with the argument list restricted to the characters those functions can legally\n * contain, so no `;`, no `}` and no nested `url(` — and `var(--token)`, which is what\n * this kit's own `paletteFor`/`CHART_COLORS` return. `var()`'s optional FALLBACK is\n * deliberately not accepted: its second argument is an arbitrary value, i.e. the same\n * hole in a shape that looks safe.\n */\nconst CSS_COLOR = /^(?:#[0-9a-fA-F]{3}|#[0-9a-fA-F]{6}|(?:rgba?|hsla?)\\([0-9A-Za-z.,%/ -]*\\)|var\\(--[A-Za-z0-9_-]+\\))$/;\n\n/**\n * The colour an entry is painted with, or `undefined` when the style block refuses it.\n *\n * The one gate both sides go through: {@link ChartStyle} emits exactly these, and the\n * legend and tooltip swatches show exactly these. They used to read `config[key].color`\n * directly, so an entry the style block had skipped still showed its configured colour\n * in the key — a swatch promising a colour the series was never painted in.\n */\nfunction paintedColor(key: string, entry: ChartSeriesConfig | undefined): string | undefined {\n if (!entry?.color) return undefined;\n return CSS_IDENT.test(key) && CSS_COLOR.test(entry.color) ? entry.color : undefined;\n}\n\n// Inject each series colour as a `--color-<key>` custom property scoped to this\n// chart, so chart JSX can refer to `var(--color-total)` and the legend/tooltip\n// stay in sync from one source of truth.\nfunction ChartStyle({ id, config }: { id: string; config: ChartConfig }) {\n const entries = Object.entries(config).filter(([key, c]) => {\n if (!c.color) return false;\n // Skipping the entry costs that one series its `var(--color-…)` and leaves the rest\n // of the chart painted; emitting it unchecked costs the page. The warning is so the\n // consumer finds out from their console rather than from a colourless bar.\n if (!CSS_IDENT.test(key)) {\n console.warn(`[ui-kit] chart config key is not a CSS identifier, skipped: ${key}`);\n return false;\n }\n if (!CSS_COLOR.test(c.color)) {\n console.warn(`[ui-kit] chart config colour for \"${key}\" is not a colour, skipped: ${c.color}`);\n return false;\n }\n return true;\n });\n if (!entries.length) return null;\n const css = `[data-chart=${id}] {\\n${entries.map(([key, c]) => ` --color-${key}: ${c.color};`).join(\"\\n\")}\\n}`;\n return <style dangerouslySetInnerHTML={{ __html: css }} />;\n}\n\n/**\n * recharts' `Tooltip`, with `filterNull` defaulting to **false**.\n *\n * recharts drops every payload entry whose value is `null`/`undefined` before the\n * content sees it, so the missing-value path {@link ChartTooltipContent} documents —\n * the em dash, `formatValue(undefined)` — never ran under the default: a series with a\n * gap at the cursor simply vanished from the tooltip, which reads as \"this series does\n * not exist here\" rather than \"it has no value here\". Kept, the entry prints \"—\".\n *\n * The other half of that filter — dropping series drawn with `hide` — is redone by\n * `ChartTooltipContent` itself (honouring `includeHidden`), so a legend toggle still\n * takes a series out of the tooltip. Pass `filterNull` to get recharts' behaviour back.\n */\nexport function ChartTooltip(props: ComponentProps<typeof RechartsTooltip>) {\n return <RechartsTooltip filterNull={false} {...props} />;\n}\nexport const ChartLegend = RechartsLegend;\n\ninterface TooltipPayloadItem {\n dataKey?: string | number;\n name?: string | number;\n value?: number | string;\n color?: string;\n /** A series drawn with `hide` — dropped here, see {@link ChartTooltip}. */\n hide?: boolean;\n payload?: Record<string, unknown>;\n}\n\n/**\n * What a series has no value for at this point: a gap in a history, a projection that\n * has not happened yet, a field that did not exist at an earlier capture.\n *\n * An em dash rather than an empty cell, so the row still lines up with its siblings and\n * the reader can see that the series WAS asked and had nothing, rather than wondering\n * whether it was dropped. It is a mark, not a word, so there is nothing here to\n * translate and no label prop to resolve; a caller who wants words passes\n * `formatValue`, which is handed the `undefined` and decides.\n */\nconst NO_VALUE = \"—\";\n\n/**\n * The number this payload item carries, or `undefined` when it carries none.\n *\n * This was `item.value ?? 0`, so every one of the absences above printed a confident\n * \"0\" that appears nowhere in the data — a fabricated figure in a tooltip, which is the\n * one place a reader goes to check an exact number. keksdose found it and wrapped the\n * component rather than trust it. `\"\"` and a non-numeric string are the same absence\n * spelled differently (recharts hands a string value straight through from the data),\n * and are answered the same way.\n */\nfunction tooltipValue(value: number | string | undefined): number | undefined {\n if (value == null || value === \"\") return undefined;\n const n = typeof value === \"string\" ? Number(value) : value;\n return Number.isFinite(n) ? n : undefined;\n}\n\ninterface ChartTooltipContentProps {\n active?: boolean;\n payload?: TooltipPayloadItem[];\n label?: string | number;\n indicator?: \"dot\" | \"line\";\n hideLabel?: boolean;\n labelFormatter?: (label: string | number) => ReactNode;\n /** Formats one series' value for display. NOT called when the series has no value at\n * this point — {@link ChartTooltipContentProps.formatValue} is the one that is. */\n valueFormatter?: (value: number) => ReactNode;\n /** Formats one series' value INCLUDING its absence: handed `undefined` for a gap, a\n * projection that has not happened yet, or a field that did not exist at an earlier\n * capture, and whatever it returns is rendered as-is. Only the caller knows whether\n * that absence reads as a dash, a \"not yet\" or a genuine zero, so this is how it\n * says. Supplying it wins over `valueFormatter` for every value.\n *\n * It sits NEXT to `valueFormatter` rather than widening it because that prop's\n * parameter is contextually typed at every call site there is — `(v) => money(v)` —\n * so widening it to `number | undefined` is a compile error in all three consuming\n * apps, which is a breaking change this module does not get to make by itself. */\n formatValue?: (value: number | undefined) => ReactNode;\n /** Cursor position within the chart, injected by recharts. Used with\n * {@link boundaryRef} to decide whether to flip the tooltip. */\n coordinate?: { x?: number; y?: number };\n /** When set, the tooltip flips to the LEFT of the cursor if it would otherwise\n * spill past the right edge of this (usually horizontally-scrolling) container\n * — e.g. a wide chart whose rightmost bars sat off-screen (feedback #77). */\n boundaryRef?: RefObject<HTMLElement | null>;\n /** Injected by recharts: keep series drawn with `hide` in the tooltip. */\n includeHidden?: boolean;\n /**\n * `\"floating\"` (the default): the box recharts moves with the cursor. `\"readout\"`: the\n * same heading and values as a flat row for a FIXED place outside the plot — a\n * `SeriesChart` whose `tooltip.placement` is `\"above\"`/`\"below\"` portals it there, so\n * a finger on a phone no longer sits on the box it is trying to read (keksdose #359).\n * No border, shadow, width cap or viewport nudge; the values flow in a grid whose\n * column count depends on the row's width alone, so the row is the same height\n * whatever is being pointed at — and, idle, it shows {@link idleKeys} with \"—\".\n */\n variant?: \"floating\" | \"readout\";\n /** Readout only: the series to list while nothing is active, so the row keeps the\n * height it will have once something is. */\n idleKeys?: readonly string[];\n /** Readout only: the heading while nothing is active. Default \"—\". */\n idleLabel?: ReactNode;\n}\n\nexport function ChartTooltipContent({\n active,\n payload,\n label,\n indicator = \"dot\",\n hideLabel = false,\n labelFormatter,\n valueFormatter,\n formatValue,\n coordinate,\n boundaryRef,\n includeHidden = false,\n variant = \"floating\",\n idleKeys,\n idleLabel,\n}: ChartTooltipContentProps) {\n const config = useChart();\n const tipRef = useRef<HTMLDivElement>(null);\n const readout = variant === \"readout\";\n // `ChartTooltip` keeps null values (so they can print \"—\"), which also keeps the\n // series a legend toggle switched off with `hide`; those go here instead.\n const items = includeHidden ? payload : payload?.filter((item) => item.hide !== true);\n const shown = Boolean(active && items?.length);\n // Everything below that measures and moves the box is about a FLOATING one.\n const visible = shown && !readout;\n // Recharts anchors the tooltip at coordinate.x inside the full (scrolled) chart\n // width; subtract the container's scrollLeft to get its on-screen x, then flip\n // left if the tooltip's own width would run past the visible right edge.\n //\n // Both the cursor and the flip are PHYSICAL (the plot is never mirrored, see\n // `ChartContainer`), but an RTL scroller counts `scrollLeft` from its right edge —\n // 0 at the start, negative going left — so there the content's left edge sits\n // `scrollWidth - clientWidth + scrollLeft` px to the left of the visible one.\n //\n // Measured in a layout effect (the DOM is not readable during render), so it commits\n // before paint. `payload` and `label` are deps because they change the tooltip's own\n // width, and recharts hands a fresh `payload` on every tooltip render, so a scroll\n // followed by any re-render is re-measured just as it was when this ran in render.\n const [flip, setFlip] = useState(false);\n const x = coordinate?.x;\n useLayoutEffect(() => {\n const boundary = boundaryRef?.current;\n if (!visible || !boundary || x == null) {\n setFlip(false);\n return;\n }\n const offset = isRtl(boundary)\n ? boundary.scrollWidth - boundary.clientWidth + boundary.scrollLeft\n : boundary.scrollLeft;\n const visibleX = x - offset;\n const tipWidth = tipRef.current?.offsetWidth ?? 160;\n setFlip(visibleX + tipWidth + 12 > boundary.clientWidth);\n }, [visible, x, boundaryRef, payload, label]);\n\n // Whatever placed it — recharts' own rule or the boundary flip above — the box then\n // clamps itself into the viewport, 8px off each edge. The width cap alone did not\n // keep it on screen: on a 390px phone recharts opened a near-full-width tooltip to\n // the RIGHT of a pointer 35% into the plot, and the values ran off the edge\n // (lenkbank, on 0.15.2). Measured after placement, minus the shift it already has,\n // so a re-measure never compounds; an unmeasured box (no layout) is left alone.\n const [nudge, setNudge] = useState(0);\n const nudgeRef = useRef(0);\n // recharts slides its wrapper to each new position with a CSS transition, so the box\n // measured on commit is still where it WAS; measure again once the slide ends.\n const [settled, setSettled] = useState(0);\n useEffect(() => {\n const wrapper = tipRef.current?.closest<HTMLElement>(\".recharts-tooltip-wrapper\");\n if (!visible || !wrapper) return;\n const onEnd = () => setSettled((n) => n + 1);\n wrapper.addEventListener(\"transitionend\", onEnd);\n return () => wrapper.removeEventListener(\"transitionend\", onEnd);\n }, [visible]);\n useLayoutEffect(() => {\n const el = tipRef.current;\n if (!visible || !el) {\n nudgeRef.current = 0;\n setNudge(0);\n return;\n }\n const rect = el.getBoundingClientRect();\n if (rect.width === 0) return;\n const margin = 8;\n const viewport = document.documentElement.clientWidth || window.innerWidth;\n const left = rect.left - nudgeRef.current;\n const right = left + rect.width;\n let next = 0;\n if (right > viewport - margin) next = viewport - margin - right;\n if (left + next < margin) next = margin - left;\n if (next !== nudgeRef.current) {\n nudgeRef.current = next;\n setNudge(next);\n }\n }, [visible, x, payload, label, flip, settled]);\n // One series' line — the floating box stacks them; the readout lays them in a grid,\n // where a long name is cut rather than wrapped so every cell stays one line high.\n const tooltipRow = (item: TooltipPayloadItem, i: number, inRow: boolean) => {\n // Bars/lines/areas identify a series by dataKey; pies/treemaps key off\n // `name` (the nameKey value). Prefer whichever the config knows.\n const dk = item.dataKey != null ? String(item.dataKey) : undefined;\n const nm = item.name != null ? String(item.name) : undefined;\n const cfgKey = dk && config[dk] ? dk : nm && config[nm] ? nm : (dk ?? nm ?? String(i));\n const series = config[cfgKey];\n const fill = typeof item.payload?.fill === \"string\" ? item.payload.fill : undefined;\n // The data point's own `fill` before recharts' `item.color`: a treemap tile,\n // or a bar with a per-cell colour, IS the colour the reader is pointing at,\n // while `item.color` is the series-wide one — every tile's swatch came out\n // `--chart-1` whatever colour the tile was.\n const color = paintedColor(cfgKey, series) ?? fill ?? item.color ?? \"var(--text-muted)\";\n const name = series?.label ?? nm ?? cfgKey;\n const raw = tooltipValue(item.value);\n let text: ReactNode;\n // An idle readout cell has asked nothing yet, so there is no absence for\n // `formatValue` to word — it is the bare mark.\n if (inRow && !shown) text = NO_VALUE;\n else if (formatValue) text = formatValue(raw);\n else if (raw == null) text = NO_VALUE;\n // The bare number's minus is U+2212, as every kit figure's (0.49, KIT-1).\n else text = valueFormatter ? valueFormatter(raw) : String(raw).replace(/-/g, TYPOGRAPHIC_MINUS);\n return (\n <div\n key={`${cfgKey}-${i}`}\n // A readout cell is capped so a wide row does not push its value a column away\n // from its name.\n className={cn(\"flex items-center\", inRow ? \"min-w-0 max-w-[12rem] gap-1.5\" : \"gap-2\")}\n >\n <span\n className={cn(\"shrink-0 rounded-[2px]\", indicator === \"dot\" ? \"h-2.5 w-2.5 rounded-full\" : \"h-2.5 w-1\")}\n style={{ backgroundColor: color }}\n />\n <span className={cn(\"min-w-0 text-muted\", inRow ? \"truncate\" : \"break-words\")}>{name}</span>\n <span\n data-private\n // Isolated, so an RTL page does not turn \"-26.6\" into \"26.6-\": a figure has no\n // strong direction of its own, and one formatted with an RTL mark keeps it.\n dir=\"auto\"\n className=\"ms-auto shrink-0 whitespace-nowrap font-mono font-medium tabular-nums text-primary\"\n >\n {text}\n </span>\n </div>\n );\n };\n\n if (readout) {\n // Idle, the row lists every series with no value — the same cells it will have, so\n // nothing under it moves on the first tap.\n const rows: TooltipPayloadItem[] = shown\n ? items!\n : (idleKeys ?? Object.keys(config)).map((key) => ({ dataKey: key }));\n return (\n <div className=\"text-xs\" data-chart-readout={shown ? \"active\" : \"idle\"}>\n {/* A full 24 px line: the zoom's reset button sits at the top corner of a chart\n whose row is above it, and has to fit beside the heading, not over a value. */}\n <div data-readout-heading className=\"truncate font-medium leading-6 text-primary\">\n {shown && !hideLabel && label != null\n ? labelFormatter\n ? labelFormatter(label)\n : label\n : (idleLabel ?? NO_VALUE)}\n </div>\n <div className=\"grid grid-cols-[repeat(auto-fill,minmax(9rem,1fr))] gap-x-4 gap-y-1\">\n {rows.map((item, i) => tooltipRow(item, i, true))}\n </div>\n </div>\n );\n }\n if (!visible || !items) return null;\n return (\n <div\n ref={tipRef}\n style={\n boundaryRef || nudge\n ? {\n transform: [\n boundaryRef ? (flip ? \"translateX(calc(-100% - 12px))\" : \"translateX(12px)\") : \"\",\n nudge ? `translateX(${nudge}px)` : \"\",\n ]\n .filter(Boolean)\n .join(\" \"),\n }\n : undefined\n }\n // Capped at the viewport: six long series names (lenkbank's German motion\n // labels) made the box wider than a 390px chart, and recharts, which keeps a box\n // inside the plot only when it fits, let it run off the edge with the values cut.\n // Names wrap inside the cap; the values never do.\n className=\"min-w-[9rem] max-w-[min(20rem,calc(100vw-2rem))] rounded-lg border border-subtle bg-surface px-2.5 py-2 text-xs shadow-xl\"\n >\n {!hideLabel && label != null && (\n <div className=\"mb-1.5 font-medium text-primary\">{labelFormatter ? labelFormatter(label) : label}</div>\n )}\n <div className=\"grid gap-1.5\">{items.map((item, i) => tooltipRow(item, i, false))}</div>\n </div>\n );\n}\n\ninterface LegendPayloadItem {\n value?: string;\n dataKey?: string | number;\n color?: string;\n}\n\ninterface ChartLegendContentProps {\n payload?: LegendPayloadItem[];\n onItemClick?: (key: string) => void;\n /**\n * TOGGLE mode (recommended): the series currently switched off. Each entry is a\n * toggle button — pressed while its series is shown — and a hidden one is drawn\n * faded with its label struck through. Several can be off at once; pair with\n * `toggleHidden(hidden, key)` in `onItemClick` and `hide` on the chart's series.\n */\n hiddenKeys?: ReadonlySet<string> | readonly string[];\n /**\n * ISOLATE mode: the one series shown in full, every other entry dimmed. Kept for\n * the charts built on it; a legend where each entry switches its own series is\n * what readers expect from a legend, so prefer `hiddenKeys`.\n */\n activeKey?: string | null;\n}\n\nexport function ChartLegendContent({ payload, onItemClick, hiddenKeys, activeKey }: ChartLegendContentProps) {\n const hidden = hiddenKeys ? new Set(hiddenKeys) : null;\n const config = useChart();\n if (!payload?.length) return null;\n return (\n <div className=\"flex flex-wrap items-center justify-center gap-x-4 gap-y-1 pt-3\">\n {payload.map((item, i) => {\n const dk = item.dataKey != null ? String(item.dataKey) : undefined;\n const vv = item.value != null ? String(item.value) : undefined;\n const cfgKey = dk && config[dk] ? dk : vv && config[vv] ? vv : (dk ?? vv ?? String(i));\n const series = config[cfgKey];\n const color = paintedColor(cfgKey, series) ?? item.color ?? \"var(--text-muted)\";\n const label = series?.label ?? vv ?? cfgKey;\n const off = hidden?.has(cfgKey) ?? false;\n const dimmed = off || (activeKey != null && activeKey !== cfgKey);\n // The label's cue, in a key and on a button alike: a hidden series struck through,\n // and an entry an isolate legend dims in the muted ink — drawn still, so not struck.\n // Only a toggle button had one, and a key or an isolate legend told an entry off by\n // its 10px swatch at 40 % alone (0.52 code review, deep, charts, finding 9).\n const cue = off ? \"line-through\" : dimmed ? \"text-muted\" : undefined;\n const className = cn(\n \"flex items-center gap-1.5 text-secondary transition-opacity\",\n onItemClick ? \"cursor-pointer hover:opacity-80\" : \"cursor-default\",\n );\n // Only the swatch fades; the label keeps an ink of AA contrast (0.52, refactor audit\n // 2026-10-30, DSP-9). The whole entry at 40 % put a switched-off label under 2:1,\n // on a button that brings its series back and is named by nothing else.\n const swatch = (\n <span\n aria-hidden\n className={cn(\"h-2.5 w-2.5 shrink-0 rounded-[3px]\", dimmed && \"opacity-40\")}\n style={{ backgroundColor: color } as CSSProperties}\n />\n );\n // A legend with no `onItemClick` is a KEY, not a control — and that is every\n // legend in kastlan, where this rendered N focusable buttons per chart that did\n // nothing when activated. A keyboard user paid the tab stops for all of them,\n // and a screen reader announced each one as a button, promising an action there\n // was none of. The interactive branch is unchanged.\n return onItemClick ? (\n <button\n key={`${cfgKey}-${i}`}\n type=\"button\"\n onClick={() => onItemClick(cfgKey)}\n // Toggles report their state: in toggle mode pressed = the series is shown;\n // in isolate mode pressed = the isolated one. With neither supplied the\n // legend knows nothing about state, and a pressed=\"false\" it cannot vouch\n // for would lie — so it says nothing.\n aria-pressed={hidden ? !off : activeKey === undefined ? undefined : activeKey === cfgKey}\n className={className}\n >\n {swatch}\n <span className={cue}>{label}</span>\n </button>\n ) : (\n // On the entry, not round the label: the text is its own, so the key's DOM is the\n // same with a cue as without. A line-through reaches the text only (the swatch\n // holds none), and the ink colours no swatch.\n <span key={`${cfgKey}-${i}`} className={cn(className, cue)}>\n {swatch}\n {label}\n </span>\n );\n })}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
@@ -222,9 +222,75 @@ export declare function useComboboxCore<V extends string | number>({ options, mi
222
222
  * heading is the one thing that says where in the alphabet the thumb is. Opaque — the
223
223
  * list's own `bg-surface` — so the rows pass beneath it rather than through it. Shared
224
224
  * by every list that heads groups (the panel and the sheet, `Combobox`, `Autocomplete`,
225
- * `InlineEntityCombobox`).
225
+ * `InlineEntityCombobox`). Inside its {@link OptionGroup} since 0.52, it sticks while its
226
+ * own group is in view and the next group's heading pushes it out.
226
227
  */
227
228
  export declare const GROUP_HEADING_STICKY = "sticky top-0 z-10 bg-surface";
229
+ /** One run of rows that share a group, as {@link groupRuns} finds them: `group` is
230
+ * `null` for rows filed under none, which stand bare in the list; `start` is the index
231
+ * of the run's first row in the whole list (the option ids and the arrows count by it). */
232
+ export interface GroupRun<T> {
233
+ group: string | null;
234
+ start: number;
235
+ rows: T[];
236
+ }
237
+ /**
238
+ * A list's rows, cut where the group changes — the runs {@link OptionGroup} wraps. The
239
+ * lists keep their rows group-contiguous (the panel's source order, or the ranked list
240
+ * regrouped by its best member), so a run is "the rows up to the next change of group".
241
+ * `groupOf` is asked once per row. An empty group name is no group: a heading of nothing
242
+ * names nothing.
243
+ */
244
+ export declare function groupRuns<T>(rows: readonly T[], groupOf: (row: T) => string | null | undefined): GroupRun<T>[];
245
+ /**
246
+ * A group of options under its heading, inside a `role="listbox"`: a `role="group"`
247
+ * NAMED by the heading, so a reader moving through the list hears which group a row is
248
+ * in (refactor audit 2026-10-30, INP-8). The heading was loose text among the options,
249
+ * and focus stays in the search box with the row announced through
250
+ * `aria-activedescendant` — keksdose's "Groceries" under Recent and again under its own
251
+ * group were two identical names, and an account filed by its type never said the type.
252
+ * The heading stays a visible first item of the group (sticky within it); the columns of
253
+ * {@link ComboboxPanel} carry the same `role="group"`.
254
+ *
255
+ * `id` must be unique in the document (the list's id plus the run's start); `className`
256
+ * places the heading's text in its list.
257
+ */
258
+ export declare function OptionGroup({ id, heading, className, children, }: {
259
+ id: string;
260
+ heading: ReactNode;
261
+ className?: string;
262
+ children: ReactNode;
263
+ }): import("react").JSX.Element;
264
+ /**
265
+ * A list's rows in their {@link groupRuns}: a run filed under no group stands bare, any
266
+ * other is an {@link OptionGroup} under its heading. The one drawing of it for
267
+ * {@link ComboboxPanel}, {@link Combobox}, {@link InlineEntityCombobox} and
268
+ * {@link Autocomplete}, which had a copy each (0.52 code review, combobox-core.tsx).
269
+ *
270
+ * A run is keyed by its group and how many runs of that group came before it — NOT by
271
+ * the index of its first row, which every keystroke that drops a row from an earlier
272
+ * group moves: each later group was re-keyed and React rebuilt it, every row of it, per
273
+ * keystroke. A row is keyed by `rowKey` among the rows of its run; an option may stand
274
+ * in two groups (keksdose's "Recent", live #203), never twice in one.
275
+ *
276
+ * `row(o, i, grouped)`: `i` is the row's index in the whole list (the option ids and the
277
+ * arrows count by it), `grouped` whether it stands under a heading. `headingClassName`
278
+ * places a run's heading text in its list. The group's id is the list's id and the run's
279
+ * first row, unique in the document.
280
+ */
281
+ export declare function GroupedOptions<T>({ runs, listboxId, rowKey, row, headingClassName, }: {
282
+ runs: readonly GroupRun<T>[];
283
+ listboxId: string;
284
+ rowKey: (o: T) => string;
285
+ row: (o: T, index: number, grouped: boolean) => ReactNode;
286
+ headingClassName: (run: GroupRun<T>) => string;
287
+ }): import("react").JSX.Element[];
288
+ /** The anchored list's heading text: the first group's sits closer to the list's top
289
+ * edge, unless a row stands above it (`first: false`, Combobox's create row). */
290
+ export declare const anchoredHeadingClass: (run: GroupRun<unknown>, first?: boolean) => string;
291
+ /** The typed comboboxes' phone sheet's heading text ({@link Combobox},
292
+ * {@link InlineEntityCombobox}). */
293
+ export declare const SHEET_HEADING_CLASS = "px-4 pb-0.5 pt-3 text-caption";
228
294
  export declare const SUGGESTION_LIST_CLASS = "max-h-64 overflow-y-auto rounded-md border border-subtle bg-surface py-1 shadow-lg";
229
295
  export declare const suggestionRowClass: (isActive: boolean) => string;
230
296
  /**
@@ -240,6 +306,47 @@ export declare const suggestionRowClass: (isActive: boolean) => string;
240
306
  * trade than a list that does not scroll in a test.
241
307
  */
242
308
  export declare function useActiveOptionScroll(activeId: string | undefined): void;
309
+ /**
310
+ * What a TYPED combobox's list says — {@link Combobox} and {@link InlineEntityCombobox},
311
+ * which had a copy each, the second saying "see the twin" (0.52 code review,
312
+ * inline-entity-combobox.tsx; the rules are refactor audit 2026-10-30, INP-6):
313
+ *
314
+ * - `showing`: the anchored list is on screen — open, not inert, with a row to show (an
315
+ * option, or `extraRow`, the create row). The list is then a layer on the Escape stack
316
+ * (Wave 2, DUP-1). Never on a phone, where the sheet is the list and the layer.
317
+ * - `expanded`, the field's `aria-expanded`: a list on screen. An open list with nothing
318
+ * to show said "expanded" with no listbox in the document.
319
+ * - `activeOption`: the id of the row the keyboard is on — `active`, a TAKEABLE row's
320
+ * index, else -1 — in whichever list shows, kept scrolled into view.
321
+ * - `statusRegion`: a polite live region with the row count, or "No results" for a query
322
+ * that left nothing to show or do. Focus stays in the field, or in the sheet's search
323
+ * box, so a list changing under it was silent. A list opened on no query asked nothing.
324
+ * Render it outside the list, so the list going cannot take it along: on a phone inside
325
+ * the sheet, which hides from a reader what lies outside it. There it mounts with the
326
+ * sheet, so the count the sheet opens with is not announced — as a ComboboxPanel's is
327
+ * not, in either shell — and every change after it is.
328
+ */
329
+ export declare function useTypedListStatus({ isPhone, open, inert, rowCount, extraRow, query, active, optionId, labels, }: {
330
+ isPhone: boolean;
331
+ open: boolean;
332
+ inert: boolean;
333
+ /** The options the list shows. */
334
+ rowCount: number;
335
+ /** A create row shows: the list is not empty, it offers to fill itself. */
336
+ extraRow: boolean;
337
+ query: string;
338
+ active: number;
339
+ optionId: (index: number) => string;
340
+ labels: {
341
+ resultCount: (count: number) => string;
342
+ noResults: string;
343
+ };
344
+ }): {
345
+ showing: boolean;
346
+ expanded: boolean;
347
+ activeOption: string | undefined;
348
+ statusRegion: ReactNode;
349
+ };
243
350
  /**
244
351
  * How wide the anchored panel is. The phone sheet is the screen's width whatever this
245
352
  * says.
@@ -297,7 +404,9 @@ export interface ComboboxPanelProps<V extends string | number> extends Component
297
404
  */
298
405
  renderOption?: (o: ComboOption<V>) => ReactNode;
299
406
  /** Controls between the search box and the list, in the anchored panel and the phone
300
- * sheet alike — MultiSelect's "Select all" and "Clear". */
407
+ * sheet alike — MultiSelect's "Select all" and "Clear". In the anchored panel Tab from
408
+ * the search box walks them before it leaves (0.52); keep them focusable when they
409
+ * have nothing to do (`aria-disabled`), or a press drops their focus to <body>. */
301
410
  toolbar?: ReactNode;
302
411
  /** See {@link ComboboxPanelWidth}. */
303
412
  width?: ComboboxPanelWidth;