@eifi1/ui-kit 0.54.0 → 0.56.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 (847) hide show
  1. package/README.md +90 -76
  2. package/dist/admin/plan-change.js +2 -2
  3. package/dist/admin/plan-change.js.map +1 -1
  4. package/dist/admin/user-roster.d.ts +6 -0
  5. package/dist/admin/user-roster.js +4 -0
  6. package/dist/admin/user-roster.js.map +1 -1
  7. package/dist/billing/billing-labels.d.ts +26 -0
  8. package/dist/billing/billing-labels.js +9 -0
  9. package/dist/billing/billing-labels.js.map +1 -1
  10. package/dist/billing/subscription-standing.d.ts +61 -0
  11. package/dist/billing/subscription-standing.js +51 -0
  12. package/dist/billing/subscription-standing.js.map +1 -0
  13. package/dist/billing/usage-meter.d.ts +52 -0
  14. package/dist/billing/usage-meter.js +52 -0
  15. package/dist/billing/usage-meter.js.map +1 -0
  16. package/dist/chart.d.ts +1 -0
  17. package/dist/chart.js +4 -0
  18. package/dist/chart.js.map +1 -1
  19. package/dist/components/alert-banner.js +2 -3
  20. package/dist/components/alert-banner.js.map +1 -1
  21. package/dist/components/amount-input.js +3 -15
  22. package/dist/components/amount-input.js.map +1 -1
  23. package/dist/components/autocomplete.js +2 -2
  24. package/dist/components/autocomplete.js.map +1 -1
  25. package/dist/components/bulk-action-bar.js +2 -1
  26. package/dist/components/bulk-action-bar.js.map +1 -1
  27. package/dist/components/button.d.ts +35 -1
  28. package/dist/components/button.js +6 -9
  29. package/dist/components/button.js.map +1 -1
  30. package/dist/components/calendar-heatmap.js +2 -2
  31. package/dist/components/calendar-heatmap.js.map +1 -1
  32. package/dist/components/camera-button.d.ts +89 -0
  33. package/dist/components/camera-button.js +122 -0
  34. package/dist/components/camera-button.js.map +1 -0
  35. package/dist/components/camera-capture.d.ts +66 -0
  36. package/dist/components/camera-capture.js +190 -0
  37. package/dist/components/camera-capture.js.map +1 -0
  38. package/dist/components/camera-labels.d.ts +20 -0
  39. package/dist/components/camera-labels.js +15 -0
  40. package/dist/components/camera-labels.js.map +1 -0
  41. package/dist/components/chart-edge.d.ts +9 -0
  42. package/dist/components/chart-edge.js +14 -3
  43. package/dist/components/chart-edge.js.map +1 -1
  44. package/dist/components/chart-zoom-core.js +34 -29
  45. package/dist/components/chart-zoom-core.js.map +1 -1
  46. package/dist/components/checkbox-group.js +1 -1
  47. package/dist/components/checkbox-group.js.map +1 -1
  48. package/dist/components/combobox-core.js +3 -2
  49. package/dist/components/combobox-core.js.map +1 -1
  50. package/dist/components/combobox.js +2 -2
  51. package/dist/components/combobox.js.map +1 -1
  52. package/dist/components/consent-again-notice-labels.d.ts +25 -0
  53. package/dist/components/consent-again-notice-labels.js +15 -0
  54. package/dist/components/consent-again-notice-labels.js.map +1 -0
  55. package/dist/components/consent-again-notice.d.ts +85 -0
  56. package/dist/components/consent-again-notice.js +94 -0
  57. package/dist/components/consent-again-notice.js.map +1 -0
  58. package/dist/components/country-select.js +2 -7
  59. package/dist/components/country-select.js.map +1 -1
  60. package/dist/components/currency-select.js +2 -2
  61. package/dist/components/currency-select.js.map +1 -1
  62. package/dist/components/danger-confirm.js +2 -2
  63. package/dist/components/danger-confirm.js.map +1 -1
  64. package/dist/components/data-table-column-settings.js +2 -2
  65. package/dist/components/data-table-column-settings.js.map +1 -1
  66. package/dist/components/data-table-fit.js.map +1 -1
  67. package/dist/components/data-table-virtual.js +13 -3
  68. package/dist/components/data-table-virtual.js.map +1 -1
  69. package/dist/components/date-field.js +2 -2
  70. package/dist/components/date-field.js.map +1 -1
  71. package/dist/components/date-picker.js +2 -2
  72. package/dist/components/date-picker.js.map +1 -1
  73. package/dist/components/date-range-picker.js +2 -6
  74. package/dist/components/date-range-picker.js.map +1 -1
  75. package/dist/components/dialog-frame.d.ts +3 -1
  76. package/dist/components/dialog-frame.js.map +1 -1
  77. package/dist/components/dropdown.js +7 -3
  78. package/dist/components/dropdown.js.map +1 -1
  79. package/dist/components/entity-combobox.js +2 -2
  80. package/dist/components/entity-combobox.js.map +1 -1
  81. package/dist/components/field-parts.d.ts +108 -6
  82. package/dist/components/field-parts.js +59 -6
  83. package/dist/components/field-parts.js.map +1 -1
  84. package/dist/components/field-suggestion-labels.d.ts +2 -0
  85. package/dist/components/field-suggestion-labels.js +10 -0
  86. package/dist/components/field-suggestion-labels.js.map +1 -0
  87. package/dist/components/field.js +1 -1
  88. package/dist/components/field.js.map +1 -1
  89. package/dist/components/file-button.d.ts +1 -1
  90. package/dist/components/file-button.js.map +1 -1
  91. package/dist/components/floating-panel.d.ts +7 -6
  92. package/dist/components/floating-panel.js +3 -17
  93. package/dist/components/floating-panel.js.map +1 -1
  94. package/dist/components/form-actions.d.ts +3 -2
  95. package/dist/components/form-actions.js.map +1 -1
  96. package/dist/components/hover-menu.js +3 -2
  97. package/dist/components/hover-menu.js.map +1 -1
  98. package/dist/components/icon-button.d.ts +23 -5
  99. package/dist/components/icon-button.js +7 -8
  100. package/dist/components/icon-button.js.map +1 -1
  101. package/dist/components/inline-confirm.js +2 -1
  102. package/dist/components/inline-confirm.js.map +1 -1
  103. package/dist/components/inline-entity-combobox.js +2 -2
  104. package/dist/components/inline-entity-combobox.js.map +1 -1
  105. package/dist/components/input.js +2 -2
  106. package/dist/components/input.js.map +1 -1
  107. package/dist/components/jump-calendar.js +3 -3
  108. package/dist/components/jump-calendar.js.map +1 -1
  109. package/dist/components/legal.d.ts +13 -1
  110. package/dist/components/legal.js +12 -0
  111. package/dist/components/legal.js.map +1 -1
  112. package/dist/components/legended-series-chart.d.ts +83 -0
  113. package/dist/components/legended-series-chart.js +77 -0
  114. package/dist/components/legended-series-chart.js.map +1 -0
  115. package/dist/components/lightbox.js +2 -1
  116. package/dist/components/lightbox.js.map +1 -1
  117. package/dist/components/mini-calendar.js +13 -5
  118. package/dist/components/mini-calendar.js.map +1 -1
  119. package/dist/components/modal.d.ts +23 -2
  120. package/dist/components/modal.js +24 -6
  121. package/dist/components/modal.js.map +1 -1
  122. package/dist/components/month-picker.d.ts +1 -1
  123. package/dist/components/month-picker.js +6 -6
  124. package/dist/components/month-picker.js.map +1 -1
  125. package/dist/components/multi-entity-combobox.js +2 -2
  126. package/dist/components/multi-entity-combobox.js.map +1 -1
  127. package/dist/components/multi-select.js +2 -6
  128. package/dist/components/multi-select.js.map +1 -1
  129. package/dist/components/number-input.js +2 -2
  130. package/dist/components/number-input.js.map +1 -1
  131. package/dist/components/page-header-layout.d.ts +18 -0
  132. package/dist/components/page-header-layout.js +23 -0
  133. package/dist/components/page-header-layout.js.map +1 -0
  134. package/dist/components/page-header-skeleton.d.ts +41 -0
  135. package/dist/components/page-header-skeleton.js +52 -0
  136. package/dist/components/page-header-skeleton.js.map +1 -0
  137. package/dist/components/page-header.js +4 -17
  138. package/dist/components/page-header.js.map +1 -1
  139. package/dist/components/popover.js +7 -2
  140. package/dist/components/popover.js.map +1 -1
  141. package/dist/components/release-notes-labels.d.ts +16 -0
  142. package/dist/components/release-notes-labels.js +18 -0
  143. package/dist/components/release-notes-labels.js.map +1 -0
  144. package/dist/components/release-notes.d.ts +65 -0
  145. package/dist/components/release-notes.js +65 -0
  146. package/dist/components/release-notes.js.map +1 -0
  147. package/dist/components/select.js +2 -2
  148. package/dist/components/select.js.map +1 -1
  149. package/dist/components/series-chart-labels.d.ts +9 -0
  150. package/dist/components/series-chart-labels.js +3 -1
  151. package/dist/components/series-chart-labels.js.map +1 -1
  152. package/dist/components/series-chart.d.ts +5 -0
  153. package/dist/components/series-chart.js +89 -36
  154. package/dist/components/series-chart.js.map +1 -1
  155. package/dist/components/toggle-group.js +3 -2
  156. package/dist/components/toggle-group.js.map +1 -1
  157. package/dist/components/treemap-layout.js.map +1 -1
  158. package/dist/components/treemap.d.ts +17 -15
  159. package/dist/components/treemap.js +167 -85
  160. package/dist/components/treemap.js.map +1 -1
  161. package/dist/eslint/index.js +1 -1
  162. package/dist/eslint/role-utilities.d.ts +2 -2
  163. package/dist/eslint/role-utilities.js.map +1 -1
  164. package/dist/feedback/feedback-inbox-page.d.ts +260 -0
  165. package/dist/feedback/feedback-inbox-page.js +207 -0
  166. package/dist/feedback/feedback-inbox-page.js.map +1 -0
  167. package/dist/feedback/feedback-vocabulary.js +2 -2
  168. package/dist/feedback/feedback-vocabulary.js.map +1 -1
  169. package/dist/feedback.d.ts +1 -0
  170. package/dist/feedback.js +6 -0
  171. package/dist/feedback.js.map +1 -1
  172. package/dist/hooks/use-debounce.d.ts +32 -5
  173. package/dist/hooks/use-debounce.js +6 -5
  174. package/dist/hooks/use-debounce.js.map +1 -1
  175. package/dist/hooks/use-file-picker.d.ts +3 -1
  176. package/dist/hooks/use-file-picker.js.map +1 -1
  177. package/dist/hooks/use-focus-trap.d.ts +19 -3
  178. package/dist/hooks/use-focus-trap.js +15 -5
  179. package/dist/hooks/use-focus-trap.js.map +1 -1
  180. package/dist/hooks/use-has-camera.d.ts +36 -0
  181. package/dist/hooks/use-has-camera.js +53 -0
  182. package/dist/hooks/use-has-camera.js.map +1 -0
  183. package/dist/hooks/use-hotkey.js +1 -10
  184. package/dist/hooks/use-hotkey.js.map +1 -1
  185. package/dist/hooks/use-overlay-history.d.ts +6 -1
  186. package/dist/hooks/use-overlay-history.js +10 -2
  187. package/dist/hooks/use-overlay-history.js.map +1 -1
  188. package/dist/i18n/defaults.js +11 -1
  189. package/dist/i18n/defaults.js.map +1 -1
  190. package/dist/i18n/german.js +75 -3
  191. package/dist/i18n/german.js.map +1 -1
  192. package/dist/i18n/kit-labels.d.ts +20 -0
  193. package/dist/i18n/kit-labels.js.map +1 -1
  194. package/dist/i18n/locales/en.js +92 -4
  195. package/dist/i18n/locales/en.js.map +3 -3
  196. package/dist/i18n/locales/es.js +71 -3
  197. package/dist/i18n/locales/es.js.map +1 -1
  198. package/dist/i18n/locales/fr.js +72 -2
  199. package/dist/i18n/locales/fr.js.map +1 -1
  200. package/dist/i18n/locales/hu.js +74 -3
  201. package/dist/i18n/locales/hu.js.map +1 -1
  202. package/dist/i18n/locales/it.js +74 -3
  203. package/dist/i18n/locales/it.js.map +1 -1
  204. package/dist/i18n/locales/zh.js +71 -3
  205. package/dist/i18n/locales/zh.js.map +1 -1
  206. package/dist/i18n/review.js +22 -1
  207. package/dist/i18n/review.js.map +1 -1
  208. package/dist/index.d.ts +24 -5
  209. package/dist/index.js +35 -1
  210. package/dist/index.js.map +1 -1
  211. package/dist/landing/landing-labels.d.ts +8 -0
  212. package/dist/landing/landing-labels.js +2 -1
  213. package/dist/landing/landing-labels.js.map +1 -1
  214. package/dist/lib/changelog.d.ts +130 -0
  215. package/dist/lib/changelog.js +137 -0
  216. package/dist/lib/changelog.js.map +1 -0
  217. package/dist/lib/clipboard-minus.js +2 -2
  218. package/dist/lib/clipboard-minus.js.map +1 -1
  219. package/dist/lib/dates.d.ts +29 -3
  220. package/dist/lib/dates.js +41 -12
  221. package/dist/lib/dates.js.map +1 -1
  222. package/dist/lib/editable-content.d.ts +33 -0
  223. package/dist/lib/editable-content.js +15 -0
  224. package/dist/lib/editable-content.js.map +1 -0
  225. package/dist/lib/focus-left.d.ts +47 -1
  226. package/dist/lib/focus-left.js +11 -2
  227. package/dist/lib/focus-left.js.map +1 -1
  228. package/dist/lib/server-wake.d.ts +49 -9
  229. package/dist/lib/server-wake.js +25 -14
  230. package/dist/lib/server-wake.js.map +1 -1
  231. package/dist/lib/session-refresh.d.ts +53 -0
  232. package/dist/lib/session-refresh.js +27 -1
  233. package/dist/lib/session-refresh.js.map +1 -1
  234. package/dist/rhf/fields.js +3 -3
  235. package/dist/rhf/fields.js.map +1 -1
  236. package/dist/shell/app-shell.d.ts +25 -1
  237. package/dist/shell/app-shell.js +3 -1
  238. package/dist/shell/app-shell.js.map +1 -1
  239. package/dist/shell/dev-menu.d.ts +3 -0
  240. package/dist/shell/dev-menu.js +4 -2
  241. package/dist/shell/dev-menu.js.map +1 -1
  242. package/dist/shell/held-changes-banner-labels.d.ts +23 -0
  243. package/dist/shell/held-changes-banner-labels.js +14 -0
  244. package/dist/shell/held-changes-banner-labels.js.map +1 -0
  245. package/dist/shell/held-changes-banner.d.ts +75 -0
  246. package/dist/shell/held-changes-banner.js +106 -0
  247. package/dist/shell/held-changes-banner.js.map +1 -0
  248. package/dist/shell/sync-status-indicator.d.ts +27 -12
  249. package/dist/shell/sync-status-indicator.js +13 -4
  250. package/dist/shell/sync-status-indicator.js.map +1 -1
  251. package/dist/shell/top-bar-parts-labels.d.ts +5 -0
  252. package/dist/shell/top-bar-parts-labels.js +3 -1
  253. package/dist/shell/top-bar-parts-labels.js.map +1 -1
  254. package/dist/shell.d.ts +2 -0
  255. package/dist/shell.js +3 -0
  256. package/dist/shell.js.map +1 -1
  257. package/dist/sync/engine.d.ts +11 -2
  258. package/dist/sync/engine.js +13 -5
  259. package/dist/sync/engine.js.map +1 -1
  260. package/dist/sync/hooks.d.ts +3 -2
  261. package/dist/sync/hooks.js +4 -2
  262. package/dist/sync/hooks.js.map +1 -1
  263. package/dist/testing/css-variables.generated.d.ts +2 -2
  264. package/dist/testing/css-variables.generated.js +1 -1
  265. package/dist/testing/css-variables.generated.js.map +1 -1
  266. package/dist/theme/chart-palette.d.ts +18 -0
  267. package/dist/theme/chart-palette.js +17 -1
  268. package/dist/theme/chart-palette.js.map +1 -1
  269. package/dist/theme/contrast-tokens.d.ts +3 -3
  270. package/dist/theme/contrast-tokens.js.map +1 -1
  271. package/dist/theme/contrast.d.ts +4 -4
  272. package/dist/theme/contrast.js.map +1 -1
  273. package/dist/tour/tour-overlay.js +2 -1
  274. package/dist/tour/tour-overlay.js.map +1 -1
  275. package/package.json +9 -12
  276. package/tokens.css +19 -54
  277. package/src/account/account-parts.ts +0 -47
  278. package/src/account/account-settings-labels.ts +0 -240
  279. package/src/account/account-settings.tsx +0 -698
  280. package/src/account/data-export-setting-labels.ts +0 -20
  281. package/src/account/data-export-setting.tsx +0 -200
  282. package/src/account/delete-account-setting-labels.ts +0 -33
  283. package/src/account/delete-account-setting.tsx +0 -342
  284. package/src/account/email-change-setting-labels.ts +0 -31
  285. package/src/account/email-change-setting.tsx +0 -443
  286. package/src/account/passkeys-setting.tsx +0 -445
  287. package/src/account/sessions-setting-labels.ts +0 -24
  288. package/src/account/sessions-setting.tsx +0 -283
  289. package/src/admin/admin-action-confirm-labels.ts +0 -19
  290. package/src/admin/admin-action-confirm.tsx +0 -509
  291. package/src/admin/admin-action-log-labels.ts +0 -83
  292. package/src/admin/admin-action-log.tsx +0 -464
  293. package/src/admin/admin-area-labels.ts +0 -114
  294. package/src/admin/admin-forbidden.tsx +0 -75
  295. package/src/admin/admin-overview.tsx +0 -258
  296. package/src/admin/admin-parts.ts +0 -68
  297. package/src/admin/admin-sections.tsx +0 -208
  298. package/src/admin/admin-tool-links.tsx +0 -90
  299. package/src/admin/invitations-panel-labels.ts +0 -31
  300. package/src/admin/invitations-panel.tsx +0 -516
  301. package/src/admin/mail-test-card-labels.ts +0 -19
  302. package/src/admin/mail-test-card.tsx +0 -189
  303. package/src/admin/plan-change-labels.ts +0 -5
  304. package/src/admin/plan-change.tsx +0 -474
  305. package/src/admin/reviewer-scope-editor-labels.ts +0 -16
  306. package/src/admin/reviewer-scope-editor.tsx +0 -224
  307. package/src/admin/role-select-labels.ts +0 -16
  308. package/src/admin/role-select.tsx +0 -282
  309. package/src/admin/transfer-ownership-dialog-labels.ts +0 -20
  310. package/src/admin/transfer-ownership-dialog.tsx +0 -162
  311. package/src/admin/user-roster-labels.ts +0 -17
  312. package/src/admin/user-roster.tsx +0 -559
  313. package/src/auth/accept-invitation-labels.ts +0 -21
  314. package/src/auth/accept-invitation.tsx +0 -210
  315. package/src/auth/auth-errors-labels.ts +0 -23
  316. package/src/auth/auth-errors.ts +0 -439
  317. package/src/auth/complete-name-dialog-labels.ts +0 -16
  318. package/src/auth/complete-name-dialog.tsx +0 -253
  319. package/src/auth/email-tag.ts +0 -75
  320. package/src/auth/forgot-password-form-labels.ts +0 -20
  321. package/src/auth/forgot-password-form.tsx +0 -221
  322. package/src/auth/form-rules.ts +0 -40
  323. package/src/auth/new-password-fields.tsx +0 -85
  324. package/src/auth/not-found-page-labels.ts +0 -12
  325. package/src/auth/not-found-page.tsx +0 -95
  326. package/src/auth/register-form-labels.ts +0 -31
  327. package/src/auth/register-form.tsx +0 -456
  328. package/src/auth/reset-password-form-labels.ts +0 -27
  329. package/src/auth/reset-password-form.tsx +0 -341
  330. package/src/auth/sign-in-form-labels.ts +0 -44
  331. package/src/auth/sign-in-form.tsx +0 -858
  332. package/src/auth/status-parts.tsx +0 -132
  333. package/src/auth/verify-email-labels.ts +0 -26
  334. package/src/auth/verify-email.tsx +0 -445
  335. package/src/billing/billing-banner.tsx +0 -258
  336. package/src/billing/billing-labels.ts +0 -381
  337. package/src/billing/billing-lock.ts +0 -108
  338. package/src/billing/billing-overview.ts +0 -157
  339. package/src/billing/billing-standing.ts +0 -80
  340. package/src/billing/cancel-request.ts +0 -97
  341. package/src/billing/cancel-subscription.tsx +0 -98
  342. package/src/billing/checkout-processing.ts +0 -394
  343. package/src/billing/checkout-return.ts +0 -90
  344. package/src/billing/pay-page.ts +0 -330
  345. package/src/billing/plan-card.tsx +0 -562
  346. package/src/billing/plan-change-labels.ts +0 -66
  347. package/src/billing/plan-limit-notice.tsx +0 -148
  348. package/src/billing/plan-limit-toast.tsx +0 -90
  349. package/src/billing/plan-limit.ts +0 -52
  350. package/src/billing/plan-price.ts +0 -98
  351. package/src/billing/plan-switch.tsx +0 -216
  352. package/src/billing/provider-page.ts +0 -84
  353. package/src/billing/subscription-actions.tsx +0 -247
  354. package/src/billing/subscription-status-chip.tsx +0 -95
  355. package/src/chart.ts +0 -103
  356. package/src/components/account-chips-labels.ts +0 -16
  357. package/src/components/account-chips.tsx +0 -474
  358. package/src/components/alert-banner.tsx +0 -670
  359. package/src/components/amount-input.tsx +0 -816
  360. package/src/components/appearance-labels.ts +0 -63
  361. package/src/components/authed-image-labels.ts +0 -11
  362. package/src/components/authed-image.tsx +0 -209
  363. package/src/components/autocomplete.tsx +0 -645
  364. package/src/components/breadcrumbs-labels.ts +0 -10
  365. package/src/components/breadcrumbs.tsx +0 -205
  366. package/src/components/bulk-action-bar-labels.ts +0 -11
  367. package/src/components/bulk-action-bar.tsx +0 -443
  368. package/src/components/button-group.tsx +0 -223
  369. package/src/components/button-look.ts +0 -256
  370. package/src/components/button.tsx +0 -339
  371. package/src/components/calculator.tsx +0 -341
  372. package/src/components/calendar-heatmap-labels.ts +0 -15
  373. package/src/components/calendar-heatmap.tsx +0 -600
  374. package/src/components/card.tsx +0 -377
  375. package/src/components/chart-edge.ts +0 -106
  376. package/src/components/chart-figure.tsx +0 -35
  377. package/src/components/chart-height.ts +0 -30
  378. package/src/components/chart-zoom-core.tsx +0 -627
  379. package/src/components/chart-zoom.tsx +0 -22
  380. package/src/components/chart.tsx +0 -554
  381. package/src/components/checkbox-group.tsx +0 -247
  382. package/src/components/checkbox.tsx +0 -346
  383. package/src/components/chip-input.tsx +0 -370
  384. package/src/components/chip-labels.ts +0 -13
  385. package/src/components/chip.tsx +0 -843
  386. package/src/components/choice-card.tsx +0 -544
  387. package/src/components/column-mapper-labels.ts +0 -38
  388. package/src/components/column-mapper.tsx +0 -655
  389. package/src/components/combobox-core.tsx +0 -1321
  390. package/src/components/combobox-suggestions.tsx +0 -131
  391. package/src/components/combobox.tsx +0 -716
  392. package/src/components/confirm-context.ts +0 -7
  393. package/src/components/confirm-dialog-labels.ts +0 -13
  394. package/src/components/confirm-dialog.tsx +0 -341
  395. package/src/components/copy-button-labels.ts +0 -13
  396. package/src/components/copy-button.tsx +0 -192
  397. package/src/components/country-select-labels.ts +0 -11
  398. package/src/components/country-select.tsx +0 -539
  399. package/src/components/currency-option-list.tsx +0 -188
  400. package/src/components/currency-select.tsx +0 -343
  401. package/src/components/danger-confirm-labels.ts +0 -20
  402. package/src/components/danger-confirm.tsx +0 -987
  403. package/src/components/data-table-cells.tsx +0 -118
  404. package/src/components/data-table-column-settings.tsx +0 -198
  405. package/src/components/data-table-filter-popover.tsx +0 -289
  406. package/src/components/data-table-filters.ts +0 -315
  407. package/src/components/data-table-fit.ts +0 -301
  408. package/src/components/data-table-groups.tsx +0 -288
  409. package/src/components/data-table-head.tsx +0 -280
  410. package/src/components/data-table-labels.ts +0 -198
  411. package/src/components/data-table-leading-row.tsx +0 -58
  412. package/src/components/data-table-mobile.tsx +0 -1117
  413. package/src/components/data-table-pagination.tsx +0 -202
  414. package/src/components/data-table-resize.ts +0 -188
  415. package/src/components/data-table-row-actions.tsx +0 -302
  416. package/src/components/data-table-row.tsx +0 -496
  417. package/src/components/data-table-selection.ts +0 -71
  418. package/src/components/data-table-shared.tsx +0 -287
  419. package/src/components/data-table-sort.ts +0 -163
  420. package/src/components/data-table-virtual.ts +0 -576
  421. package/src/components/data-table.tsx +0 -2598
  422. package/src/components/date-field.tsx +0 -684
  423. package/src/components/date-format-setting.tsx +0 -64
  424. package/src/components/date-picker.tsx +0 -404
  425. package/src/components/date-range-picker.tsx +0 -518
  426. package/src/components/description-list-labels.ts +0 -9
  427. package/src/components/description-list.tsx +0 -386
  428. package/src/components/dialog-frame-labels.ts +0 -7
  429. package/src/components/dialog-frame.tsx +0 -284
  430. package/src/components/disclosure.tsx +0 -476
  431. package/src/components/dropdown.tsx +0 -461
  432. package/src/components/empty-state.tsx +0 -136
  433. package/src/components/entity-combobox.tsx +0 -422
  434. package/src/components/error-boundary-labels.ts +0 -20
  435. package/src/components/error-boundary.tsx +0 -675
  436. package/src/components/facing-pair.ts +0 -93
  437. package/src/components/field-base-labels.ts +0 -20
  438. package/src/components/field-base.tsx +0 -656
  439. package/src/components/field-parts.tsx +0 -825
  440. package/src/components/field-strip.tsx +0 -129
  441. package/src/components/field.tsx +0 -206
  442. package/src/components/file-button.tsx +0 -295
  443. package/src/components/file-dropzone.tsx +0 -559
  444. package/src/components/floating-panel-labels.ts +0 -10
  445. package/src/components/floating-panel.tsx +0 -828
  446. package/src/components/focus-ring.ts +0 -16
  447. package/src/components/form-actions-labels.ts +0 -11
  448. package/src/components/form-actions.tsx +0 -578
  449. package/src/components/full-bleed-dialog.tsx +0 -294
  450. package/src/components/grouped-picker.tsx +0 -184
  451. package/src/components/help-popover.tsx +0 -117
  452. package/src/components/hover-menu.tsx +0 -343
  453. package/src/components/iban-input-labels.ts +0 -14
  454. package/src/components/iban-input.tsx +0 -236
  455. package/src/components/icon-button.tsx +0 -959
  456. package/src/components/icon-picker-labels.ts +0 -13
  457. package/src/components/icon-picker.tsx +0 -232
  458. package/src/components/image-grid-labels.ts +0 -11
  459. package/src/components/image-grid.tsx +0 -202
  460. package/src/components/inline-confirm.tsx +0 -116
  461. package/src/components/inline-edit-field-labels.ts +0 -11
  462. package/src/components/inline-edit-field.tsx +0 -552
  463. package/src/components/inline-entity-combobox.tsx +0 -849
  464. package/src/components/input.tsx +0 -394
  465. package/src/components/jump-calendar.tsx +0 -265
  466. package/src/components/language-select.tsx +0 -93
  467. package/src/components/legal-labels.ts +0 -105
  468. package/src/components/legal-page.tsx +0 -711
  469. package/src/components/legal.tsx +0 -668
  470. package/src/components/lightbox-labels.ts +0 -18
  471. package/src/components/lightbox.tsx +0 -399
  472. package/src/components/line-items-labels.ts +0 -14
  473. package/src/components/line-items.tsx +0 -602
  474. package/src/components/list-labels.ts +0 -12
  475. package/src/components/list.tsx +0 -870
  476. package/src/components/loading-state.tsx +0 -78
  477. package/src/components/measured-grid-labels.ts +0 -25
  478. package/src/components/measured-grid.tsx +0 -837
  479. package/src/components/menu-item.tsx +0 -241
  480. package/src/components/mini-calendar-labels.ts +0 -17
  481. package/src/components/mini-calendar.tsx +0 -792
  482. package/src/components/modal.tsx +0 -534
  483. package/src/components/money-draft.ts +0 -83
  484. package/src/components/money-field.tsx +0 -76
  485. package/src/components/month-grid.tsx +0 -229
  486. package/src/components/month-picker-labels.ts +0 -22
  487. package/src/components/month-picker.tsx +0 -1087
  488. package/src/components/multi-entity-combobox.tsx +0 -369
  489. package/src/components/multi-select.tsx +0 -376
  490. package/src/components/nav-pills.tsx +0 -198
  491. package/src/components/number-field.tsx +0 -327
  492. package/src/components/number-input.tsx +0 -559
  493. package/src/components/numeric-field.ts +0 -169
  494. package/src/components/numpad-sheet.tsx +0 -285
  495. package/src/components/one-time-code-input.tsx +0 -270
  496. package/src/components/page-contents-labels.ts +0 -9
  497. package/src/components/page-contents.tsx +0 -282
  498. package/src/components/page-header-context.ts +0 -8
  499. package/src/components/page-header.tsx +0 -298
  500. package/src/components/password-strength-labels.ts +0 -22
  501. package/src/components/password-strength.tsx +0 -241
  502. package/src/components/phone-input-labels.ts +0 -10
  503. package/src/components/phone-input.tsx +0 -282
  504. package/src/components/picker-sheet.tsx +0 -330
  505. package/src/components/pie-chart-labels.ts +0 -40
  506. package/src/components/pie-chart.tsx +0 -480
  507. package/src/components/popover-labels.ts +0 -14
  508. package/src/components/popover.tsx +0 -268
  509. package/src/components/progress-bar-labels.ts +0 -10
  510. package/src/components/progress-bar.tsx +0 -479
  511. package/src/components/qr-code.tsx +0 -76
  512. package/src/components/reauth-dialog-labels.ts +0 -13
  513. package/src/components/reauth-dialog.tsx +0 -265
  514. package/src/components/row-actions-labels.ts +0 -12
  515. package/src/components/row-actions.tsx +0 -795
  516. package/src/components/scroll-area.tsx +0 -123
  517. package/src/components/search-field.tsx +0 -192
  518. package/src/components/select.tsx +0 -294
  519. package/src/components/separator.tsx +0 -33
  520. package/src/components/series-chart-axes.ts +0 -342
  521. package/src/components/series-chart-budget.ts +0 -150
  522. package/src/components/series-chart-labels.ts +0 -33
  523. package/src/components/series-chart-thin.ts +0 -180
  524. package/src/components/series-chart-ticks.ts +0 -194
  525. package/src/components/series-chart.tsx +0 -1906
  526. package/src/components/server-wake-labels.ts +0 -11
  527. package/src/components/server-wake.tsx +0 -129
  528. package/src/components/settings-fields.tsx +0 -237
  529. package/src/components/share-card-labels.ts +0 -30
  530. package/src/components/share-card.tsx +0 -675
  531. package/src/components/signature-pad-labels.ts +0 -24
  532. package/src/components/signature-pad.tsx +0 -747
  533. package/src/components/signed-amount-labels.ts +0 -10
  534. package/src/components/signed-amount.tsx +0 -336
  535. package/src/components/skeleton.tsx +0 -80
  536. package/src/components/slider.tsx +0 -338
  537. package/src/components/sparkline-labels.ts +0 -14
  538. package/src/components/sparkline.tsx +0 -322
  539. package/src/components/spinner.tsx +0 -81
  540. package/src/components/stat-tile-labels.ts +0 -15
  541. package/src/components/stat-tile.tsx +0 -754
  542. package/src/components/status-dot.tsx +0 -170
  543. package/src/components/swatch-picker-labels.ts +0 -10
  544. package/src/components/swatch-picker.tsx +0 -283
  545. package/src/components/swipeable-row.tsx +0 -386
  546. package/src/components/switch.tsx +0 -273
  547. package/src/components/table-skeleton.tsx +0 -136
  548. package/src/components/table.tsx +0 -647
  549. package/src/components/tabs-labels.ts +0 -13
  550. package/src/components/tabs.tsx +0 -693
  551. package/src/components/text-link.tsx +0 -359
  552. package/src/components/text.tsx +0 -150
  553. package/src/components/textarea.tsx +0 -330
  554. package/src/components/tile-radio.tsx +0 -332
  555. package/src/components/time-input.tsx +0 -121
  556. package/src/components/toast-labels.ts +0 -12
  557. package/src/components/toast.tsx +0 -519
  558. package/src/components/toggle-group.tsx +0 -752
  559. package/src/components/toggle-legend.tsx +0 -394
  560. package/src/components/tooltip.tsx +0 -1149
  561. package/src/components/translation-review-editor.tsx +0 -290
  562. package/src/components/translation-review-labels.ts +0 -181
  563. package/src/components/translation-review-parts.tsx +0 -272
  564. package/src/components/translation-review.tsx +0 -1240
  565. package/src/components/tree-view.tsx +0 -589
  566. package/src/components/treemap-labels.ts +0 -47
  567. package/src/components/treemap-layout.ts +0 -210
  568. package/src/components/treemap.tsx +0 -1184
  569. package/src/components/trigger-aria.ts +0 -44
  570. package/src/components/use-anchor-dir.ts +0 -20
  571. package/src/components/use-mobile-reveal.ts +0 -62
  572. package/src/components/use-table-state.ts +0 -753
  573. package/src/components/user-avatar.tsx +0 -116
  574. package/src/components/wizard-stepper.tsx +0 -42
  575. package/src/components/write-lock-labels.ts +0 -10
  576. package/src/components/write-lock.tsx +0 -253
  577. package/src/currencies.ts +0 -90
  578. package/src/data-table.ts +0 -42
  579. package/src/demo/demo-banner.tsx +0 -153
  580. package/src/demo/demo-countdown.ts +0 -136
  581. package/src/demo/demo-ended.tsx +0 -90
  582. package/src/demo/demo-labels.ts +0 -78
  583. package/src/demo/demo-session.ts +0 -32
  584. package/src/demo/demo-start.tsx +0 -156
  585. package/src/eslint/index.ts +0 -161
  586. package/src/eslint/role-utilities.generated.ts +0 -146
  587. package/src/eslint/role-utilities.ts +0 -233
  588. package/src/feedback/feedback-attachment-download.tsx +0 -77
  589. package/src/feedback/feedback-attachment-labels.ts +0 -37
  590. package/src/feedback/feedback-attachment.tsx +0 -1239
  591. package/src/feedback/feedback-capture.ts +0 -287
  592. package/src/feedback/feedback-comment-thread.tsx +0 -353
  593. package/src/feedback/feedback-context-labels.ts +0 -11
  594. package/src/feedback/feedback-context.tsx +0 -245
  595. package/src/feedback/feedback-crash.ts +0 -547
  596. package/src/feedback/feedback-diagnostics.tsx +0 -41
  597. package/src/feedback/feedback-dialog-labels.ts +0 -29
  598. package/src/feedback/feedback-dialog.tsx +0 -452
  599. package/src/feedback/feedback-entry-file.tsx +0 -150
  600. package/src/feedback/feedback-inbox.tsx +0 -552
  601. package/src/feedback/feedback-labels.ts +0 -144
  602. package/src/feedback/feedback-menu-labels.ts +0 -12
  603. package/src/feedback/feedback-menu.tsx +0 -177
  604. package/src/feedback/feedback-mutation.ts +0 -20
  605. package/src/feedback/feedback-page-link.ts +0 -90
  606. package/src/feedback/feedback-public-labels.ts +0 -81
  607. package/src/feedback/feedback-public-list.tsx +0 -524
  608. package/src/feedback/feedback-public-switch.tsx +0 -102
  609. package/src/feedback/feedback-public-types.ts +0 -101
  610. package/src/feedback/feedback-record.ts +0 -428
  611. package/src/feedback/feedback-replies-context.ts +0 -9
  612. package/src/feedback/feedback-replies.tsx +0 -343
  613. package/src/feedback/feedback-rework-entry-chip.tsx +0 -18
  614. package/src/feedback/feedback-row-detail-labels.ts +0 -31
  615. package/src/feedback/feedback-row-detail.tsx +0 -871
  616. package/src/feedback/feedback-status-undo.ts +0 -109
  617. package/src/feedback/feedback-submit-dialog.tsx +0 -131
  618. package/src/feedback/feedback-submit.tsx +0 -270
  619. package/src/feedback/feedback-swipe.tsx +0 -192
  620. package/src/feedback/feedback-table-labels.ts +0 -27
  621. package/src/feedback/feedback-table.tsx +0 -912
  622. package/src/feedback/feedback-thread-labels.ts +0 -22
  623. package/src/feedback/feedback-thread-parts-labels.ts +0 -41
  624. package/src/feedback/feedback-thread.tsx +0 -905
  625. package/src/feedback/feedback-vocabulary.ts +0 -263
  626. package/src/feedback/feedback-vote-button.tsx +0 -124
  627. package/src/feedback.ts +0 -116
  628. package/src/hooks/use-anchored-panel.ts +0 -396
  629. package/src/hooks/use-anchored-rect.ts +0 -77
  630. package/src/hooks/use-announce.ts +0 -85
  631. package/src/hooks/use-authed-src.ts +0 -167
  632. package/src/hooks/use-body-scroll-lock.ts +0 -119
  633. package/src/hooks/use-breakpoint.ts +0 -133
  634. package/src/hooks/use-close-transition.ts +0 -107
  635. package/src/hooks/use-copy-to-clipboard.ts +0 -122
  636. package/src/hooks/use-debounce.ts +0 -114
  637. package/src/hooks/use-dismiss.ts +0 -371
  638. package/src/hooks/use-document-title.ts +0 -33
  639. package/src/hooks/use-file-drop.ts +0 -121
  640. package/src/hooks/use-file-picker-labels.ts +0 -28
  641. package/src/hooks/use-file-picker.tsx +0 -359
  642. package/src/hooks/use-focus-trap.ts +0 -199
  643. package/src/hooks/use-hotkey.ts +0 -212
  644. package/src/hooks/use-large-text.ts +0 -41
  645. package/src/hooks/use-long-press.ts +0 -140
  646. package/src/hooks/use-media-query.ts +0 -39
  647. package/src/hooks/use-mounted.ts +0 -39
  648. package/src/hooks/use-noindex.ts +0 -37
  649. package/src/hooks/use-overlay-history.ts +0 -714
  650. package/src/hooks/use-row-swipe.ts +0 -207
  651. package/src/hooks/use-search-param-state.ts +0 -434
  652. package/src/hooks/use-windowed-rows.ts +0 -225
  653. package/src/i18n/defaults.ts +0 -267
  654. package/src/i18n/german.ts +0 -2059
  655. package/src/i18n/kit-labels.tsx +0 -1003
  656. package/src/i18n/languages.ts +0 -349
  657. package/src/i18n/locales/de-CH.ts +0 -24
  658. package/src/i18n/locales/en.ts +0 -267
  659. package/src/i18n/locales/es.ts +0 -1999
  660. package/src/i18n/locales/fr.ts +0 -2037
  661. package/src/i18n/locales/hu.ts +0 -2004
  662. package/src/i18n/locales/it.ts +0 -2014
  663. package/src/i18n/locales/zh.ts +0 -1912
  664. package/src/i18n/plurals.ts +0 -86
  665. package/src/i18n/review.ts +0 -475
  666. package/src/i18n/shared-labels.ts +0 -138
  667. package/src/i18n/swiss.ts +0 -29
  668. package/src/index.ts +0 -1191
  669. package/src/landing/access.ts +0 -93
  670. package/src/landing/landing-actions.tsx +0 -97
  671. package/src/landing/landing-labels.ts +0 -59
  672. package/src/landing/landing-sections.tsx +0 -341
  673. package/src/landing/page-seo.ts +0 -261
  674. package/src/landing/public-header.tsx +0 -121
  675. package/src/landing/public-page.tsx +0 -106
  676. package/src/landing/routing.tsx +0 -225
  677. package/src/lib/broadcast.ts +0 -52
  678. package/src/lib/calc.ts +0 -323
  679. package/src/lib/chip-edge-snap.ts +0 -212
  680. package/src/lib/clipboard-minus.ts +0 -107
  681. package/src/lib/clipping.ts +0 -34
  682. package/src/lib/cn.ts +0 -28
  683. package/src/lib/column-mapping.ts +0 -234
  684. package/src/lib/countries.ts +0 -91
  685. package/src/lib/data-attributes.ts +0 -14
  686. package/src/lib/date-format.ts +0 -227
  687. package/src/lib/dates.ts +0 -288
  688. package/src/lib/day-format.ts +0 -96
  689. package/src/lib/decimal-marks.ts +0 -108
  690. package/src/lib/describe-failure.ts +0 -18
  691. package/src/lib/device-settings.ts +0 -147
  692. package/src/lib/direction.ts +0 -74
  693. package/src/lib/document-navigation.ts +0 -17
  694. package/src/lib/document-title.ts +0 -41
  695. package/src/lib/download.ts +0 -172
  696. package/src/lib/float-clearance.ts +0 -70
  697. package/src/lib/floating-stack.ts +0 -70
  698. package/src/lib/focus-left.ts +0 -156
  699. package/src/lib/format.ts +0 -585
  700. package/src/lib/http-status.ts +0 -38
  701. package/src/lib/iban.ts +0 -251
  702. package/src/lib/ime.ts +0 -24
  703. package/src/lib/intl-cache.ts +0 -27
  704. package/src/lib/iso-day.ts +0 -13
  705. package/src/lib/lazy-with-fallback.ts +0 -82
  706. package/src/lib/logger.ts +0 -69
  707. package/src/lib/minus.ts +0 -50
  708. package/src/lib/path-pattern.ts +0 -55
  709. package/src/lib/person-name.ts +0 -155
  710. package/src/lib/phone.ts +0 -208
  711. package/src/lib/plain-text.ts +0 -22
  712. package/src/lib/presence.ts +0 -18
  713. package/src/lib/qr-encode.ts +0 -444
  714. package/src/lib/quiet-focus.ts +0 -32
  715. package/src/lib/reduced-motion.ts +0 -22
  716. package/src/lib/refs.ts +0 -22
  717. package/src/lib/round.ts +0 -43
  718. package/src/lib/safe-storage.ts +0 -119
  719. package/src/lib/server-wake.ts +0 -315
  720. package/src/lib/session-refresh.ts +0 -472
  721. package/src/lib/share-format.ts +0 -18
  722. package/src/lib/status-tone.ts +0 -43
  723. package/src/lib/strip-fade.ts +0 -287
  724. package/src/lib/table-text.ts +0 -554
  725. package/src/lib/text-case.ts +0 -19
  726. package/src/lib/text-match.ts +0 -169
  727. package/src/lib/thenable.ts +0 -19
  728. package/src/lib/translation-review.ts +0 -769
  729. package/src/notifications/device-zone-report.ts +0 -67
  730. package/src/notifications/feedback-notification-types-labels.ts +0 -33
  731. package/src/notifications/feedback-notification-types.ts +0 -71
  732. package/src/notifications/notification-bell.tsx +0 -119
  733. package/src/notifications/notification-inbox.tsx +0 -309
  734. package/src/notifications/notification-parts.tsx +0 -81
  735. package/src/notifications/notification-types-setting.tsx +0 -406
  736. package/src/notifications/notification-types.ts +0 -89
  737. package/src/notifications/notifications-labels.ts +0 -287
  738. package/src/notifications/push-device-setting.tsx +0 -230
  739. package/src/notifications/push-devices-setting.tsx +0 -374
  740. package/src/notifications/push-worker.ts +0 -168
  741. package/src/notifications/quiet-hours-setting.tsx +0 -238
  742. package/src/notifications/unsubscribe-panel.tsx +0 -200
  743. package/src/notifications/use-push-device.ts +0 -448
  744. package/src/offline/local-first.ts +0 -100
  745. package/src/offline/pwa-update-prompt-labels.ts +0 -9
  746. package/src/offline/pwa-update-prompt.tsx +0 -78
  747. package/src/offline/query-persistence.ts +0 -391
  748. package/src/offline/service-worker-updates.ts +0 -81
  749. package/src/offline/shared-computer-labels.ts +0 -8
  750. package/src/offline/shared-computer.tsx +0 -87
  751. package/src/offline/sign-out-pending-dialog-labels.ts +0 -21
  752. package/src/offline/sign-out-pending-dialog.tsx +0 -135
  753. package/src/offline.ts +0 -37
  754. package/src/push-worker.ts +0 -25
  755. package/src/rhf/fields.tsx +0 -2187
  756. package/src/rhf/form-core.tsx +0 -324
  757. package/src/rhf/form.tsx +0 -12
  758. package/src/rhf/line-items.tsx +0 -135
  759. package/src/rhf/use-rhf-wizard-step.ts +0 -104
  760. package/src/rhf.ts +0 -21
  761. package/src/search/command-palette-labels.ts +0 -16
  762. package/src/search/command-palette.tsx +0 -624
  763. package/src/search/global-search-labels.ts +0 -13
  764. package/src/search/global-search.tsx +0 -660
  765. package/src/search/search-index.ts +0 -283
  766. package/src/search/use-command-key.ts +0 -23
  767. package/src/search.ts +0 -14
  768. package/src/settings/device-reset-setting-labels.ts +0 -20
  769. package/src/settings/device-reset-setting.tsx +0 -116
  770. package/src/settings/settings-card-classes.ts +0 -11
  771. package/src/settings/settings-catalogue.ts +0 -150
  772. package/src/settings/settings-context.ts +0 -33
  773. package/src/settings/settings-heading.tsx +0 -67
  774. package/src/settings/settings-labels.ts +0 -87
  775. package/src/settings/settings-layout.tsx +0 -471
  776. package/src/settings/settings-section.tsx +0 -108
  777. package/src/settings/use-account-appearance.ts +0 -148
  778. package/src/settings/use-account-language.ts +0 -146
  779. package/src/settings/use-settings-focus.ts +0 -128
  780. package/src/settings/use-settings-route.ts +0 -258
  781. package/src/shell/account-menu-language.tsx +0 -130
  782. package/src/shell/account-menu-text-size.tsx +0 -80
  783. package/src/shell/admin-menu.tsx +0 -183
  784. package/src/shell/app-shell-labels.ts +0 -11
  785. package/src/shell/app-shell.tsx +0 -1560
  786. package/src/shell/auth-layout.tsx +0 -186
  787. package/src/shell/company-switcher-labels.ts +0 -13
  788. package/src/shell/company-switcher.tsx +0 -136
  789. package/src/shell/dev-menu.tsx +0 -257
  790. package/src/shell/fill-height.tsx +0 -45
  791. package/src/shell/language-code.ts +0 -5
  792. package/src/shell/option-switcher-menu.tsx +0 -84
  793. package/src/shell/sync-status-indicator.tsx +0 -303
  794. package/src/shell/top-bar-actions.tsx +0 -87
  795. package/src/shell/top-bar-brand.tsx +0 -78
  796. package/src/shell/top-bar-parts-labels.ts +0 -148
  797. package/src/shell/top-bar.tsx +0 -40
  798. package/src/shell/topbar-action-menu.tsx +0 -567
  799. package/src/shell/topbar-controls.tsx +0 -269
  800. package/src/shell/tour-menu.tsx +0 -187
  801. package/src/shell.ts +0 -108
  802. package/src/sync/db.ts +0 -129
  803. package/src/sync/engine.ts +0 -1011
  804. package/src/sync/hooks.ts +0 -98
  805. package/src/sync/testing.ts +0 -73
  806. package/src/sync/wire.ts +0 -61
  807. package/src/sync.ts +0 -37
  808. package/src/table-text.ts +0 -8
  809. package/src/testing/css-variables.generated.ts +0 -112
  810. package/src/testing/css-variables.ts +0 -189
  811. package/src/testing/index.ts +0 -29
  812. package/src/testing/react.ts +0 -58
  813. package/src/testing/tour.ts +0 -41
  814. package/src/testing/viewport.ts +0 -357
  815. package/src/theme/chart-palette.ts +0 -232
  816. package/src/theme/color.ts +0 -272
  817. package/src/theme/contrast-tokens.ts +0 -268
  818. package/src/theme/contrast.ts +0 -250
  819. package/src/theme/palette-derive.ts +0 -825
  820. package/src/theme/palette-presets.ts +0 -544
  821. package/src/theme/palette-store.ts +0 -108
  822. package/src/theme/text-size.ts +0 -289
  823. package/src/theme/theme-store.ts +0 -157
  824. package/src/tour/tour-catalogue-labels.ts +0 -75
  825. package/src/tour/tour-catalogue.tsx +0 -335
  826. package/src/tour/tour-dock.ts +0 -134
  827. package/src/tour/tour-engine.ts +0 -37
  828. package/src/tour/tour-gates.ts +0 -218
  829. package/src/tour/tour-guide-button.tsx +0 -64
  830. package/src/tour/tour-guide-labels.ts +0 -15
  831. package/src/tour/tour-info.ts +0 -144
  832. package/src/tour/tour-labels.ts +0 -23
  833. package/src/tour/tour-overlay.tsx +0 -1170
  834. package/src/tour/tour.tsx +0 -750
  835. package/src/tour/use-tour-progress.ts +0 -671
  836. package/src/tour/use-tour-url-sync.ts +0 -266
  837. package/src/tour.ts +0 -21
  838. package/src/vite-env.d.ts +0 -23
  839. package/src/wizard/stepper-nav.tsx +0 -360
  840. package/src/wizard/types.ts +0 -275
  841. package/src/wizard/use-wizard.ts +0 -560
  842. package/src/wizard/validation.ts +0 -39
  843. package/src/wizard/wizard-context.tsx +0 -74
  844. package/src/wizard/wizard-labels.ts +0 -29
  845. package/src/wizard/wizard-step.tsx +0 -76
  846. package/src/wizard/wizard-summary.tsx +0 -84
  847. package/src/wizard.ts +0 -15
@@ -1,1184 +0,0 @@
1
- // A tile chart ("treemap"): one rectangle per node, its AREA the node's share of what
2
- // is drawn. Lifted out of keksdose's reports, where five cards drew it (spending by
3
- // group and by category, net worth by account and by type, allocation, payees, food
4
- // groups) — every domain word is gone and every string it could print is a prop.
5
- //
6
- // It uses the kit's own chart shell (`ChartContainer`, `ChartTooltipContent`) and the
7
- // kit's own ink rule (`textOn`), so it re-skins with the palette like every other
8
- // chart. The one thing it cannot take from a CSS var is the tile colour: the label ink
9
- // is MEASURED against the fill, and a `var(--chart-3)` string measures as nothing — see
10
- // `useChartTokenHex` below for how the concrete hex is found instead.
11
- //
12
- // Since 0.52 a node can hold `children`: a group, drawn as a labelled frame round its
13
- // tiles (keksdose dev #642, shops by chain). Where every node is drawn is the map's own
14
- // layout (treemap-layout.ts), not recharts': see "Groups: a frame round their tiles".
15
- import {
16
- createContext,
17
- useCallback,
18
- useContext,
19
- useLayoutEffect,
20
- useMemo,
21
- useRef,
22
- useState,
23
- useSyncExternalStore,
24
- } from "react";
25
- import type { ComponentProps, ComponentPropsWithoutRef, KeyboardEvent, ReactElement, ReactNode } from "react";
26
- import { Treemap as RechartsTreemap, useChartHeight, useChartWidth } from "recharts";
27
- import { ChartContainer, ChartTooltip, ChartTooltipContent, type ChartConfig } from "./chart";
28
- import { CHART_ROLES, PALETTE_HEX, PALETTE_SIZE, lerpHex, textOn } from "../theme/chart-palette";
29
- import { resolveChartColor, useChartEdge, useEdgesInForce, type ChartEdges } from "./chart-edge";
30
- import { parseHex } from "../theme/color";
31
- import { dirOf, rovingTarget, type Direction } from "../lib/direction";
32
- import { shareFormat } from "../lib/share-format";
33
- import { cn } from "../lib/cn";
34
- import { useTextSize } from "../theme/text-size";
35
- import { chartHeightProps, type ChartHeight } from "./chart-height";
36
- import { formatWithMinus } from "../lib/minus";
37
- import { useKitLabels, useKitLocale, type LabelOverride } from "../i18n/kit-labels";
38
- import { DEFAULT_TREEMAP_LABELS, type TreemapLabels } from "./treemap-labels";
39
- import { groupBody, layoutTreemap, type Box, type Placed } from "./treemap-layout";
40
-
41
- /**
42
- * One tile.
43
- *
44
- * `value`, not keksdose's `total`: a treemap's areas are not always money (keksdose
45
- * already sizes one by transaction COUNT), and `value` is the word every other chart
46
- * in recharts uses for the number a mark is sized by. Migrating is a rename at the
47
- * mapping step — `{ id, name, value: row.total }`.
48
- */
49
- export interface TreemapNode {
50
- /** Stable key, handed back by {@link TreemapProps.onNodeClick} and
51
- * {@link TreemapProps.nodeNote}. Names can collide; ids must not among a group's own
52
- * nodes (or the top's). A group and a tile inside it may share one — chain 12 and shop
53
- * 12 from two tables — and `onNodeClick`'s `groups` tells them apart (0.52). */
54
- id: string;
55
- name: string;
56
- /** The tile's area. A node whose value is not a positive finite number is NOT
57
- * drawn — an area cannot be negative, and recharts lays a negative size out as
58
- * nonsense rather than refusing it. Saying so is the caller's job: a group that
59
- * vanishes silently between two views of the same data is a bug report.
60
- *
61
- * A GROUP's own `value` is not read: its area is its drawn children's together. Pass
62
- * that sum — the field stays required so that every reader of a `TreemapNode[]` keeps
63
- * a `number`. */
64
- value: number;
65
- /** Overrides the by-index palette colour for this tile — e.g. to paint every child
66
- * in its parent group's colour, so the groups stay legible in one picture. A
67
- * concrete `#rrggbb` or a bare `var(--chart-N)` keeps the label ink measured — so
68
- * does `CHART_ROLES.other` (`var(--chart-muted)`), the colour for a "rest" bucket,
69
- * which should not wrap round to a hue the reader matches to a real node;
70
- * anything else (another var, a `color-mix()`) gets `currentColor` ink, because
71
- * there is nothing to measure — set `labelColor` then.
72
- *
73
- * On a group, the colour of every tile inside it that sets none (0.52): keksdose's
74
- * "hue = chain" is a `fill` per chain. A group left without one takes the ramp by its
75
- * index, as a tile does, and hands that down; its frame is drawn in the page's own
76
- * colours whatever it is. */
77
- fill?: string;
78
- /** The label's ink, for a `fill` the kit cannot measure (a `var()` or `color-mix()`):
79
- * keksdose's recency plot fills with `color-mix(in srgb, var(--chart-1) N%,
80
- * transparent)`, where `currentColor` put dark page text on full-strength indigo
81
- * (~1.5:1). Wins over the measured ink when given. A tile that takes its group's
82
- * `fill` takes the group's `labelColor` with it; one with a `fill` of its own, or one
83
- * that `shade` turns into another colour, is measured again. */
84
- labelColor?: string;
85
- /**
86
- * The tile's colour strength, 0–1 (0.52): its `fill` mixed into the card's colour
87
- * (`--bg-surface`) as a concrete hex, `1` the full hue. For a second, ordered quantity
88
- * on top of the hue — keksdose's recency steps (1, 0.78, 0.55) over the chain's hue —
89
- * without a `color-mix()`: the result is measured like any hex, so the label ink stays
90
- * automatic and no `labelColor` is needed. keksdose found a tile under ~0.55 stops
91
- * reading as its hue and passes for the muted "other" (#642).
92
- *
93
- * Only a fill the kit can measure is shaded (a hex, a bare `var(--chart-N)` or another
94
- * bare token), and only on a page that declares `--bg-surface` as a hex; anything else
95
- * keeps its full hue. On a group, the default for its tiles, as `fill` is.
96
- */
97
- shade?: number;
98
- /**
99
- * Makes this node a GROUP (0.52, keksdose dev #642: "Is there already a distinction
100
- * between shop and chain?"): a labelled frame, its name in a band along the top, its
101
- * tiles laid out inside it by the map's own layout, each at its share of the whole
102
- * picture (see "Groups: a frame round their tiles"). Groups nest. A group with nothing
103
- * drawable inside is not drawn; an empty array is a tile.
104
- *
105
- * The frame is in the page's colours (the well, `--bg-surface-2`, under
106
- * `--text-primary`, at least 7.4:1 in every preset and contrast), and under More
107
- * contrast it is outlined in `--chart-ink` as a pale tile is. Its band is drawn only
108
- * where the group has the room for it — see `TreemapCell`.
109
- */
110
- children?: readonly TreemapNode[];
111
- }
112
-
113
- /**
114
- * Everything a `<div>` takes reaches the chart's root (`ChartContainer`), so a test id
115
- * or a `data-tour` anchor can find it. `children` is the chart's own.
116
- *
117
- * The root is a `role="figure"` (0.52, refactor audit 2026-10-30, DSP-5): name it with
118
- * `aria-label` ("Spending by category"), and say what it shows with {@link
119
- * TreemapProps.description}.
120
- */
121
- export interface TreemapProps extends Omit<ComponentPropsWithoutRef<"div">, "children"> {
122
- data: readonly TreemapNode[];
123
- /**
124
- * Formats a node's value in the tooltip. Handed the node's SHARE of what is drawn
125
- * (0–1) as a second argument, because a tile is read for how big it is relative to
126
- * the rest, and "€412.30" alone makes the reader estimate that ratio off the areas.
127
- * The share is `undefined` when the drawn total is not positive.
128
- *
129
- * A tile's and a group's accessible name (`labels.tile`, `labels.group`, 0.52) says the
130
- * value too, asked of the formatter as the tooltip asks it, share and all, and its text
131
- * is used; markup is said as the bare number. The name says the share itself, so a
132
- * formatter that writes it into its text has it said twice — say so with
133
- * {@link TreemapProps.shareInValue}.
134
- *
135
- * Default: the bare number through `Intl.NumberFormat` in the provider's locale (0.52,
136
- * DOC-10: before, the runtime's). A host with a currency passes this — the kit knows
137
- * none.
138
- */
139
- valueFormatter?: (value: number, share: number | undefined) => ReactNode;
140
- /**
141
- * The `valueFormatter`'s text already says the share ("CHF 412.30 · 12%"), so a tile's
142
- * and a group's accessible name leave their own " (12%)" out (0.53, keksdose after 0.52):
143
- * the map hands `labels.tile` and `labels.group` the share as `""`, and every catalogue's
144
- * template drops it there, in its own typography. Before, the only way out was to replace
145
- * both templates, and with them the kit's seven catalogues. The tooltip is the same either
146
- * way: it prints what the formatter prints. A formatter whose value is markup is said as
147
- * the bare number, so its share is then not said at all.
148
- *
149
- * Default `false`: the name says the share.
150
- */
151
- shareInValue?: boolean;
152
- /**
153
- * What the map SHOWS, in a sentence — "Rent is three fifths of the month." — the
154
- * figure's accessible description (0.52, DSP-5). Not drawn; a caption the eye should
155
- * read too belongs in the page.
156
- */
157
- description?: string;
158
- /** Per-map strings over `<UiKitProvider labels={{ treemap }}>`. */
159
- labels?: LabelOverride<TreemapLabels>;
160
- /** Chart height: a CSS length (`"20rem"`, `"min(20rem, 50dvh)"`), a Tailwind class,
161
- * or px. Default `"20rem"` — 320 px at Normal, growing with the text size like the
162
- * labels in the tiles (0.32, docs/text-size-harmonization.md §10.10). */
163
- height?: ChartHeight;
164
- /**
165
- * A tile was activated — by click, or by Enter/Space on a focused tile. Setting this
166
- * makes every tile a `role="button"`.
167
- *
168
- * Either way every tile is reachable (0.52, DSP-5): ONE tab stop for the map, the
169
- * arrows (←/→ along the reading direction, ↓/↑), Home and End between tiles in the
170
- * order given, each named "name: value (share)" (`labels.tile`) — a `role="img"` to read
171
- * when nothing listens, as PieChart's slices. Before, a map nobody clicked was nothing
172
- * to a screen reader, and a clickable tile said its name alone, one tab stop each.
173
- *
174
- * In a map with groups (`TreemapNode.children`, 0.52) the walk is the data's own order,
175
- * depth first: a group's frame, then its tiles, then the next group. A frame is named
176
- * by `labels.group` ("Interspar, group: €1,240 (40%)"), a tile inside one by
177
- * `labels.inGroup` round its own name ("Rankweil: €412 (12%), in Interspar"). Only
178
- * tiles reach this callback (a frame reaches `onGroupClick`); a tile inside a group is
179
- * handed a third argument, the ids of its groups, outermost first. Optional in the type,
180
- * so a forwarder calling this with two arguments still type-checks — and absent for a
181
- * tile at the top, so a flat map calls it exactly as before.
182
- */
183
- onNodeClick?: (id: string, name: string, groups?: readonly string[]) => void;
184
- /**
185
- * A group's frame was activated — by click on its band or margin, or by Enter/Space on
186
- * the focused frame (0.52). Setting this makes every frame a `role="button"`; without it
187
- * a frame is a `role="img"`, still a stop of the map's keyboard so its total is heard.
188
- * `groups` as for {@link TreemapProps.onNodeClick}: the groups round this one, passed
189
- * only for a group inside another.
190
- */
191
- onGroupClick?: (id: string, name: string, groups?: readonly string[]) => void;
192
- /**
193
- * The node names are the user's own data (payees, not categories): the tile labels
194
- * and the whole tooltip bubble get `data-private`, the same attribute the kit's
195
- * `Tooltip redact` and `ChartTooltipContent`'s value cell carry, so the host's one
196
- * privacy rule reaches them. Off by default — a category name is a label of the app.
197
- *
198
- * Every tile is tagged as a whole too, clickable or not (0.52): its accessible name
199
- * (`labels.tile`) says the name and the value, in an attribute a host's redaction
200
- * reaches only through the tag.
201
- *
202
- * - `true`: every node — the tiles, and a group's frame (its `<g>` and its band's name)
203
- * and tooltip bubble too.
204
- * - `"tiles"` (0.53, keksdose after 0.52): the tiles as `true` tags them, and no group —
205
- * for groups that are public names over private ones, as chains (Migros, Coop) over
206
- * the shops a user paid. Its frame's band, its frame and its bubble's name are
207
- * untagged, and so is the frame's accessible name (`labels.group`), which says the
208
- * group's TOTAL: use it only where the groups' names and totals are not the private
209
- * part. The bubble's value cell is tagged whatever this says, as in every chart.
210
- * A tile's name still says its group's (`labels.inGroup`), under the tile's tag.
211
- */
212
- redactNames?: boolean | "tiles";
213
- /**
214
- * A second, smaller line under a tile's label — one extra figure per node that the
215
- * name cannot carry (a year-on-year `+12%`, `new`). Return `undefined` for a node
216
- * with nothing to add. Never tagged `data-private`: a ratio reveals no amount.
217
- *
218
- * ON THE TILE rather than in the tooltip, for two reasons. The tooltip identifies a
219
- * node by name or by raw value, so a per-node note would be keyed on an amount (two
220
- * nodes of equal size would swap notes). And a tooltip is a pointer affordance: a
221
- * phone never hovers, so a figure that only exists on hover does not exist there.
222
- *
223
- * Asked for tiles only: a group's band holds its name and nothing else.
224
- */
225
- nodeNote?: (id: string) => string | undefined;
226
- /**
227
- * Draw at most this many tiles (the first N drawable nodes, in the order given —
228
- * sort first). Unbounded by default. A tile under ~44x20px has no room for a label,
229
- * and a map made of unlabelled rectangles answers nothing; around 24 is where the
230
- * smallest tile of a skewed series reaches that floor on a phone-width card. The
231
- * caller owns the "N more not shown" line, since it knows how many it passed.
232
- *
233
- * With groups, the tiles are counted across them in the order given, depth first; a
234
- * group left with none is not drawn, and a group's frame is not a tile.
235
- */
236
- maxTiles?: number;
237
- /**
238
- * The categorical ramp tiles take by index, as concrete hex. Default: the page's
239
- * live `--chart-1…8` tokens (see `useChartTokenHex`), cycled after eight like
240
- * `paletteFor` (0.49). Pass one when the host
241
- * already holds the active palette — e.g. `createPaletteStore`'s `useChartHex()` —
242
- * so a caller colouring a legend beside the map is guaranteed the same list.
243
- */
244
- colors?: readonly string[];
245
- /**
246
- * How tiles are parted (0.51.6, keksdose dev #642, Marcel: "The borders do not look
247
- * nice when changing from tile to tile. Can this be uniform black?" — and then, on the
248
- * contrast rule that would go with it: follow the contrast setting).
249
- *
250
- * - `"auto"` (default): `"uniform"` at Standard contrast, `"contrast"` while More
251
- * contrast is in force (`<html data-contrast="more">`: the reader chose it, or chose
252
- * System on a device that asks for more). Followed live.
253
- * - `"uniform"`: one 2px gutter in the page's ground colour (`--bg-page`, near-black
254
- * in the dark theme) round every tile, and nothing else. Tiles always abut, so the
255
- * gutter parts them; each tile is named by its label.
256
- * - `"contrast"`: the 0.49 rule. The gutter is the card's colour (`--bg-surface`),
257
- * and a tile whose hue is under 3:1 against the card gets a 1px ink edge inside it
258
- * (WCAG 1.4.11, `useChartEdge`). In the dark theme that ink is light, so some tiles
259
- * carry a white outline and their neighbours none — the unevenness #642 saw, and
260
- * the price of the edge for a reader who asked for it.
261
- */
262
- edges?: TreemapEdges;
263
- }
264
-
265
- /** {@link TreemapProps.edges}. The switch itself is chart-edge's `useEdgesInForce`,
266
- * which SeriesChart's bars and areas follow too since 0.52 (DSP-18). */
267
- export type TreemapEdges = ChartEdges;
268
-
269
- // ── The categorical ramp, as concrete hex ──────────────────────────────────
270
- //
271
- // The label ink is measured against the tile's fill, so the fill has to be a colour
272
- // and not a reference to one. keksdose read it from its palette STORE, which is an
273
- // app object the kit cannot import. What the kit does own is the token layer: whether
274
- // a palette arrives through `applyTokenSet` (inline properties on <html>) or through
275
- // tokens.css (`:root` / `.dark`), the resolved `--chart-N` on <html> is the colour
276
- // every other chart on the page is being painted with. So that is what is read, and
277
- // re-read whenever <html>'s class or style changes — which is exactly when a theme
278
- // toggle or a palette switch lands.
279
-
280
- const CHART_TOKENS = PALETTE_SIZE;
281
-
282
- /** The resolved ramp, and after a `|` the card's colour, joined into ONE string so
283
- * `useSyncExternalStore` compares a primitive: a fresh array per read would never be
284
- * `Object.is`-equal to the last and would re-render forever. */
285
- function readChartTokens(): string {
286
- const root = document.documentElement;
287
- const fallback = root.classList.contains("dark") ? PALETTE_HEX.dark : PALETTE_HEX.light;
288
- const style = getComputedStyle(root);
289
- const ramp = Array.from({ length: CHART_TOKENS }, (_, i) => {
290
- const v = style.getPropertyValue(`--chart-${i + 1}`).trim();
291
- // An unresolved token (a host that never loaded tokens.css, a test DOM) falls
292
- // back to the built-in ramp for the current theme rather than to no colour.
293
- return parseHex(v) ? v : fallback[i];
294
- }).join(",");
295
- // The card, which `TreemapNode.shade` mixes a hue into (0.52). No fallback: a page that
296
- // declares no `--bg-surface` hex leaves nothing to mix into, and the tile keeps its hue.
297
- const card = style.getPropertyValue("--bg-surface").trim();
298
- return `${ramp}|${parseHex(card) ? card : ""}`;
299
- }
300
-
301
- function subscribeChartTokens(onChange: () => void): () => void {
302
- const observer = new MutationObserver(onChange);
303
- observer.observe(document.documentElement, {
304
- attributes: true,
305
- attributeFilter: ["class", "style", "data-theme"],
306
- });
307
- return () => observer.disconnect();
308
- }
309
-
310
- const SERVER_TOKENS = `${PALETTE_HEX.light.join(",")}|`;
311
-
312
- interface ChartTokens {
313
- ramp: readonly string[];
314
- /** `--bg-surface` as hex, or null when the page declares none. */
315
- card: string | null;
316
- }
317
-
318
- function useChartTokenHex(): ChartTokens {
319
- const joined = useSyncExternalStore(subscribeChartTokens, readChartTokens, () => SERVER_TOKENS);
320
- // One object per reading, not per render: the map's nodes are memoised on it.
321
- return useMemo(() => {
322
- const [ramp = "", card = ""] = joined.split("|");
323
- return { ramp: ramp.split(","), card: card || null };
324
- }, [joined]);
325
- }
326
-
327
- const CHART_TOKEN_REF = /^var\(\s*--chart-(\d+)\s*\)$/;
328
-
329
- /**
330
- * The hex a tile fill measures as: the fill itself when it is hex, the live token when
331
- * it is a bare `var(--chart-N)` (what `paletteFor` returns), otherwise null. Without
332
- * the token case a hand-built recharts map coloured with the kit's own `paletteFor`
333
- * got `currentColor` ink — dark page text on dark indigo in the light theme.
334
- */
335
- function measurableFill(fill: string, tokens: readonly string[]): string | null {
336
- if (parseHex(fill)) return fill;
337
- const m = CHART_TOKEN_REF.exec(fill.trim());
338
- const hex = m ? tokens[Number(m[1]) - 1] : undefined;
339
- if (hex && parseHex(hex)) return hex;
340
- // Any other bare token the page declares — `var(--chart-muted)` for a rest bucket,
341
- // `var(--chart-9)`, a money colour — read off <html> as it resolves.
342
- if (typeof document === "undefined") return null;
343
- return resolveChartColor(fill, getComputedStyle(document.documentElement));
344
- }
345
-
346
- // ── What a tile reads from its map ───────────────────────────────────────
347
- //
348
- // 0.52 (refactor audit 2026-10-30, DSP-22): every tile resolved the token ramp, the ink
349
- // edge and the edge rule itself — three stores, each with a MutationObserver on <html>, so
350
- // 72 on keksdose's 24-tile maps, and up to 29 custom properties re-read on every render of
351
- // every tile. `Treemap` resolves them once and hands them down here; a TreemapCell drawn
352
- // in a caller's own recharts `<Treemap>` has no map above it and resolves its own.
353
-
354
- interface TileContext {
355
- tokens: readonly string[];
356
- edgeFor: (fill: string | undefined) => string | undefined;
357
- edges: "uniform" | "contrast";
358
- /** The map's keyboard (DSP-5): absent for a standalone cell. */
359
- keys?: TileKeys;
360
- /** Where every node is drawn in a chart of this size (see "Groups"), by path: absent
361
- * for a standalone cell, which draws where recharts laid it. */
362
- layout?: (width: number, height: number, scale: number) => ReadonlyMap<string, Placed>;
363
- /** The pointer is on the node drawn at `box`: where the map's tooltip stands. */
364
- hover?: (box: Box) => void;
365
- }
366
-
367
- /**
368
- * Keyed by a node's PATH (`MapNode.path`), not its id: a group and a tile may share an id
369
- * — chain 12 and shop 12 come from two tables — and keyed by id the map had two tab stops,
370
- * the frame said the tile's name and the arrows stuck (0.52 code review of treemap.tsx,
371
- * finding 3).
372
- */
373
- interface TileKeys {
374
- /** A tile's or a group's accessible name (`labels.tile`, `labels.group`). */
375
- name: (path: string) => string | undefined;
376
- onFocus: (path: string) => void;
377
- /** A node's element as it mounts (and `null` as it goes): the map sets its `tabindex` and
378
- * gives it back the focus its previous element held (see the map's keyboard). */
379
- register: (path: string, element: SVGGElement | null) => void;
380
- /** Moves focus for an arrow, Home or End pressed on node `path`; `false` for any other key. */
381
- move: (path: string, event: KeyboardEvent<SVGGElement>) => boolean;
382
- }
383
-
384
- const TreemapTiles = createContext<TileContext | null>(null);
385
-
386
- // ── Groups: a frame round their tiles ─────────────────────────────────────
387
- //
388
- // 0.52 (keksdose dev #642). A group is a frame with its name in a band along the top, its
389
- // tiles inside it. recharts lays nested data out edge to edge, with no room for a band, so
390
- // the map lays it out ITSELF (treemap-layout.ts): squarified as recharts does it, each
391
- // group given its frame's area on top of its tiles' — so a tile's area stays its share of
392
- // the picture in any group (0.52 code review of treemap.tsx, finding 1) — and computed up
393
- // front from the drawn nodes and the chart's size, which every cell reads by its path.
394
- // Before, a group's cell made the room during render and left it in a module cache for its
395
- // children's cells, which held only while recharts rendered a group before its children
396
- // (finding 8). recharts still renders each node's cell, and its tooltip; the map stands the
397
- // tooltip at the tile as drawn (`TreemapTooltip`), since recharts' own anchor is the centre
398
- // of the rectangle IT laid out.
399
-
400
- // ── Tile ──────────────────────────────────────────────────────────────────
401
-
402
- /** The name's size in px at Normal; a tile draws it × the text size's scale (§10.10), and
403
- * every distance below that is measured in lines grows with it. */
404
- const LABEL_FONT_SIZE = 12;
405
- /** The optional second line, a step down from the name so the tile reads name-first. */
406
- const NOTE_FONT_SIZE = 11;
407
-
408
- /**
409
- * Fit a label to its tile: the text itself when it fits, an ellipsised head when it
410
- * does not, `null` when not even three characters would. Without this a long name ran
411
- * on across its neighbours and collided with THEIR labels. 0.58em is the average
412
- * advance of a UI sans at this weight; it only needs to be close, since the tile keeps
413
- * 6px of padding either side. The full name stays in the tooltip.
414
- */
415
- export function fitLabel(name: string, boxWidth: number, fontSize: number): string | null {
416
- const max = Math.floor((boxWidth - 12) / (fontSize * 0.58));
417
- if (max < 3) return null;
418
- return name.length <= max ? name : `${name.slice(0, max - 1).trimEnd()}…`;
419
- }
420
-
421
- /** A tile's text, painted over a halo in its tile's colour (see `TreemapCell`). */
422
- const haloOf = (halo: string) => ({
423
- stroke: halo,
424
- strokeWidth: 2.5,
425
- strokeLinejoin: "round" as const,
426
- paintOrder: "stroke",
427
- });
428
-
429
- /** A band's name: the page's text on the well, 8.2:1 at the least in every preset, 7.4:1
430
- * under More contrast (measured 0.52) — no halo, nothing to stand out from; `none`
431
- * against the inherited chart stroke. */
432
- const BAND_INK = { fill: "var(--text-primary)", stroke: "none" };
433
-
434
- /** What recharts hands a `content` element for each node, plus the three options the
435
- * chart forwards. Everything optional because recharts clones the element it was
436
- * given and fills the geometry in — the element as written carries only the options. */
437
- export interface TreemapCellProps {
438
- depth?: number;
439
- x?: number;
440
- y?: number;
441
- width?: number;
442
- height?: number;
443
- name?: string;
444
- fill?: string;
445
- /** See {@link TreemapNode.labelColor}; recharts hands each node's fields to its cell. */
446
- labelColor?: string;
447
- id?: string;
448
- /** Called with `groups` as its third argument when the cell has any; see
449
- * {@link TreemapProps.onNodeClick}. */
450
- onNodeClick?: (id: string, name: string, groups?: readonly string[]) => void;
451
- /** A group's cell's click; see {@link TreemapProps.onGroupClick}. */
452
- onGroupClick?: (id: string, name: string, groups?: readonly string[]) => void;
453
- /** The ids of the groups round the node, outermost first. `Treemap` puts it on every
454
- * node it hands recharts; a cell drawn in a caller's own map has it if the data does. */
455
- groups?: readonly string[];
456
- /** The node's place in the tree, which `Treemap` puts on every node it hands recharts:
457
- * its key in the map's keyboard, where an id may repeat between a group and a tile. */
458
- path?: string;
459
- /** Filled in by recharts for a GROUP: the nodes laid out inside it. A cell with
460
- * children draws a frame, not a tile (0.52). */
461
- children?: readonly object[] | null;
462
- /** See {@link TreemapProps.redactNames}: `"tiles"` tags a cell with no `children`, and
463
- * leaves a group's frame and band untagged (0.53). */
464
- redactNames?: boolean | "tiles";
465
- nodeNote?: (id: string) => string | undefined;
466
- /** The page's reading direction, which `Treemap` reads off its own root. In `rtl`
467
- * the label stands in the tile's top RIGHT corner and is shaped right-to-left, so a
468
- * clipped name ends in its ellipsis on the reading end. Default `ltr`. */
469
- dir?: Direction;
470
- /** See {@link TreemapProps.edges}. Default `auto`. Read by a cell drawn on its own; a
471
- * `Treemap` hands its tiles its own `edges`. */
472
- edges?: TreemapEdges;
473
- }
474
-
475
- /**
476
- * One tile. Exported so a caller drawing its own recharts `<Treemap>` gets the same
477
- * label rules.
478
- *
479
- * recharts' default cell inherits the chart-level `stroke`, an SVG presentation
480
- * attribute that lands on the `<svg>` root and cascades into the label — so the glyphs
481
- * were painted with a 1px outline ON TOP of themselves, which reads as blur. This cell
482
- * draws its own label with a halo in the TILE colour painted BEHIND the fill
483
- * (`paint-order: stroke`), which defeats any inherited stroke and keeps the text solid
484
- * where it overlaps a lighter or darker part of its own tile.
485
- *
486
- * Inside a `Treemap` it is one stop of the map's keyboard, named with its value and
487
- * share (0.52); drawn on its own it is a stop only as a button (`onNodeClick`), named by
488
- * `name`.
489
- *
490
- * A node with `children` is a GROUP (0.52): the cell draws a frame in the well's colour
491
- * (`--bg-surface-2`), with the group's name in a band along its top where the group has
492
- * the room (61px at Normal, treemap-layout's `BODY_SHARE`). Inside a `Treemap` its tiles are laid out
493
- * in the frame's body; in a caller's own nested recharts `<Treemap>` they are drawn where
494
- * recharts lays them, over the frame — only `Treemap` makes the room.
495
- */
496
- export function TreemapCell(props: TreemapCellProps) {
497
- const map = useContext(TreemapTiles);
498
- return map ? <MapTile {...props} map={map} /> : <StandaloneTile {...props} />;
499
- }
500
-
501
- /** A cell inside a `Treemap`: drawn where the map's layout puts its node, read by path
502
- * for the chart's size as recharts measured it. */
503
- function MapTile(props: TreemapCellProps & { map: TileContext }) {
504
- const width = useChartWidth();
505
- const height = useChartHeight();
506
- const { scale } = useTextSize();
507
- const place =
508
- props.path !== undefined && width && height ? props.map.layout?.(width, height, scale).get(props.path) : undefined;
509
- return <Tile {...props} place={place} />;
510
- }
511
-
512
- /** A cell with no `Treemap` above it: it resolves what the map would have handed it. */
513
- function StandaloneTile(props: TreemapCellProps) {
514
- const tokens = useChartTokenHex().ramp;
515
- const edgeFor = useChartEdge();
516
- const edges = useEdgesInForce(props.edges ?? "auto");
517
- const map = useMemo(() => ({ tokens, edgeFor, edges }), [tokens, edgeFor, edges]);
518
- return <Tile {...props} map={map} />;
519
- }
520
-
521
- function Tile({
522
- depth,
523
- x = 0,
524
- y = 0,
525
- width = 0,
526
- height = 0,
527
- name,
528
- fill,
529
- labelColor,
530
- id,
531
- onNodeClick,
532
- onGroupClick,
533
- groups,
534
- path,
535
- children,
536
- redactNames,
537
- nodeNote,
538
- dir = "ltr",
539
- map,
540
- place,
541
- }: TreemapCellProps & { map: TileContext; place?: Placed }) {
542
- // Before the early return: a hook has to run on every render of the cell.
543
- const { scale } = useTextSize();
544
- const { tokens: tokenHex, edgeFor, edges, keys } = map;
545
- // Depth 0 is recharts' synthetic root, which spans the whole chart.
546
- if (!depth) return null;
547
- // Inside a Treemap, where the map's layout draws the node; on its own, where recharts
548
- // laid it out.
549
- const box = place?.box ?? { x, y, width, height };
550
- // Whole pixels: a rect on a half pixel renders its edge across two half-lit
551
- // columns, and the text baseline inherits that blur.
552
- const px = Math.round(box.x);
553
- const py = Math.round(box.y);
554
- const pw = Math.round(box.width);
555
- const ph = Math.round(box.height);
556
- const isGroup = children != null && children.length > 0;
557
- const group = !isGroup
558
- ? undefined
559
- : place?.body
560
- ? { banded: place.banded ?? false, body: place.body }
561
- : groupBody({ x: px, y: py, width: pw, height: ph }, scale);
562
- const tileFill = fill ?? "var(--chart-1)";
563
- const measured = measurableFill(tileFill, tokenHex);
564
- const ink = labelColor ?? (measured ? textOn(measured) : "currentColor");
565
- // `contrast`: a tile in a hue under 3:1 against the card gets a 1px ink edge inside
566
- // the gutter (WCAG 1.4.11, anyplot's rule); a hue that clears it stays bare.
567
- // `uniform`: no per-tile edge; every tile is parted by the same gutter.
568
- // A group's frame is the well, 1.09–1.24:1 from the card in every preset and contrast
569
- // (measured 0.52): under `contrast` it always takes the edge.
570
- const edge =
571
- edges !== "contrast" ? undefined : group ? CHART_ROLES.outline : measured ? edgeFor(measured) : undefined;
572
- // The halo only on an OPAQUE fill we could measure. A translucent fill (a
573
- // `color-mix(… transparent)`) composited twice — once as the tile, once as the halo
574
- // over it — draws a darker ring round every glyph, which read as doubled text on the
575
- // faded tiles. `none` still overrides the inherited chart `stroke`.
576
- const halo = measured ? tileFill : "none";
577
- // Below ~44x20 there is no room for a readable word, so no label at all rather than
578
- // a one-letter stub bleeding over the tile edge. A group's name only in its band.
579
- const labelSize = LABEL_FONT_SIZE * scale;
580
- const noteSize = NOTE_FONT_SIZE * scale;
581
- const roomForName = group ? group.banded : pw >= 44 * scale && ph >= 20 * scale;
582
- const label = roomForName ? fitLabel(name ?? "", pw, labelSize) : null;
583
- // Only under a label that is itself drawn — a stray "+12%" on an unnamed rectangle
584
- // names nothing — and only where a second baseline fits: the note's baseline sits
585
- // 29px down and an 11px glyph hangs ~2px below it, so 36 leaves the same ~5px under
586
- // the note that the label keeps under itself at the 20px floor. Never in a band.
587
- const rawNote = !group && id != null && nodeNote ? nodeNote(id) : undefined;
588
- const note = label != null && rawNote != null && ph >= 36 * scale ? fitLabel(rawNote, pw, noteSize) : null;
589
- // The label's START edge, 6px in from the tile's. The tiles themselves are laid out
590
- // physically (the plot is not mirrored, see `ChartContainer`) and the chart's SVG is
591
- // pinned `ltr`, so the direction is set on each <text> explicitly: `start` is then
592
- // the right end in RTL, and the label grows leftwards from the tile's right edge.
593
- const textX = dir === "rtl" ? px + pw - 6 : px + 6;
594
- // A tile's click is `onNodeClick`, a frame's `onGroupClick`. The groups go along only
595
- // when there are any, so a flat map calls back exactly as it did before 0.52.
596
- const click = group ? onGroupClick : onNodeClick;
597
- const activate =
598
- id != null && click
599
- ? () => (groups && groups.length > 0 ? click(id, name ?? "", groups) : click(id, name ?? ""))
600
- : undefined;
601
- // Inside a Treemap every tile and frame is a stop of the map's one roving tab stop,
602
- // named with its value and share; a standalone cell is a stop of its own only when it
603
- // is a button.
604
- const roving = path != null && keys ? keys : undefined;
605
- const focusable = roving !== undefined || activate !== undefined;
606
- // `"tiles"` (0.53, keksdose after 0.52) leaves a group's frame and band untagged: its
607
- // groups are public chain names, and its blur on `[data-private]` blurred them with the
608
- // payees. A truthy test on the union would have tagged them still.
609
- const redact = redactNames === true || (redactNames === "tiles" && !group);
610
- const onKeyDown = focusable
611
- ? (e: KeyboardEvent<SVGGElement>) => {
612
- if (roving?.move(path!, e)) {
613
- e.preventDefault();
614
- return;
615
- }
616
- if (!activate || (e.key !== "Enter" && e.key !== " ")) return;
617
- e.preventDefault();
618
- activate();
619
- }
620
- : undefined;
621
- return (
622
- <g
623
- ref={roving ? refFor(roving.register, path!) : undefined}
624
- onClick={activate}
625
- onKeyDown={onKeyDown}
626
- onFocus={roving ? () => roving.onFocus(path!) : undefined}
627
- onMouseEnter={map.hover ? () => map.hover!({ x: px, y: py, width: pw, height: ph }) : undefined}
628
- // A clickable tile is a button to a keyboard and a screen reader too; the
629
- // drilldown it opens was mouse-only in the app this came from. Under `redactNames`
630
- // the group carries `data-private` whenever it carries a name, button or not: the
631
- // name says the node's name and value, and an untagged `aria-label` is read by a
632
- // session replay or a redaction keyed on `[data-private]` (0.52, code review of
633
- // feat/0.52.0 — DSP-5 named every tile, and this tagged only the buttons).
634
- role={activate ? "button" : roving ? "img" : undefined}
635
- // In a map, -1 as drawn: the map moves its one stop on the elements in place.
636
- tabIndex={roving ? -1 : activate ? 0 : undefined}
637
- aria-label={focusable ? (roving?.name(path!) ?? name) : undefined}
638
- data-private={focusable && redact ? "" : undefined}
639
- data-group={group ? "" : undefined}
640
- cursor={activate ? "pointer" : undefined}
641
- // CSS beats a presentation attribute, so this recolours the rect's own stroke
642
- // for as long as the tile has keyboard focus.
643
- className={focusable ? "outline-none [&:focus-visible>rect]:stroke-[var(--brand)]" : undefined}
644
- >
645
- {/* The 2px gutter: the page's ground (`uniform`, the same dark line round every
646
- tile in the dark theme) or the card showing through (`contrast`). Never a white
647
- grid: on a dark surface white lines glare harder than the tiles. A group's frame
648
- is the well, the inset surface: the page's own colour in light, a step up from
649
- it in dark, and never one of the hues its tiles are told apart by. */}
650
- <rect
651
- x={px}
652
- y={py}
653
- width={pw}
654
- height={ph}
655
- fill={group ? "var(--bg-surface-2)" : tileFill}
656
- stroke={edges === "contrast" ? "var(--bg-surface)" : "var(--bg-page)"}
657
- strokeWidth={2}
658
- rx={3}
659
- />
660
- {edge && pw > 3 && ph > 3 && (
661
- <rect
662
- data-edge=""
663
- x={px + 1.5}
664
- y={py + 1.5}
665
- width={pw - 3}
666
- height={ph - 3}
667
- fill="none"
668
- stroke={edge}
669
- strokeWidth={1}
670
- rx={2}
671
- style={{ pointerEvents: "none" }}
672
- />
673
- )}
674
- {label != null && (
675
- <text
676
- // A plain attribute, so the host's `[data-private]` rule reaches an SVG
677
- // <text> exactly as it reaches a table cell.
678
- data-private={redact ? "" : undefined}
679
- x={textX}
680
- // A band's name centred in its 20px; a tile's where its floor leaves room below.
681
- y={py + (group ? 14 : 15) * scale}
682
- direction={dir}
683
- {...(group ? BAND_INK : { fill: ink, ...haloOf(halo) })}
684
- fontSize={labelSize}
685
- fontWeight={600}
686
- style={{ pointerEvents: "none" }}
687
- >
688
- {label}
689
- </text>
690
- )}
691
- {note != null && (
692
- <text
693
- x={textX}
694
- y={py + 29 * scale}
695
- direction={dir}
696
- fill={ink}
697
- fillOpacity={0.85}
698
- {...haloOf(halo)}
699
- fontSize={noteSize}
700
- fontWeight={500}
701
- style={{ pointerEvents: "none" }}
702
- >
703
- {note}
704
- </text>
705
- )}
706
- </g>
707
- );
708
- }
709
-
710
- // ── Chart ─────────────────────────────────────────────────────────────────
711
-
712
- // Nodes carry their own `fill`, and `ChartTooltipContent` falls back to it for the
713
- // swatch, so no series config is needed.
714
- const TREEMAP_CONFIG: ChartConfig = {};
715
-
716
- /**
717
- * The shared tooltip with the whole bubble tagged `data-private`, for names that are
718
- * the user's own data.
719
- *
720
- * The bubble, not just the name: `ChartTooltipContent` prints the name either from
721
- * `item.name` or from a config `label`, and only the config route could be tagged — but
722
- * a config entry keyed by a payee name is not a CSS identifier, and `ChartStyle` would
723
- * (rightly) refuse it. The bubble holds the name and the value, and the value cell is
724
- * already `data-private`, so tagging the wrapper hides nothing extra.
725
- *
726
- * recharts CLONES the `content` element to inject `active`/`payload`, so this has to BE
727
- * that element — a `<div>` wrapped around `<ChartTooltipContent />` in the JSX would
728
- * take those props itself and hand the tooltip nothing.
729
- *
730
- * `groups: false` is `redactNames="tiles"` (0.53): a group's bubble is left untagged, as its
731
- * frame is. The payload says which node is hovered — recharts hands the node itself, with
732
- * its laid-out `children` for a group and `null` for a tile, as the cell is told — so
733
- * anything else, or no payload, is tagged.
734
- */
735
- function RedactedTooltipContent({
736
- groups = true,
737
- ...props
738
- }: ComponentProps<typeof ChartTooltipContent> & { groups?: boolean }) {
739
- const children = props.payload?.[0]?.payload?.children;
740
- const group = Array.isArray(children) && children.length > 0;
741
- return (
742
- <div data-private={groups || !group ? true : undefined}>
743
- <ChartTooltipContent {...props} />
744
- </div>
745
- );
746
- }
747
-
748
- /** Where the pointer last entered a node, as drawn: a store of one box, so that only the
749
- * tooltip re-renders when it moves — not the map, nor (through recharts) every tile. */
750
- interface Anchor {
751
- read: () => Box | null;
752
- subscribe: (onChange: () => void) => () => void;
753
- set: (box: Box) => void;
754
- }
755
-
756
- function createAnchor(): Anchor {
757
- let at: Box | null = null;
758
- const listeners = new Set<() => void>();
759
- return {
760
- read: () => at,
761
- subscribe: (onChange) => {
762
- listeners.add(onChange);
763
- return () => listeners.delete(onChange);
764
- },
765
- set: (box) => {
766
- if (at && at.x === box.x && at.y === box.y && at.width === box.width && at.height === box.height) return;
767
- at = box;
768
- listeners.forEach((onChange) => onChange());
769
- },
770
- };
771
- }
772
-
773
- /**
774
- * The map's tooltip, stood at the tile the pointer is on AS DRAWN. recharts anchors its
775
- * tooltip at the centre of the rectangle it laid the node out in, and since 0.52 the map
776
- * draws its nodes where its own layout puts them (see "Groups") — a tooltip anchored by
777
- * recharts could stand over a neighbour. It stands 10px off the tile's centre, towards the
778
- * chart's middle, so it does not run off the side the tile is on. `content` is the bubble,
779
- * as recharts' own `content`.
780
- */
781
- function TreemapTooltip({ content, anchor }: { content: ReactElement; anchor: Anchor }) {
782
- const at = useSyncExternalStore(anchor.subscribe, anchor.read, anchor.read);
783
- const width = useChartWidth() ?? 0;
784
- const height = useChartHeight() ?? 0;
785
- if (!at) return <ChartTooltip content={content} />;
786
- const cx = at.x + at.width / 2;
787
- const cy = at.y + at.height / 2;
788
- const left = cx > width / 2;
789
- const up = cy > height / 2;
790
- return (
791
- <ChartTooltip
792
- content={content}
793
- position={{ x: cx + (left ? -10 : 10), y: cy + (up ? -10 : 10) }}
794
- // `translate` composes with the `transform` recharts sets for `position`.
795
- wrapperStyle={{ translate: `${left ? "-100%" : "0"} ${up ? "-100%" : "0"}` }}
796
- />
797
- );
798
- }
799
-
800
- /** What `Treemap` hands recharts per node: a tile, or a group with `children`. recharts
801
- * hands each node's fields to its cell; a field left out of this never reaches
802
- * `TreemapCell` (0.8.0 shipped `labelColor` without it — keksdose). A type, not an
803
- * interface: recharts' `data` wants an index signature, which only an alias meets. */
804
- type MapNode = {
805
- id: string;
806
- name: string;
807
- /** A tile's value; a group's, its drawn tiles' together. recharts sums a group again
808
- * for its layout, and its tooltip reads this field. */
809
- size: number;
810
- fill: string;
811
- labelColor?: string;
812
- /** The ids of the groups round the node, outermost first. */
813
- groups: readonly string[];
814
- /** The node's key in the map's keyboard and names: its id and its groups' (see TileKeys). */
815
- path: string;
816
- children?: MapNode[];
817
- };
818
-
819
- /** What a group hands down to the tiles inside it that set none of their own. */
820
- interface Paint {
821
- fill: string;
822
- labelColor?: string;
823
- shade?: number;
824
- }
825
-
826
- /**
827
- * `data` as drawn: tiles with a positive value, at most `maxTiles` of them in the order
828
- * given (depth first), and the groups that still hold any. `shaded` turns a fill and a
829
- * `shade` into the fill drawn.
830
- *
831
- * Coloured by index into `data` AS GIVEN, before anything is dropped: a caller painting a
832
- * legend or a second map from the same list must land on the same hue for the same row,
833
- * and filtering first would shift every colour after a gap. Below the top a node takes
834
- * its group's colour, so a group's tiles share one hue unless they set their own.
835
- */
836
- function mapNodes(
837
- data: readonly TreemapNode[],
838
- ramp: readonly string[],
839
- maxTiles: number | null | undefined,
840
- shaded: (fill: string, shade: number | undefined) => string,
841
- ): MapNode[] {
842
- // Whole tiles, as `slice(0, maxTiles ?? undefined)` counted them up to 0.51: 2.5 draws 2,
843
- // NaN none and null every one (0.52 code review of treemap.tsx, finding 4 — a bare
844
- // counter drew 3, and every tile; deep review, charts, finding 2 — Math.floor(null) is 0,
845
- // and a JS caller's or a `number | null` setting's null drew an empty card).
846
- let left = maxTiles == null ? Infinity : Number.isNaN(maxTiles) ? 0 : Math.floor(maxTiles);
847
- const walk = (list: readonly TreemapNode[], groups: readonly string[], paint: Paint | undefined): MapNode[] =>
848
- list.flatMap((d, idx): MapNode[] => {
849
- const fill = d.fill ?? paint?.fill ?? ramp[idx % ramp.length]!;
850
- // An inherited fill brings its ink along (a `color-mix()` group needs its
851
- // `labelColor`); a fill of the tile's own is measured again.
852
- const labelColor = d.labelColor ?? (d.fill === undefined ? paint?.labelColor : undefined);
853
- const shade = d.shade ?? paint?.shade;
854
- if (d.children && d.children.length > 0) {
855
- const children = walk(d.children, [...groups, d.id], { fill, labelColor, shade });
856
- if (children.length === 0) return [];
857
- const size = children.reduce((acc, c) => acc + c.size, 0);
858
- return [{ id: d.id, name: d.name, size, fill, groups, path: pathOf(groups, d.id), children }];
859
- }
860
- if (!(Number.isFinite(d.value) && d.value > 0) || left <= 0) return [];
861
- left -= 1;
862
- const drawn = shaded(fill, shade);
863
- // An ink the GROUP chose was chosen for the group's colour: once `shade` makes the
864
- // tile another hex, that hex is measured instead (0.52 code review of treemap.tsx,
865
- // finding 5 — white ink on a tile shaded almost to the card, ~1.3:1). The tile's
866
- // own `labelColor` still wins: the caller set it for this tile.
867
- const ink = d.labelColor ?? (drawn === fill ? labelColor : undefined);
868
- return [
869
- { id: d.id, name: d.name, size: d.value, fill: drawn, labelColor: ink, groups, path: pathOf(groups, d.id) },
870
- ];
871
- });
872
- return walk(data, [], undefined);
873
- }
874
-
875
- /** The last layout made for each drawn tree, by size and text size: a memo, not state — it
876
- * is a pure function of its key, so whichever cell asks first, every cell gets the same. */
877
- const LAYOUTS = new WeakMap<readonly MapNode[], { key: string; placed: ReadonlyMap<string, Placed> }>();
878
-
879
- function layoutFor(nodes: readonly MapNode[], width: number, height: number, scale: number) {
880
- const key = `${width}x${height}@${scale}`;
881
- const last = LAYOUTS.get(nodes);
882
- if (last?.key === key) return last.placed;
883
- const placed = layoutTreemap(nodes, width, height, scale);
884
- LAYOUTS.set(nodes, { key, placed });
885
- return placed;
886
- }
887
-
888
- /** One ref callback per map and path, the same on every render: a new function each time
889
- * makes React detach and attach the element on every render of the tile. A path no longer
890
- * drawn is let go as the map lays out its new data (see the map's keyboard). */
891
- const REFS = new WeakMap<TileKeys["register"], Map<string, (element: SVGGElement | null) => void>>();
892
-
893
- function refFor(register: TileKeys["register"], path: string) {
894
- let byPath = REFS.get(register);
895
- if (!byPath) {
896
- byPath = new Map();
897
- REFS.set(register, byPath);
898
- }
899
- let ref = byPath.get(path);
900
- if (!ref) {
901
- ref = (element) => register(path, element);
902
- byPath.set(path, ref);
903
- }
904
- return ref;
905
- }
906
-
907
- /** A node's path: its groups' ids and its own, as JSON so that no id can fake a separator. */
908
- const pathOf = (groups: readonly string[], id: string) => JSON.stringify([...groups, id]);
909
-
910
- /** `nodes` in the order the keyboard walks them: each group, then what is inside it. */
911
- function depthFirst(nodes: readonly MapNode[]): MapNode[] {
912
- return nodes.flatMap((n) => [n, ...(n.children ? depthFirst(n.children) : [])]);
913
- }
914
-
915
- /**
916
- * Tile chart: one rectangle per {@link TreemapNode}, sized by `value`, coloured by
917
- * index from the chart ramp (or by the node's own `fill`), labelled in whichever of the
918
- * kit's two inks measures better against it. Hover shows the name and the formatted
919
- * value; every tile is a keyboard stop named with its value and share (see
920
- * `onNodeClick`), and `onNodeClick` turns the tiles into buttons.
921
- *
922
- * A node with `children` is a group (0.52): a labelled frame with its tiles inside, in
923
- * its colour unless they set their own — see {@link TreemapNode.children}.
924
- *
925
- * Renders nothing when no node has a positive value — an empty box is a hole in the
926
- * card, and the caller's own sentence is the better answer there.
927
- *
928
- * Needs `recharts`, like everything behind `@eifi1/ui-kit/chart`.
929
- */
930
- export function Treemap({
931
- data,
932
- valueFormatter,
933
- height = "20rem",
934
- onNodeClick,
935
- onGroupClick,
936
- redactNames = false,
937
- shareInValue = false,
938
- nodeNote,
939
- maxTiles,
940
- colors,
941
- edges = "auto",
942
- description,
943
- labels: labelsProp,
944
- style,
945
- ...rest
946
- }: TreemapProps) {
947
- // Resolved once for every tile, which reads them from `TreemapTiles` (DSP-22).
948
- const { ramp: tokenHex, card } = useChartTokenHex();
949
- const edgeFor = useChartEdge();
950
- const edgesInForce = useEdgesInForce(edges);
951
- const labels = useKitLabels("treemap", DEFAULT_TREEMAP_LABELS, labelsProp);
952
- // The provider's locale (0.52, refactor audit 2026-10-30, DOC-10): this was the one
953
- // formatter default in the kit that took the runtime's.
954
- const locale = useKitLocale();
955
- const numberFormat = useMemo(() => new Intl.NumberFormat(locale), [locale]);
956
- // One decimal under 10 %, the rule PieChart says its shares by too (lib/share-format):
957
- // a tile of 0.4 % named "0%" says it is not there.
958
- const shareText = useMemo(() => shareFormat(locale), [locale]);
959
- // Read off the DOM after mount: the `dir` a treemap sits under is an ancestor's
960
- // attribute, and SVG text anchoring cannot follow it by CSS (see TreemapCell). Again
961
- // on new data, because a map that had nothing to draw rendered no root to read.
962
- const rootRef = useRef<HTMLDivElement>(null);
963
- const [dir, setDir] = useState<Direction>("ltr");
964
- useLayoutEffect(() => {
965
- setDir(dirOf(rootRef.current));
966
- }, [data]);
967
- const ramp = colors && colors.length > 0 ? colors : tokenHex;
968
- // Memoised: moving the tab stop re-renders the map, and fresh nodes would re-lay it.
969
- const nodes = useMemo(
970
- () =>
971
- mapNodes(data, ramp, maxTiles, (fill, shade) => {
972
- // `shade` (0.52): the hue mixed into the card as a hex, so it is measured like
973
- // any other. A fill or a card it cannot read keeps the full hue.
974
- if (shade === undefined || !Number.isFinite(shade) || shade >= 1 || !card) return fill;
975
- const hex = measurableFill(fill, tokenHex);
976
- return hex ? lerpHex(card, hex, shade) : fill;
977
- }),
978
- [data, ramp, maxTiles, tokenHex, card],
979
- );
980
- // The keyboard walks groups and tiles alike, in the data's order (see `onNodeClick`).
981
- const order = useMemo(() => depthFirst(nodes), [nodes]);
982
- // The map's layout (see "Groups"), for the size recharts measured: each cell asks for it
983
- // by that size, and `layoutFor` makes it once per size and text size.
984
- const layout = useMemo(
985
- () => (width: number, height: number, scale: number) => layoutFor(nodes, width, height, scale),
986
- [nodes],
987
- );
988
- // Where the pointer is, for the tooltip to stand at (`TreemapTooltip`).
989
- const [anchor] = useState(createAnchor);
990
-
991
- // The keyboard (DSP-5): one tab stop for the map, moved between tiles in `order` as
992
- // PieChart's is between slices. The tiles are drawn by recharts, so they register
993
- // their element here rather than being looked up.
994
- //
995
- // The stop is moved on the elements IN PLACE, as PieChart moves its own, and never
996
- // reaches the tiles' context: kept there, every arrow-key step was a new context and
997
- // every tile drew again — layout, fitted labels, notes (0.52 code review of treemap.tsx,
998
- // finding 6). And recharts keys a node by where IT lays it out (`x-y-name`), so new data
999
- // or a resize remounts the focused node: its element leaves holding the focus, which
1000
- // fell to <body> (finding 7). The path it held is kept, and its new element takes the
1001
- // focus back as it mounts — or, if the node is gone, the stop does.
1002
- const tiles = useRef(new Map<string, SVGGElement>());
1003
- const orderRef = useRef(order);
1004
- const stopRef = useRef<string | undefined>(undefined);
1005
- const restoreRef = useRef<string | null>(null);
1006
- /** The path holding the stop: the last one focused while it is drawn, else the first. */
1007
- const stopOf = useCallback(() => {
1008
- const drawn = orderRef.current;
1009
- return drawn.some((n) => n.path === stopRef.current) ? stopRef.current : drawn[0]?.path;
1010
- }, []);
1011
- const applyStop = useCallback(() => {
1012
- const at = stopOf();
1013
- tiles.current.forEach((el, path) => el.setAttribute("tabindex", path === at ? "0" : "-1"));
1014
- return at;
1015
- }, [stopOf]);
1016
- const register = useCallback(
1017
- (path: string, element: SVGGElement | null) => {
1018
- if (element) {
1019
- tiles.current.set(path, element);
1020
- element.setAttribute("tabindex", path === stopOf() ? "0" : "-1");
1021
- if (restoreRef.current === path) {
1022
- restoreRef.current = null;
1023
- element.focus();
1024
- }
1025
- return;
1026
- }
1027
- // Detached before it is removed: still the focused element if it held the focus.
1028
- // Kept for this commit only — the node's new element mounts in it, or the effect below
1029
- // hands the focus on. A node that left with no new data (`<Activity mode="hidden">`, a
1030
- // panel 0 wide) took the focus back whenever it was shown again, from wherever the
1031
- // reader was by then (0.52 code review, deep, charts, finding 3).
1032
- if (tiles.current.get(path) === document.activeElement) {
1033
- restoreRef.current = path;
1034
- queueMicrotask(() => {
1035
- if (restoreRef.current === path) restoreRef.current = null;
1036
- });
1037
- }
1038
- tiles.current.delete(path);
1039
- },
1040
- [stopOf],
1041
- );
1042
- const onFocus = useCallback(
1043
- (path: string) => {
1044
- stopRef.current = path;
1045
- applyStop();
1046
- },
1047
- [applyStop],
1048
- );
1049
- // New data: the order the stop is read against, the stop set again on what is drawn now,
1050
- // the focus of a node that is gone handed to the stop — and the ref callbacks of the paths
1051
- // no longer drawn let go: a map that switched data (a month picker over payees) kept one
1052
- // for every node it had ever drawn (0.52 code review, deep, charts, finding 11).
1053
- useLayoutEffect(() => {
1054
- orderRef.current = order;
1055
- const refs = REFS.get(register);
1056
- if (refs) {
1057
- const drawn = new Set(order.map((n) => n.path));
1058
- for (const path of refs.keys()) if (!drawn.has(path)) refs.delete(path);
1059
- }
1060
- const at = applyStop();
1061
- if (restoreRef.current === null) return;
1062
- restoreRef.current = null;
1063
- if (at) tiles.current.get(at)?.focus();
1064
- }, [order, applyStop, register]);
1065
- const move = useCallback((path: string, event: KeyboardEvent<SVGGElement>) => {
1066
- const drawn = orderRef.current;
1067
- const at = drawn.findIndex((n) => n.path === path);
1068
- // The ends hold: a map is a plane, not PieChart's ring. → is the next tile in any page:
1069
- // the tiles are laid out left to right whatever the reading direction (the plot is not
1070
- // mirrored, see `ChartContainer`), and → read off the page walked an RTL page's map
1071
- // backwards, against the picture (0.52 code review, deep, charts, finding 1).
1072
- const to = at < 0 ? undefined : rovingTarget(event.key, "ltr", at, drawn.length, false);
1073
- if (to === undefined) return false;
1074
- const target = drawn[to];
1075
- if (target) tiles.current.get(target.path)?.focus();
1076
- return true;
1077
- }, []);
1078
-
1079
- // Everything below is memoised over what it reads (0.52, code review of feat/0.52.0). A
1080
- // tile re-renders when its context or its recharts element changes, and recharts' map is
1081
- // a PureComponent that redraws every node when `content` or its tooltip child is a new
1082
- // element: built afresh per render, as they were, a page re-rendering the card or one
1083
- // arrow-key step redrew every tile and ran `labels.tile` and two `Intl` formats per node
1084
- // again — the cost DSP-22 had just taken out of the tiles.
1085
- //
1086
- // The share is of what is DRAWN, so it always reads as "of this picture" — a group's
1087
- // and a tile's inside it alike.
1088
- const total = useMemo(() => nodes.reduce((acc, n) => acc + n.size, 0), [nodes]);
1089
- const format = useCallback(
1090
- (v: number): ReactNode =>
1091
- valueFormatter ? valueFormatter(v, total > 0 ? v / total : undefined) : formatWithMinus(numberFormat, v),
1092
- [valueFormatter, total, numberFormat],
1093
- );
1094
- const names = useMemo(() => {
1095
- // The value as the tooltip says it: the caller's formatter, handed the node's share as
1096
- // it is there, when it gives text; a value it formats as markup is said as a number.
1097
- //
1098
- // It was asked with the share `undefined` — which, with anything drawn, it had never
1099
- // been before 0.52 — so a formatter leaning on the share printed "NaN%" into every name
1100
- // or threw and took the chart down (0.52 code review of treemap.tsx, finding 2). A guard
1101
- // caught both; every named node has its share, so it is handed over instead (deep
1102
- // review, charts, finding 13).
1103
- const valueText = (v: number) => {
1104
- const out = format(v);
1105
- return typeof out === "string" || typeof out === "number" ? String(out) : formatWithMinus(numberFormat, v);
1106
- };
1107
- // A group's name by its path; a node's groups by theirs, its own path's prefixes.
1108
- const groupName = new Map(order.filter((n) => n.children).map((n) => [n.path, n.name]));
1109
- // `""` when the formatter's text says the share already, which every template drops
1110
- // (0.53, `shareInValue`).
1111
- const shareOf = (v: number) => (shareInValue ? "" : shareText(total > 0 ? v / total : 0));
1112
- return new Map(
1113
- order.map((n) => {
1114
- const say = n.children ? labels.group : labels.tile;
1115
- const own = say(n.name, valueText(n.size), shareOf(n.size));
1116
- // The innermost group first: "Rent: …, in Housing, in Fixed costs".
1117
- const named = n.groups
1118
- .map((g, i) => groupName.get(pathOf(n.groups.slice(0, i), g)) ?? g)
1119
- .reduceRight((acc, group) => labels.inGroup(acc, group), own);
1120
- return [n.path, named] as const;
1121
- }),
1122
- );
1123
- }, [order, labels, format, numberFormat, shareText, shareInValue, total]);
1124
- const keys = useMemo<TileKeys>(
1125
- () => ({ name: (path) => names.get(path), onFocus, register, move }),
1126
- [names, onFocus, register, move],
1127
- );
1128
- const context = useMemo<TileContext>(
1129
- () => ({ tokens: tokenHex, edgeFor, edges: edgesInForce, keys, layout, hover: anchor.set }),
1130
- [tokenHex, edgeFor, edgesInForce, keys, layout, anchor],
1131
- );
1132
- const cell = useMemo(
1133
- () => (
1134
- <TreemapCell
1135
- onNodeClick={onNodeClick}
1136
- onGroupClick={onGroupClick}
1137
- redactNames={redactNames}
1138
- nodeNote={nodeNote}
1139
- dir={dir}
1140
- />
1141
- ),
1142
- [onNodeClick, onGroupClick, redactNames, nodeNote, dir],
1143
- );
1144
- const tooltip = useMemo(
1145
- () => (
1146
- <TreemapTooltip
1147
- content={
1148
- redactNames ? (
1149
- <RedactedTooltipContent hideLabel valueFormatter={format} groups={redactNames === true} />
1150
- ) : (
1151
- <ChartTooltipContent hideLabel valueFormatter={format} />
1152
- )
1153
- }
1154
- anchor={anchor}
1155
- />
1156
- ),
1157
- [redactNames, format, anchor],
1158
- );
1159
-
1160
- if (nodes.length === 0) return null;
1161
- const box = chartHeightProps(height);
1162
- return (
1163
- // Round the container, not inside it: ResponsiveContainer sizes its ONE child, the chart.
1164
- <TreemapTiles.Provider value={context}>
1165
- <ChartContainer
1166
- role="figure"
1167
- {...rest}
1168
- // Read through `aria-describedby`, after a caller's own ids; `hidden`, inside the
1169
- // figure (0.52 code review, deep, charts, finding 7).
1170
- description={description}
1171
- ref={rootRef}
1172
- config={TREEMAP_CONFIG}
1173
- className={cn(box.className, rest.className)}
1174
- style={{ ...style, ...box.style }}
1175
- >
1176
- {/* No chart-level `stroke`: recharts spreads it onto the <svg> root, from where
1177
- it inherits into every label (see TreemapCell). Each cell strokes its own rect. */}
1178
- <RechartsTreemap data={nodes} dataKey="size" fill="var(--chart-1)" isAnimationActive={false} content={cell}>
1179
- {tooltip}
1180
- </RechartsTreemap>
1181
- </ChartContainer>
1182
- </TreemapTiles.Provider>
1183
- );
1184
- }