@eifi1/ui-kit 0.51.4 → 0.51.6

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 (759) hide show
  1. package/README.md +5 -5
  2. package/dist/account/account-settings-labels.js.map +1 -1
  3. package/dist/account/account-settings.js +11 -9
  4. package/dist/account/account-settings.js.map +1 -1
  5. package/dist/account/delete-account-setting.js +56 -65
  6. package/dist/account/delete-account-setting.js.map +1 -1
  7. package/dist/account/email-change-setting-labels.js.map +1 -1
  8. package/dist/account/email-change-setting.js.map +1 -1
  9. package/dist/account/passkeys-setting.js.map +1 -1
  10. package/dist/account/sessions-setting.js.map +1 -1
  11. package/dist/admin/admin-action-confirm.js +1 -3
  12. package/dist/admin/admin-action-confirm.js.map +1 -1
  13. package/dist/admin/admin-action-log.js.map +1 -1
  14. package/dist/admin/admin-area-labels.js +4 -1
  15. package/dist/admin/admin-area-labels.js.map +1 -1
  16. package/dist/admin/admin-overview.js +19 -11
  17. package/dist/admin/admin-overview.js.map +1 -1
  18. package/dist/admin/admin-sections.js.map +1 -1
  19. package/dist/admin/admin-tool-links.d.ts +1 -1
  20. package/dist/admin/admin-tool-links.js +7 -1
  21. package/dist/admin/admin-tool-links.js.map +1 -1
  22. package/dist/admin/invitations-panel.js +2 -13
  23. package/dist/admin/invitations-panel.js.map +1 -1
  24. package/dist/admin/mail-test-card.js.map +1 -1
  25. package/dist/admin/reviewer-scope-editor.js +4 -11
  26. package/dist/admin/reviewer-scope-editor.js.map +1 -1
  27. package/dist/admin/role-select.js.map +1 -1
  28. package/dist/admin/user-roster.js +1 -9
  29. package/dist/admin/user-roster.js.map +1 -1
  30. package/dist/auth/accept-invitation.js.map +1 -1
  31. package/dist/auth/auth-errors.js.map +1 -1
  32. package/dist/auth/forgot-password-form.js +23 -31
  33. package/dist/auth/forgot-password-form.js.map +1 -1
  34. package/dist/auth/register-form.js.map +1 -1
  35. package/dist/auth/reset-password-form.js.map +1 -1
  36. package/dist/auth/sign-in-form.js +13 -5
  37. package/dist/auth/sign-in-form.js.map +1 -1
  38. package/dist/auth/verify-email.js +1 -8
  39. package/dist/auth/verify-email.js.map +1 -1
  40. package/dist/billing/billing-banner.js.map +1 -1
  41. package/dist/billing/billing-labels.js.map +1 -1
  42. package/dist/billing/checkout-processing.js.map +1 -1
  43. package/dist/billing/plan-card.js +91 -85
  44. package/dist/billing/plan-card.js.map +1 -1
  45. package/dist/billing/plan-limit-toast.js.map +1 -1
  46. package/dist/billing/plan-limit.js.map +1 -1
  47. package/dist/billing/plan-price.js.map +1 -1
  48. package/dist/billing/plan-switch.js.map +1 -1
  49. package/dist/billing/subscription-actions.js.map +1 -1
  50. package/dist/bin/eifi1-pay-page.mjs +6 -1
  51. package/dist/components/account-chips.js.map +1 -1
  52. package/dist/components/alert-banner.js +1 -5
  53. package/dist/components/alert-banner.js.map +1 -1
  54. package/dist/components/amount-input.js +29 -12
  55. package/dist/components/amount-input.js.map +1 -1
  56. package/dist/components/authed-image.js.map +1 -1
  57. package/dist/components/autocomplete.js.map +1 -1
  58. package/dist/components/bulk-action-bar.js +1 -4
  59. package/dist/components/bulk-action-bar.js.map +1 -1
  60. package/dist/components/button-group.js.map +1 -1
  61. package/dist/components/button-look.js.map +1 -1
  62. package/dist/components/button.js +1 -7
  63. package/dist/components/button.js.map +1 -1
  64. package/dist/components/calculator.js +5 -5
  65. package/dist/components/calculator.js.map +1 -1
  66. package/dist/components/calendar-heatmap.js +4 -10
  67. package/dist/components/calendar-heatmap.js.map +1 -1
  68. package/dist/components/card.js +1 -8
  69. package/dist/components/card.js.map +1 -1
  70. package/dist/components/chart-zoom-core.js +3 -17
  71. package/dist/components/chart-zoom-core.js.map +1 -1
  72. package/dist/components/chart.d.ts +1 -1
  73. package/dist/components/chart.js +4 -22
  74. package/dist/components/chart.js.map +1 -1
  75. package/dist/components/checkbox-group.js +1 -4
  76. package/dist/components/checkbox-group.js.map +1 -1
  77. package/dist/components/chip-input.js +4 -1
  78. package/dist/components/chip-input.js.map +1 -1
  79. package/dist/components/chip.js +10 -5
  80. package/dist/components/chip.js.map +1 -1
  81. package/dist/components/choice-card.js +58 -74
  82. package/dist/components/choice-card.js.map +1 -1
  83. package/dist/components/column-mapper-labels.js.map +1 -1
  84. package/dist/components/column-mapper.js +167 -227
  85. package/dist/components/column-mapper.js.map +1 -1
  86. package/dist/components/combobox-core.js +1 -4
  87. package/dist/components/combobox-core.js.map +1 -1
  88. package/dist/components/combobox.js +1 -4
  89. package/dist/components/combobox.js.map +1 -1
  90. package/dist/components/confirm-dialog.js.map +1 -1
  91. package/dist/components/copy-button.js.map +1 -1
  92. package/dist/components/country-select.d.ts +5 -4
  93. package/dist/components/country-select.js +3 -19
  94. package/dist/components/country-select.js.map +1 -1
  95. package/dist/components/currency-option-list.js +10 -8
  96. package/dist/components/currency-option-list.js.map +1 -1
  97. package/dist/components/currency-select.js +1 -6
  98. package/dist/components/currency-select.js.map +1 -1
  99. package/dist/components/danger-confirm.js.map +1 -1
  100. package/dist/components/data-table-cells.js.map +1 -1
  101. package/dist/components/data-table-column-settings.js.map +1 -1
  102. package/dist/components/data-table-filter-popover.js +2 -18
  103. package/dist/components/data-table-filter-popover.js.map +1 -1
  104. package/dist/components/data-table-filters.js.map +1 -1
  105. package/dist/components/data-table-fit.js.map +1 -1
  106. package/dist/components/data-table-groups.js +3 -1
  107. package/dist/components/data-table-groups.js.map +1 -1
  108. package/dist/components/data-table-head.js +44 -56
  109. package/dist/components/data-table-head.js.map +1 -1
  110. package/dist/components/data-table-mobile.js +31 -44
  111. package/dist/components/data-table-mobile.js.map +1 -1
  112. package/dist/components/data-table-pagination.js.map +1 -1
  113. package/dist/components/data-table-row-actions.js +19 -16
  114. package/dist/components/data-table-row-actions.js.map +1 -1
  115. package/dist/components/data-table-row.js.map +1 -1
  116. package/dist/components/data-table-shared.js.map +1 -1
  117. package/dist/components/data-table-sort.js.map +1 -1
  118. package/dist/components/data-table-virtual.js +4 -1
  119. package/dist/components/data-table-virtual.js.map +1 -1
  120. package/dist/components/data-table.js +30 -39
  121. package/dist/components/data-table.js.map +1 -1
  122. package/dist/components/date-picker.js +1 -6
  123. package/dist/components/date-picker.js.map +1 -1
  124. package/dist/components/date-range-picker.js.map +1 -1
  125. package/dist/components/description-list.js +10 -10
  126. package/dist/components/description-list.js.map +1 -1
  127. package/dist/components/dialog-frame.js +9 -2
  128. package/dist/components/dialog-frame.js.map +1 -1
  129. package/dist/components/disclosure.js +17 -27
  130. package/dist/components/disclosure.js.map +1 -1
  131. package/dist/components/dropdown.js +25 -35
  132. package/dist/components/dropdown.js.map +1 -1
  133. package/dist/components/empty-state.js.map +1 -1
  134. package/dist/components/entity-combobox.js.map +1 -1
  135. package/dist/components/error-boundary.js.map +1 -1
  136. package/dist/components/field-base.js +5 -5
  137. package/dist/components/field-base.js.map +1 -1
  138. package/dist/components/field-parts.d.ts +1 -1
  139. package/dist/components/field-parts.js +10 -14
  140. package/dist/components/field-parts.js.map +1 -1
  141. package/dist/components/field-sync.js +8 -10
  142. package/dist/components/field-sync.js.map +1 -1
  143. package/dist/components/file-button.js.map +1 -1
  144. package/dist/components/file-dropzone.js.map +1 -1
  145. package/dist/components/floating-panel.js +4 -1
  146. package/dist/components/floating-panel.js.map +1 -1
  147. package/dist/components/focus-ring.js.map +1 -1
  148. package/dist/components/form-actions.js.map +1 -1
  149. package/dist/components/grouped-picker.js +6 -8
  150. package/dist/components/grouped-picker.js.map +1 -1
  151. package/dist/components/hover-menu.js +1 -4
  152. package/dist/components/hover-menu.js.map +1 -1
  153. package/dist/components/iban-input-labels.js.map +1 -1
  154. package/dist/components/icon-button.js +4 -1
  155. package/dist/components/icon-button.js.map +1 -1
  156. package/dist/components/icon-picker.js +2 -7
  157. package/dist/components/icon-picker.js.map +1 -1
  158. package/dist/components/image-grid.js +41 -50
  159. package/dist/components/image-grid.js.map +1 -1
  160. package/dist/components/inline-edit-field.js.map +1 -1
  161. package/dist/components/inline-entity-combobox.js +2 -8
  162. package/dist/components/inline-entity-combobox.js.map +1 -1
  163. package/dist/components/input.js +14 -2
  164. package/dist/components/input.js.map +1 -1
  165. package/dist/components/jump-calendar.js +1 -7
  166. package/dist/components/jump-calendar.js.map +1 -1
  167. package/dist/components/legal-labels.js.map +1 -1
  168. package/dist/components/legal-page.js +14 -2
  169. package/dist/components/legal-page.js.map +1 -1
  170. package/dist/components/legal.js +12 -2
  171. package/dist/components/legal.js.map +1 -1
  172. package/dist/components/lightbox.js.map +1 -1
  173. package/dist/components/line-items.js +26 -36
  174. package/dist/components/line-items.js.map +1 -1
  175. package/dist/components/list.js +22 -10
  176. package/dist/components/list.js.map +1 -1
  177. package/dist/components/measured-grid.js +29 -53
  178. package/dist/components/measured-grid.js.map +1 -1
  179. package/dist/components/menu-item.js.map +1 -1
  180. package/dist/components/mini-calendar.js +13 -31
  181. package/dist/components/mini-calendar.js.map +1 -1
  182. package/dist/components/money-field.js.map +1 -1
  183. package/dist/components/month-picker.js +10 -21
  184. package/dist/components/month-picker.js.map +1 -1
  185. package/dist/components/multi-entity-combobox.js +3 -16
  186. package/dist/components/multi-entity-combobox.js.map +1 -1
  187. package/dist/components/multi-select.js.map +1 -1
  188. package/dist/components/nav-pills.js +10 -1
  189. package/dist/components/nav-pills.js.map +1 -1
  190. package/dist/components/number-field.js.map +1 -1
  191. package/dist/components/number-input.js +1 -7
  192. package/dist/components/number-input.js.map +1 -1
  193. package/dist/components/numpad-sheet.js +1 -4
  194. package/dist/components/numpad-sheet.js.map +1 -1
  195. package/dist/components/one-time-code-input.js +1 -7
  196. package/dist/components/one-time-code-input.js.map +1 -1
  197. package/dist/components/page-contents.js +13 -22
  198. package/dist/components/page-contents.js.map +1 -1
  199. package/dist/components/page-header.js +1 -11
  200. package/dist/components/page-header.js.map +1 -1
  201. package/dist/components/password-strength-labels.js.map +1 -1
  202. package/dist/components/password-strength.js +2 -8
  203. package/dist/components/password-strength.js.map +1 -1
  204. package/dist/components/phone-input.js +42 -57
  205. package/dist/components/phone-input.js.map +1 -1
  206. package/dist/components/picker-sheet.js.map +1 -1
  207. package/dist/components/pie-chart.js +1 -8
  208. package/dist/components/pie-chart.js.map +1 -1
  209. package/dist/components/popover.js.map +1 -1
  210. package/dist/components/progress-bar.js +16 -9
  211. package/dist/components/progress-bar.js.map +1 -1
  212. package/dist/components/qr-code.js +1 -8
  213. package/dist/components/qr-code.js.map +1 -1
  214. package/dist/components/row-actions.js +5 -1
  215. package/dist/components/row-actions.js.map +1 -1
  216. package/dist/components/search-field.js.map +1 -1
  217. package/dist/components/select.js +28 -38
  218. package/dist/components/select.js.map +1 -1
  219. package/dist/components/separator.js +1 -5
  220. package/dist/components/separator.js.map +1 -1
  221. package/dist/components/series-chart-axes.js +4 -1
  222. package/dist/components/series-chart-axes.js.map +1 -1
  223. package/dist/components/series-chart-budget.js.map +1 -1
  224. package/dist/components/series-chart-ticks.js.map +1 -1
  225. package/dist/components/series-chart.js +29 -45
  226. package/dist/components/series-chart.js.map +1 -1
  227. package/dist/components/settings-fields.js +21 -17
  228. package/dist/components/settings-fields.js.map +1 -1
  229. package/dist/components/share-card.js +4 -9
  230. package/dist/components/share-card.js.map +1 -1
  231. package/dist/components/sign-chip.js.map +1 -1
  232. package/dist/components/signature-pad.js +4 -13
  233. package/dist/components/signature-pad.js.map +1 -1
  234. package/dist/components/signed-amount.js +12 -5
  235. package/dist/components/signed-amount.js.map +1 -1
  236. package/dist/components/skeleton.js +2 -17
  237. package/dist/components/skeleton.js.map +1 -1
  238. package/dist/components/slider.js +13 -36
  239. package/dist/components/slider.js.map +1 -1
  240. package/dist/components/sparkline.js.map +1 -1
  241. package/dist/components/stat-tile.js +14 -10
  242. package/dist/components/stat-tile.js.map +1 -1
  243. package/dist/components/swatch-picker.js +14 -4
  244. package/dist/components/swatch-picker.js.map +1 -1
  245. package/dist/components/swipeable-row.d.ts +1 -1
  246. package/dist/components/swipeable-row.js +1 -8
  247. package/dist/components/swipeable-row.js.map +1 -1
  248. package/dist/components/table-skeleton.js +4 -1
  249. package/dist/components/table-skeleton.js.map +1 -1
  250. package/dist/components/table.js +12 -9
  251. package/dist/components/table.js.map +1 -1
  252. package/dist/components/tabs.js +5 -24
  253. package/dist/components/tabs.js.map +1 -1
  254. package/dist/components/text-link.js.map +1 -1
  255. package/dist/components/text.js.map +1 -1
  256. package/dist/components/tile-radio.js +1 -7
  257. package/dist/components/tile-radio.js.map +1 -1
  258. package/dist/components/time-input.js.map +1 -1
  259. package/dist/components/toast.js.map +1 -1
  260. package/dist/components/toggle-group.js +6 -1
  261. package/dist/components/toggle-group.js.map +1 -1
  262. package/dist/components/toggle-legend.js +2 -11
  263. package/dist/components/toggle-legend.js.map +1 -1
  264. package/dist/components/tooltip.js +2 -22
  265. package/dist/components/tooltip.js.map +1 -1
  266. package/dist/components/translation-review-editor.js +1 -4
  267. package/dist/components/translation-review-editor.js.map +1 -1
  268. package/dist/components/translation-review-parts.d.ts +1 -1
  269. package/dist/components/translation-review-parts.js +7 -1
  270. package/dist/components/translation-review-parts.js.map +1 -1
  271. package/dist/components/translation-review.js +18 -7
  272. package/dist/components/translation-review.js.map +1 -1
  273. package/dist/components/tree-view.js +14 -10
  274. package/dist/components/tree-view.js.map +1 -1
  275. package/dist/components/treemap.d.ts +24 -2
  276. package/dist/components/treemap.js +28 -4
  277. package/dist/components/treemap.js.map +1 -1
  278. package/dist/components/trigger-aria.js.map +1 -1
  279. package/dist/components/use-table-state.js +14 -11
  280. package/dist/components/use-table-state.js.map +1 -1
  281. package/dist/components/user-avatar.js +3 -26
  282. package/dist/components/user-avatar.js.map +1 -1
  283. package/dist/demo/demo-start.js +8 -4
  284. package/dist/demo/demo-start.js.map +1 -1
  285. package/dist/eslint/index.js +1 -1
  286. package/dist/feedback/feedback-attachment-labels.js.map +1 -1
  287. package/dist/feedback/feedback-attachment.js +1 -8
  288. package/dist/feedback/feedback-attachment.js.map +1 -1
  289. package/dist/feedback/feedback-capture.d.ts +1 -1
  290. package/dist/feedback/feedback-capture.js +3 -1
  291. package/dist/feedback/feedback-capture.js.map +1 -1
  292. package/dist/feedback/feedback-comment-thread.js +27 -13
  293. package/dist/feedback/feedback-comment-thread.js.map +1 -1
  294. package/dist/feedback/feedback-context.js +1 -4
  295. package/dist/feedback/feedback-context.js.map +1 -1
  296. package/dist/feedback/feedback-dialog.js.map +1 -1
  297. package/dist/feedback/feedback-inbox.js.map +1 -1
  298. package/dist/feedback/feedback-public-list.js +7 -1
  299. package/dist/feedback/feedback-public-list.js.map +1 -1
  300. package/dist/feedback/feedback-record.js +1 -5
  301. package/dist/feedback/feedback-record.js.map +1 -1
  302. package/dist/feedback/feedback-replies.js +25 -10
  303. package/dist/feedback/feedback-replies.js.map +1 -1
  304. package/dist/feedback/feedback-row-detail.js.map +1 -1
  305. package/dist/feedback/feedback-status-undo.js.map +1 -1
  306. package/dist/feedback/feedback-submit-dialog.js.map +1 -1
  307. package/dist/feedback/feedback-table.js +4 -1
  308. package/dist/feedback/feedback-table.js.map +1 -1
  309. package/dist/feedback/feedback-thread.js +2 -13
  310. package/dist/feedback/feedback-thread.js.map +1 -1
  311. package/dist/feedback/feedback-vocabulary.js.map +1 -1
  312. package/dist/feedback/feedback-vote-button.js.map +1 -1
  313. package/dist/hooks/use-anchored-rect.js.map +1 -1
  314. package/dist/hooks/use-document-title.js.map +1 -1
  315. package/dist/hooks/use-file-drop.js.map +1 -1
  316. package/dist/hooks/use-file-picker-labels.js.map +1 -1
  317. package/dist/hooks/use-file-picker.js.map +1 -1
  318. package/dist/hooks/use-focus-trap.js.map +1 -1
  319. package/dist/hooks/use-overlay-history.js +10 -5
  320. package/dist/hooks/use-overlay-history.js.map +1 -1
  321. package/dist/hooks/use-row-swipe.d.ts +1 -1
  322. package/dist/hooks/use-row-swipe.js +1 -6
  323. package/dist/hooks/use-row-swipe.js.map +1 -1
  324. package/dist/i18n/defaults.js +1 -4
  325. package/dist/i18n/defaults.js.map +1 -1
  326. package/dist/i18n/german.js +4 -1
  327. package/dist/i18n/german.js.map +1 -1
  328. package/dist/i18n/kit-labels.js +2 -8
  329. package/dist/i18n/kit-labels.js.map +1 -1
  330. package/dist/i18n/languages.js.map +1 -1
  331. package/dist/i18n/locales/en.js +4 -1
  332. package/dist/i18n/locales/en.js.map +2 -2
  333. package/dist/i18n/locales/es.js +4 -1
  334. package/dist/i18n/locales/es.js.map +1 -1
  335. package/dist/i18n/locales/fr.js +4 -1
  336. package/dist/i18n/locales/fr.js.map +1 -1
  337. package/dist/i18n/locales/hu.js +4 -1
  338. package/dist/i18n/locales/hu.js.map +1 -1
  339. package/dist/i18n/locales/it.js +4 -1
  340. package/dist/i18n/locales/it.js.map +1 -1
  341. package/dist/i18n/locales/zh.js.map +1 -1
  342. package/dist/i18n/plurals.js.map +1 -1
  343. package/dist/i18n/review.js +13 -7
  344. package/dist/i18n/review.js.map +1 -1
  345. package/dist/i18n/shared-labels.js.map +1 -1
  346. package/dist/index.d.ts +16 -16
  347. package/dist/index.js +40 -23
  348. package/dist/index.js.map +1 -1
  349. package/dist/landing/access.js +1 -4
  350. package/dist/landing/access.js.map +1 -1
  351. package/dist/landing/landing-sections.js +35 -38
  352. package/dist/landing/landing-sections.js.map +1 -1
  353. package/dist/landing/page-seo.js.map +1 -1
  354. package/dist/lib/calc.js.map +1 -1
  355. package/dist/lib/clipboard-minus.js.map +1 -1
  356. package/dist/lib/dates.js.map +1 -1
  357. package/dist/lib/decimal-marks.js.map +1 -1
  358. package/dist/lib/device-settings.js +2 -1
  359. package/dist/lib/device-settings.js.map +1 -1
  360. package/dist/lib/direction.js.map +1 -1
  361. package/dist/lib/iban.js.map +1 -1
  362. package/dist/lib/logger.js +1 -5
  363. package/dist/lib/logger.js.map +1 -1
  364. package/dist/lib/person-name.js.map +1 -1
  365. package/dist/lib/phone.js.map +1 -1
  366. package/dist/lib/qr-encode.js +344 -8
  367. package/dist/lib/qr-encode.js.map +1 -1
  368. package/dist/lib/session-refresh.js.map +1 -1
  369. package/dist/lib/table-text.js +15 -3
  370. package/dist/lib/table-text.js.map +1 -1
  371. package/dist/lib/text-match.d.ts +4 -0
  372. package/dist/lib/text-match.js.map +1 -1
  373. package/dist/lib/translation-review.js.map +1 -1
  374. package/dist/notifications/notification-inbox.js +63 -44
  375. package/dist/notifications/notification-inbox.js.map +1 -1
  376. package/dist/notifications/notification-parts.js.map +1 -1
  377. package/dist/notifications/notification-types-setting.js.map +1 -1
  378. package/dist/notifications/notifications-labels.js.map +1 -1
  379. package/dist/notifications/push-device-setting.js +6 -1
  380. package/dist/notifications/push-device-setting.js.map +1 -1
  381. package/dist/notifications/push-devices-setting.js.map +1 -1
  382. package/dist/notifications/push-worker.js.map +1 -1
  383. package/dist/notifications/quiet-hours-setting.js.map +1 -1
  384. package/dist/notifications/use-push-device.js +4 -1
  385. package/dist/notifications/use-push-device.js.map +1 -1
  386. package/dist/offline/pwa-update-prompt.d.ts +1 -1
  387. package/dist/offline/pwa-update-prompt.js +7 -1
  388. package/dist/offline/pwa-update-prompt.js.map +1 -1
  389. package/dist/offline/query-persistence.js.map +1 -1
  390. package/dist/offline.d.ts +1 -1
  391. package/dist/offline.js +6 -1
  392. package/dist/offline.js.map +1 -1
  393. package/dist/pay/pay.css +8 -1
  394. package/dist/rhf/fields.d.ts +1 -2
  395. package/dist/rhf/fields.js +1 -10
  396. package/dist/rhf/fields.js.map +1 -1
  397. package/dist/rhf/form-core.js +1 -4
  398. package/dist/rhf/form-core.js.map +1 -1
  399. package/dist/rhf/line-items.js.map +1 -1
  400. package/dist/rhf/use-rhf-wizard-step.js.map +1 -1
  401. package/dist/search/command-palette.js +71 -62
  402. package/dist/search/command-palette.js.map +1 -1
  403. package/dist/search/global-search.js +34 -5
  404. package/dist/search/global-search.js.map +1 -1
  405. package/dist/search/search-index.js.map +1 -1
  406. package/dist/settings/device-reset-setting.js +1 -6
  407. package/dist/settings/device-reset-setting.js.map +1 -1
  408. package/dist/settings/settings-heading.js.map +1 -1
  409. package/dist/settings/settings-layout.js +9 -20
  410. package/dist/settings/settings-layout.js.map +1 -1
  411. package/dist/settings/use-settings-route.js.map +1 -1
  412. package/dist/shell/app-shell.js +62 -31
  413. package/dist/shell/app-shell.js.map +1 -1
  414. package/dist/shell/auth-layout.js +1 -10
  415. package/dist/shell/auth-layout.js.map +1 -1
  416. package/dist/shell/dev-menu.js +12 -6
  417. package/dist/shell/dev-menu.js.map +1 -1
  418. package/dist/shell/option-switcher-menu.js.map +1 -1
  419. package/dist/shell/sync-status-indicator.js +7 -6
  420. package/dist/shell/sync-status-indicator.js.map +1 -1
  421. package/dist/shell/top-bar-parts-labels.js.map +1 -1
  422. package/dist/shell/topbar-action-menu.js.map +1 -1
  423. package/dist/shell/topbar-controls.js +1 -10
  424. package/dist/shell/topbar-controls.js.map +1 -1
  425. package/dist/shell/tour-menu.js +9 -1
  426. package/dist/shell/tour-menu.js.map +1 -1
  427. package/dist/sync/db.js.map +1 -1
  428. package/dist/sync/engine.js +24 -6
  429. package/dist/sync/engine.js.map +1 -1
  430. package/dist/sync.d.ts +2 -2
  431. package/dist/sync.js +7 -1
  432. package/dist/sync.js.map +1 -1
  433. package/dist/testing/css-variables.js.map +1 -1
  434. package/dist/testing/index.d.ts +1 -1
  435. package/dist/testing/index.js +1 -3
  436. package/dist/testing/index.js.map +1 -1
  437. package/dist/testing/react.js.map +1 -1
  438. package/dist/testing/viewport.js +7 -4
  439. package/dist/testing/viewport.js.map +1 -1
  440. package/dist/theme/contrast-tokens.js +13 -2
  441. package/dist/theme/contrast-tokens.js.map +1 -1
  442. package/dist/theme/contrast.d.ts +2 -2
  443. package/dist/theme/palette-derive.d.ts +1 -1
  444. package/dist/theme/palette-derive.js +18 -29
  445. package/dist/theme/palette-derive.js.map +1 -1
  446. package/dist/theme/palette-presets.js.map +1 -1
  447. package/dist/theme/palette-store.js.map +1 -1
  448. package/dist/theme/text-size.js.map +1 -1
  449. package/dist/theme/theme-store.js +1 -3
  450. package/dist/theme/theme-store.js.map +1 -1
  451. package/dist/tour/tour-catalogue.js.map +1 -1
  452. package/dist/tour/tour-dock.js +5 -1
  453. package/dist/tour/tour-dock.js.map +1 -1
  454. package/dist/tour/tour-engine.js +1 -4
  455. package/dist/tour/tour-engine.js.map +1 -1
  456. package/dist/tour/tour.js +21 -12
  457. package/dist/tour/tour.js.map +1 -1
  458. package/dist/tour/use-tour-progress.js.map +1 -1
  459. package/dist/tour/use-tour-url-sync.js.map +1 -1
  460. package/dist/wizard/stepper-nav.js +3 -24
  461. package/dist/wizard/stepper-nav.js.map +1 -1
  462. package/dist/wizard/types.js.map +1 -1
  463. package/dist/wizard/use-wizard.js +24 -28
  464. package/dist/wizard/use-wizard.js.map +1 -1
  465. package/dist/wizard/validation.js.map +1 -1
  466. package/dist/wizard/wizard-context.js.map +1 -1
  467. package/dist/wizard/wizard-step.d.ts +1 -1
  468. package/dist/wizard/wizard-step.js +1 -8
  469. package/dist/wizard/wizard-step.js.map +1 -1
  470. package/dist/wizard/wizard-summary.js.map +1 -1
  471. package/package.json +5 -2
  472. package/src/account/account-settings-labels.ts +1 -2
  473. package/src/account/account-settings.tsx +11 -17
  474. package/src/account/delete-account-setting.tsx +1 -6
  475. package/src/account/email-change-setting-labels.ts +1 -2
  476. package/src/account/email-change-setting.tsx +3 -1
  477. package/src/account/passkeys-setting.tsx +1 -3
  478. package/src/account/sessions-setting.tsx +4 -2
  479. package/src/admin/admin-action-confirm.tsx +4 -7
  480. package/src/admin/admin-action-log.tsx +29 -29
  481. package/src/admin/admin-area-labels.ts +4 -1
  482. package/src/admin/admin-overview.tsx +8 -2
  483. package/src/admin/admin-sections.tsx +3 -1
  484. package/src/admin/admin-tool-links.tsx +7 -1
  485. package/src/admin/invitations-panel.tsx +5 -11
  486. package/src/admin/mail-test-card.tsx +1 -2
  487. package/src/admin/reviewer-scope-editor.tsx +4 -9
  488. package/src/admin/role-select.tsx +4 -2
  489. package/src/admin/user-roster.tsx +2 -11
  490. package/src/auth/accept-invitation.tsx +1 -7
  491. package/src/auth/auth-errors.ts +1 -6
  492. package/src/auth/forgot-password-form.tsx +2 -7
  493. package/src/auth/register-form.tsx +7 -8
  494. package/src/auth/reset-password-form.tsx +2 -5
  495. package/src/auth/sign-in-form.tsx +15 -14
  496. package/src/auth/verify-email.tsx +2 -7
  497. package/src/billing/billing-banner.tsx +3 -7
  498. package/src/billing/billing-labels.ts +6 -4
  499. package/src/billing/checkout-processing.ts +0 -1
  500. package/src/billing/plan-card.tsx +9 -8
  501. package/src/billing/plan-limit-toast.tsx +1 -2
  502. package/src/billing/plan-limit.ts +1 -2
  503. package/src/billing/plan-price.ts +1 -4
  504. package/src/billing/plan-switch.tsx +1 -2
  505. package/src/billing/subscription-actions.tsx +5 -2
  506. package/src/components/account-chips.tsx +1 -7
  507. package/src/components/alert-banner.tsx +2 -8
  508. package/src/components/amount-input.tsx +206 -178
  509. package/src/components/authed-image.tsx +7 -4
  510. package/src/components/autocomplete.tsx +7 -9
  511. package/src/components/bulk-action-bar.tsx +2 -7
  512. package/src/components/button-group.tsx +1 -2
  513. package/src/components/button-look.ts +6 -17
  514. package/src/components/button.tsx +1 -4
  515. package/src/components/calculator.tsx +6 -9
  516. package/src/components/calendar-heatmap.tsx +5 -10
  517. package/src/components/card.tsx +9 -13
  518. package/src/components/chart-zoom-core.tsx +6 -33
  519. package/src/components/chart.tsx +8 -31
  520. package/src/components/checkbox-group.tsx +1 -4
  521. package/src/components/chip-input.tsx +6 -6
  522. package/src/components/chip.tsx +14 -26
  523. package/src/components/choice-card.tsx +4 -18
  524. package/src/components/column-mapper-labels.ts +3 -10
  525. package/src/components/column-mapper.tsx +33 -122
  526. package/src/components/combobox-core.tsx +12 -42
  527. package/src/components/combobox.tsx +2 -6
  528. package/src/components/confirm-dialog.tsx +1 -3
  529. package/src/components/copy-button.tsx +2 -4
  530. package/src/components/country-select.tsx +9 -21
  531. package/src/components/currency-option-list.tsx +13 -14
  532. package/src/components/currency-select.tsx +2 -7
  533. package/src/components/danger-confirm.tsx +8 -9
  534. package/src/components/data-table-cells.tsx +1 -2
  535. package/src/components/data-table-column-settings.tsx +1 -3
  536. package/src/components/data-table-filter-popover.tsx +5 -19
  537. package/src/components/data-table-filters.ts +1 -6
  538. package/src/components/data-table-fit.ts +1 -3
  539. package/src/components/data-table-groups.tsx +3 -1
  540. package/src/components/data-table-head.tsx +27 -45
  541. package/src/components/data-table-mobile.tsx +99 -108
  542. package/src/components/data-table-pagination.tsx +2 -6
  543. package/src/components/data-table-row-actions.tsx +25 -24
  544. package/src/components/data-table-row.tsx +16 -17
  545. package/src/components/data-table-shared.tsx +10 -16
  546. package/src/components/data-table-sort.ts +1 -2
  547. package/src/components/data-table-virtual.ts +9 -4
  548. package/src/components/data-table.tsx +489 -501
  549. package/src/components/date-picker.tsx +1 -7
  550. package/src/components/date-range-picker.tsx +3 -13
  551. package/src/components/description-list.tsx +7 -13
  552. package/src/components/dialog-frame.tsx +12 -3
  553. package/src/components/disclosure.tsx +8 -12
  554. package/src/components/dropdown.tsx +3 -11
  555. package/src/components/empty-state.tsx +1 -3
  556. package/src/components/entity-combobox.tsx +2 -6
  557. package/src/components/error-boundary.tsx +7 -7
  558. package/src/components/field-base.tsx +8 -14
  559. package/src/components/field-parts.tsx +12 -23
  560. package/src/components/field-sync.tsx +7 -11
  561. package/src/components/file-button.tsx +7 -3
  562. package/src/components/file-dropzone.tsx +7 -12
  563. package/src/components/floating-panel.tsx +44 -51
  564. package/src/components/focus-ring.ts +1 -2
  565. package/src/components/form-actions.tsx +1 -4
  566. package/src/components/grouped-picker.tsx +6 -8
  567. package/src/components/hover-menu.tsx +1 -4
  568. package/src/components/iban-input-labels.ts +1 -2
  569. package/src/components/icon-button.tsx +46 -48
  570. package/src/components/icon-picker.tsx +4 -17
  571. package/src/components/image-grid.tsx +1 -6
  572. package/src/components/inline-edit-field.tsx +1 -5
  573. package/src/components/inline-entity-combobox.tsx +6 -21
  574. package/src/components/input.tsx +14 -2
  575. package/src/components/jump-calendar.tsx +2 -6
  576. package/src/components/legal-labels.ts +1 -2
  577. package/src/components/legal-page.tsx +18 -7
  578. package/src/components/legal.tsx +14 -5
  579. package/src/components/lightbox.tsx +1 -3
  580. package/src/components/line-items.tsx +7 -15
  581. package/src/components/list.tsx +29 -25
  582. package/src/components/measured-grid.tsx +8 -30
  583. package/src/components/menu-item.tsx +1 -3
  584. package/src/components/mini-calendar.tsx +129 -159
  585. package/src/components/money-field.tsx +4 -2
  586. package/src/components/month-picker.tsx +14 -21
  587. package/src/components/multi-entity-combobox.tsx +6 -26
  588. package/src/components/multi-select.tsx +5 -9
  589. package/src/components/nav-pills.tsx +10 -2
  590. package/src/components/number-field.tsx +18 -15
  591. package/src/components/number-input.tsx +3 -19
  592. package/src/components/numpad-sheet.tsx +9 -13
  593. package/src/components/one-time-code-input.tsx +6 -11
  594. package/src/components/page-contents.tsx +2 -9
  595. package/src/components/page-header.tsx +5 -10
  596. package/src/components/password-strength-labels.ts +1 -2
  597. package/src/components/password-strength.tsx +6 -9
  598. package/src/components/phone-input.tsx +2 -13
  599. package/src/components/picker-sheet.tsx +2 -5
  600. package/src/components/pie-chart.tsx +6 -12
  601. package/src/components/popover.tsx +2 -1
  602. package/src/components/progress-bar.tsx +16 -25
  603. package/src/components/qr-code.tsx +2 -9
  604. package/src/components/row-actions.tsx +9 -1
  605. package/src/components/search-field.tsx +4 -5
  606. package/src/components/select.tsx +2 -10
  607. package/src/components/separator.tsx +1 -5
  608. package/src/components/series-chart-axes.ts +5 -5
  609. package/src/components/series-chart-budget.ts +1 -4
  610. package/src/components/series-chart-ticks.ts +1 -4
  611. package/src/components/series-chart.tsx +43 -83
  612. package/src/components/settings-fields.tsx +20 -15
  613. package/src/components/share-card.tsx +6 -14
  614. package/src/components/sign-chip.tsx +16 -16
  615. package/src/components/signature-pad.tsx +21 -13
  616. package/src/components/signed-amount.tsx +6 -14
  617. package/src/components/skeleton.tsx +2 -13
  618. package/src/components/slider.tsx +9 -20
  619. package/src/components/sparkline.tsx +2 -13
  620. package/src/components/stat-tile.tsx +13 -24
  621. package/src/components/swatch-picker.tsx +8 -3
  622. package/src/components/swipeable-row.tsx +2 -11
  623. package/src/components/table-skeleton.tsx +4 -1
  624. package/src/components/table.tsx +12 -8
  625. package/src/components/tabs.tsx +24 -39
  626. package/src/components/text-link.tsx +6 -6
  627. package/src/components/text.tsx +1 -2
  628. package/src/components/tile-radio.tsx +6 -19
  629. package/src/components/time-input.tsx +4 -5
  630. package/src/components/toast.tsx +3 -12
  631. package/src/components/toggle-group.tsx +9 -9
  632. package/src/components/toggle-legend.tsx +4 -16
  633. package/src/components/tooltip.tsx +9 -48
  634. package/src/components/translation-review-editor.tsx +4 -4
  635. package/src/components/translation-review-parts.tsx +7 -1
  636. package/src/components/translation-review.tsx +15 -16
  637. package/src/components/tree-view.tsx +17 -20
  638. package/src/components/treemap.tsx +59 -9
  639. package/src/components/trigger-aria.ts +1 -4
  640. package/src/components/use-table-state.ts +16 -17
  641. package/src/components/user-avatar.tsx +4 -21
  642. package/src/demo/demo-start.tsx +8 -4
  643. package/src/feedback/feedback-attachment-labels.ts +2 -1
  644. package/src/feedback/feedback-attachment.tsx +16 -29
  645. package/src/feedback/feedback-capture.ts +3 -1
  646. package/src/feedback/feedback-comment-thread.tsx +9 -6
  647. package/src/feedback/feedback-context.tsx +1 -4
  648. package/src/feedback/feedback-dialog.tsx +3 -4
  649. package/src/feedback/feedback-inbox.tsx +3 -9
  650. package/src/feedback/feedback-public-list.tsx +7 -1
  651. package/src/feedback/feedback-record.ts +6 -14
  652. package/src/feedback/feedback-replies.tsx +10 -5
  653. package/src/feedback/feedback-row-detail.tsx +2 -5
  654. package/src/feedback/feedback-status-undo.ts +1 -4
  655. package/src/feedback/feedback-submit-dialog.tsx +4 -4
  656. package/src/feedback/feedback-table.tsx +12 -8
  657. package/src/feedback/feedback-thread.tsx +4 -16
  658. package/src/feedback/feedback-vocabulary.ts +72 -82
  659. package/src/feedback/feedback-vote-button.tsx +9 -8
  660. package/src/hooks/use-anchored-rect.ts +1 -4
  661. package/src/hooks/use-document-title.ts +3 -1
  662. package/src/hooks/use-file-drop.ts +1 -3
  663. package/src/hooks/use-file-picker-labels.ts +1 -2
  664. package/src/hooks/use-file-picker.tsx +1 -5
  665. package/src/hooks/use-focus-trap.ts +1 -2
  666. package/src/hooks/use-overlay-history.ts +10 -5
  667. package/src/hooks/use-row-swipe.ts +1 -6
  668. package/src/i18n/defaults.ts +1 -4
  669. package/src/i18n/german.ts +33 -49
  670. package/src/i18n/kit-labels.tsx +5 -20
  671. package/src/i18n/languages.ts +2 -8
  672. package/src/i18n/locales/en.ts +11 -31
  673. package/src/i18n/locales/es.ts +35 -49
  674. package/src/i18n/locales/fr.ts +34 -51
  675. package/src/i18n/locales/hu.ts +26 -37
  676. package/src/i18n/locales/it.ts +35 -52
  677. package/src/i18n/locales/zh.ts +14 -32
  678. package/src/i18n/plurals.ts +4 -1
  679. package/src/i18n/review.ts +18 -7
  680. package/src/i18n/shared-labels.ts +1 -2
  681. package/src/index.ts +106 -32
  682. package/src/landing/access.ts +2 -7
  683. package/src/landing/landing-sections.tsx +14 -15
  684. package/src/landing/page-seo.ts +1 -3
  685. package/src/lib/calc.ts +4 -1
  686. package/src/lib/clipboard-minus.ts +4 -1
  687. package/src/lib/dates.ts +2 -10
  688. package/src/lib/decimal-marks.ts +1 -3
  689. package/src/lib/device-settings.ts +2 -1
  690. package/src/lib/direction.ts +1 -3
  691. package/src/lib/iban.ts +89 -9
  692. package/src/lib/logger.ts +1 -5
  693. package/src/lib/person-name.ts +1 -3
  694. package/src/lib/phone.ts +5 -8
  695. package/src/lib/qr-encode.ts +32 -8
  696. package/src/lib/session-refresh.ts +5 -6
  697. package/src/lib/table-text.ts +24 -6
  698. package/src/lib/text-match.ts +15 -3
  699. package/src/lib/translation-review.ts +1 -3
  700. package/src/notifications/notification-inbox.tsx +17 -4
  701. package/src/notifications/notification-parts.tsx +1 -2
  702. package/src/notifications/notification-types-setting.tsx +19 -18
  703. package/src/notifications/notifications-labels.ts +1 -2
  704. package/src/notifications/push-device-setting.tsx +7 -1
  705. package/src/notifications/push-devices-setting.tsx +7 -3
  706. package/src/notifications/push-worker.ts +10 -2
  707. package/src/notifications/quiet-hours-setting.tsx +4 -1
  708. package/src/notifications/use-push-device.ts +4 -1
  709. package/src/offline/pwa-update-prompt.tsx +7 -1
  710. package/src/offline/query-persistence.ts +9 -6
  711. package/src/offline.ts +6 -1
  712. package/src/rhf/fields.tsx +50 -19
  713. package/src/rhf/form-core.tsx +2 -6
  714. package/src/rhf/line-items.tsx +5 -7
  715. package/src/rhf/use-rhf-wizard-step.ts +3 -12
  716. package/src/search/command-palette.tsx +15 -23
  717. package/src/search/global-search.tsx +38 -8
  718. package/src/search/search-index.ts +5 -1
  719. package/src/settings/device-reset-setting.tsx +5 -8
  720. package/src/settings/settings-heading.tsx +3 -1
  721. package/src/settings/settings-layout.tsx +6 -13
  722. package/src/settings/use-settings-route.ts +4 -3
  723. package/src/shell/app-shell.tsx +48 -24
  724. package/src/shell/auth-layout.tsx +1 -6
  725. package/src/shell/dev-menu.tsx +12 -6
  726. package/src/shell/option-switcher-menu.tsx +1 -3
  727. package/src/shell/sync-status-indicator.tsx +14 -12
  728. package/src/shell/top-bar-parts-labels.ts +1 -3
  729. package/src/shell/topbar-action-menu.tsx +9 -8
  730. package/src/shell/topbar-controls.tsx +5 -19
  731. package/src/shell/tour-menu.tsx +9 -1
  732. package/src/sync/db.ts +5 -1
  733. package/src/sync/engine.ts +44 -12
  734. package/src/sync.ts +16 -2
  735. package/src/testing/css-variables.ts +1 -2
  736. package/src/testing/index.ts +1 -6
  737. package/src/testing/react.ts +1 -2
  738. package/src/testing/viewport.ts +9 -9
  739. package/src/theme/contrast-tokens.ts +13 -2
  740. package/src/theme/palette-derive.ts +24 -50
  741. package/src/theme/palette-presets.ts +0 -1
  742. package/src/theme/palette-store.ts +1 -4
  743. package/src/theme/text-size.ts +1 -1
  744. package/src/theme/theme-store.ts +2 -6
  745. package/src/tour/tour-catalogue.tsx +4 -2
  746. package/src/tour/tour-dock.ts +5 -1
  747. package/src/tour/tour-engine.ts +2 -6
  748. package/src/tour/tour.tsx +28 -12
  749. package/src/tour/use-tour-progress.ts +8 -2
  750. package/src/tour/use-tour-url-sync.ts +3 -1
  751. package/src/vite-env.d.ts +1 -4
  752. package/src/wizard/stepper-nav.tsx +10 -35
  753. package/src/wizard/types.ts +1 -4
  754. package/src/wizard/use-wizard.ts +28 -43
  755. package/src/wizard/validation.ts +1 -5
  756. package/src/wizard/wizard-context.tsx +2 -6
  757. package/src/wizard/wizard-step.tsx +1 -8
  758. package/src/wizard/wizard-summary.tsx +2 -6
  759. package/tokens.css +393 -152
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/tree-view.tsx"],"sourcesContent":["import { useEffect, useId, useMemo, useRef, useState } from \"react\";\nimport type { ComponentPropsWithRef, KeyboardEvent, MouseEvent, ReactNode } from \"react\";\nimport { ChevronRight } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { horizontalStep } from \"../lib/direction\";\nimport { Spinner } from \"./spinner\";\nimport { FOCUS_RING } from \"./focus-ring\";\n\n/**\n * A hierarchy you walk with the keyboard: kastlan's chart of accounts (Assets › Current\n * assets › Bank › 1020 Operating account), and — through {@link TreeRow} alone — the\n * row labels of its gantt, which indent and fold like a tree but sit beside a timeline\n * and so cannot be one.\n *\n * Both apps had written it as nested `<div>`s with a chevron button per row: every\n * row a Tab stop (a 300-account chart was 300 presses to get past), no way to say\n * \"level 3 of 5, collapsed\" to a screen reader, and no ←/→ at all. This is the WAI-ARIA\n * tree pattern instead:\n *\n * - `role=\"tree\"` › `treeitem` (with `aria-level`, `aria-expanded` on a parent,\n * `aria-selected` when selectable) › `role=\"group\"` for its children — so the\n * reader announces the position and the state it needs.\n * - ONE Tab stop (roving tabindex). ↑/↓ walk the visible rows, → opens a closed\n * parent and then steps into it, ← closes an open one and then steps out to the\n * parent, Home/End jump to the ends, `*` opens every sibling, and typing a few\n * letters jumps to the next row that starts with them. ←/→ follow the reading\n * direction (`horizontalStep`): in an RTL tree the children hang off to the left,\n * and ← is what opens them.\n * - Enter / Space, or a click, select. The chevron alone toggles on click.\n *\n * `expanded` and `selected` each work controlled or uncontrolled.\n */\n\n/* ── Nodes ───────────────────────────────────────────────────────────────── */\n\nexport interface TreeNode<T = unknown> {\n /** Unique across the whole tree — it is what `expanded` and `selected` hold. */\n id: string;\n label: ReactNode;\n /** The text type-ahead matches. Defaults to the row's rendered text. */\n textValue?: string;\n /** Before the label: an account-type icon, a colour dot. */\n icon?: ReactNode;\n /** After the label, pushed to the end of the row: a balance, a count, an action. */\n trailing?: ReactNode;\n children?: readonly TreeNode<T>[];\n /**\n * The node has children that are not loaded yet — it gets a chevron, and opening it\n * calls `loadChildren`. Pass the children in `items` once they arrive.\n */\n hasChildren?: boolean;\n /** Focusable and announced as unavailable (`aria-disabled`), but not selectable. */\n disabled?: boolean;\n /** Anything of the host's own, handed back to `renderItem` and `onSelectedChange`. */\n data?: T;\n}\n\n/** What {@link TreeViewProps.renderItem} is told about a row. */\nexport interface TreeItemState {\n level: number;\n expanded: boolean;\n selected: boolean;\n /** It has children, loaded or not — it shows a chevron. */\n expandable: boolean;\n loading: boolean;\n disabled: boolean;\n}\n\n/* ── TreeRow ─────────────────────────────────────────────────────────────── */\n\nexport interface TreeRowProps extends ComponentPropsWithRef<\"div\"> {\n /** 1 for a top-level row. Sets the indent and the number of guides. */\n level: number;\n /** `true`/`false` for a row that can fold; leave it `undefined` for a leaf, which\n * gets a chevron-sized gap instead so labels at one level still line up. */\n expanded?: boolean;\n /**\n * Make the chevron a real button (with `aria-expanded`, named by the label) that\n * calls this. For a row used ON ITS OWN — kastlan's gantt labels, where each row is\n * a line of a timeline and not an item of a tree widget. Inside {@link TreeView} it\n * is omitted: the tree owns the keys, and a button per row would be a Tab stop per\n * row, which is what the tree exists to remove.\n */\n onToggle?: () => void;\n /** The toggle button's name. By default it is named by the row's label (the state\n * is `aria-expanded`'s to say); required when `children` replaces the label. */\n toggleLabel?: string;\n label: ReactNode;\n icon?: ReactNode;\n trailing?: ReactNode;\n /** Pixels per level. Default 16. */\n indent?: number;\n /** Draw a vertical hairline per ancestor level. Default `true`. */\n guides?: boolean;\n selected?: boolean;\n /** A spinner in place of the chevron, while lazy children load. */\n loading?: boolean;\n /** Replace the label + trailing part of the row (the indent and chevron stay). */\n children?: ReactNode;\n}\n\nconst DEFAULT_INDENT = 16;\n/** The chevron's box; the guide for a level runs through its centre. */\nconst CHEVRON = 16;\n/** The row's start padding, before any indent. */\nconst ROW_PAD = 4;\n\n/**\n * One row: indent, guides, chevron, icon, label, trailing — no tree semantics of its\n * own. {@link TreeView} is built from it, and a host that needs the look without the\n * widget (a gantt's label column) uses it directly.\n *\n * Laid out with logical properties throughout (`padding-inline-start`,\n * `inset-inline-start`), so the indent, the guides and the chevron all hang from the\n * reading start in an RTL layout without a second code path.\n */\nexport function TreeRow({\n level,\n expanded,\n onToggle,\n toggleLabel,\n label,\n icon,\n trailing,\n indent = DEFAULT_INDENT,\n guides = true,\n selected,\n loading,\n children,\n className,\n style,\n ...rest\n}: TreeRowProps) {\n const labelId = useId();\n const depth = Math.max(0, level - 1);\n const expandable = expanded !== undefined;\n // A right-pointing chevron that turns DOWN when open. In RTL it is mirrored to point\n // left, and turning that one clockwise would point it UP — so it turns the other way.\n const chevron = (\n <ChevronRight\n aria-hidden\n className={cn(\n \"size-4 transition-transform motion-reduce:transition-none rtl:-scale-x-100\",\n expanded && \"rotate-90 rtl:-rotate-90\",\n )}\n />\n );\n return (\n <div\n {...rest}\n data-tree-row=\"\"\n data-selected={selected || undefined}\n style={{ paddingInlineStart: ROW_PAD + depth * indent, ...style }}\n className={cn(\n \"relative flex min-h-8 items-center gap-1.5 rounded pe-2 text-sm\",\n selected\n ? \"bg-active font-medium text-primary\"\n : \"text-secondary\",\n className,\n )}\n >\n {guides &&\n Array.from({ length: depth }, (_, i) => (\n <span\n key={i}\n aria-hidden\n data-tree-guide=\"\"\n className=\"pointer-events-none absolute inset-y-0 w-px bg-[var(--border)]\"\n style={{ insetInlineStart: ROW_PAD + i * indent + CHEVRON / 2 }}\n />\n ))}\n {loading ? (\n <span className=\"flex size-4 shrink-0 items-center justify-center\">\n <Spinner label={null} className=\"size-3 border\" />\n </span>\n ) : expandable && onToggle ? (\n <button\n type=\"button\"\n aria-expanded={expanded}\n aria-label={toggleLabel}\n aria-labelledby={toggleLabel ? undefined : labelId}\n onClick={(e) => {\n e.stopPropagation();\n onToggle();\n }}\n className={cn(\"flex size-4 shrink-0 items-center justify-center rounded text-muted outline-none hover:text-primary\", FOCUS_RING)}\n >\n {chevron}\n </button>\n ) : expandable ? (\n <span data-tree-toggle=\"\" className=\"flex size-4 shrink-0 cursor-pointer items-center justify-center text-muted\">\n {chevron}\n </span>\n ) : (\n <span aria-hidden className=\"size-4 shrink-0\" />\n )}\n {children ?? (\n <>\n {icon && <span className=\"flex shrink-0 items-center\">{icon}</span>}\n {/* One line at Normal; from Large up it wraps and the `min-h-8` row grows\n (text size §10.17, round 0.34 T1: the sweep read \"1020 Operat…\"). */}\n <span id={labelId} data-tree-label=\"\" className=\"min-w-0 flex-1 truncate-until-large\">\n {label}\n </span>\n {trailing !== undefined && trailing !== null && (\n <span className=\"ms-auto flex shrink-0 items-center gap-1 text-xs tabular-nums text-muted\">\n {trailing}\n </span>\n )}\n </>\n )}\n </div>\n );\n}\n\n/* ── TreeView ────────────────────────────────────────────────────────────── */\n\nexport interface TreeViewProps<T = unknown>\n extends Omit<ComponentPropsWithRef<\"ul\">, \"onSelect\" | \"children\" | \"defaultValue\"> {\n items: readonly TreeNode<T>[];\n /** Open nodes, controlled. */\n expanded?: readonly string[];\n /** Open nodes on first render, uncontrolled. */\n defaultExpanded?: readonly string[];\n onExpandedChange?: (expanded: string[]) => void;\n /** The selected node's id, controlled; `null` for none. */\n selected?: string | null;\n defaultSelected?: string | null;\n onSelectedChange?: (id: string, node: TreeNode<T>) => void;\n /** `\"none\"` makes it a navigation-only tree: no `aria-selected`, Enter toggles. */\n selectionMode?: \"single\" | \"none\";\n /** The whole row content after the chevron, per node. Wins over `label`/`trailing`. */\n renderItem?: (node: TreeNode<T>, state: TreeItemState) => ReactNode;\n /**\n * Called when a node with `hasChildren` and no `children` is opened. While the\n * promise is pending the row shows a spinner and is `aria-busy`; put the children\n * into `items` before it resolves (or after — the row opens either way).\n */\n loadChildren?: (node: TreeNode<T>) => Promise<void> | void;\n /** Pixels per level. Default 16. */\n indent?: number;\n /** Vertical hairlines per level. Default `true`. */\n indentGuides?: boolean;\n /** Extra classes for each row (the `TreeRow`), e.g. a denser height. */\n rowClassName?: string;\n}\n\ninterface FlatNode<T> {\n node: TreeNode<T>;\n level: number;\n parentId: string | null;\n}\n\nconst isExpandable = (n: TreeNode<unknown>) => Boolean(n.children?.length || n.hasChildren);\n\n/** Controlled when `value` is given, otherwise state seeded from `initial`. */\nfunction useControllable<V>(value: V | undefined, initial: V): [V, (next: V) => void] {\n const [own, setOwn] = useState<V>(initial);\n const controlled = value !== undefined;\n const set = (next: V) => {\n if (!controlled) setOwn(next);\n };\n return [controlled ? value : own, set];\n}\n\n/** How long a type-ahead run stays open between keys — the APG's suggested half second. */\nconst TYPEAHEAD_MS = 500;\n\nexport function TreeView<T = unknown>({\n items,\n expanded: expandedProp,\n defaultExpanded,\n onExpandedChange,\n selected: selectedProp,\n defaultSelected,\n onSelectedChange,\n selectionMode = \"single\",\n renderItem,\n loadChildren,\n indent = DEFAULT_INDENT,\n indentGuides = true,\n rowClassName,\n className,\n onKeyDown,\n onClick,\n ...rest\n}: TreeViewProps<T>) {\n const [expandedList, setExpandedOwn] = useControllable<readonly string[]>(\n expandedProp,\n defaultExpanded ?? [],\n );\n const [selected, setSelectedOwn] = useControllable<string | null>(\n selectedProp,\n defaultSelected ?? null,\n );\n const expandedSet = useMemo(() => new Set(expandedList), [expandedList]);\n const [loading, setLoading] = useState<ReadonlySet<string>>(() => new Set());\n const [focusedId, setFocusedId] = useState<string | null>(null);\n const itemRefs = useRef(new Map<string, HTMLLIElement>());\n const pendingFocus = useRef<string | null>(null);\n const typeahead = useRef({ text: \"\", at: 0 });\n const baseId = useId();\n\n // Every node by id (for parents and lookups), and the rows currently on screen in\n // order — the list the arrow keys walk.\n const { byId, visible } = useMemo(() => {\n const map = new Map<string, FlatNode<T>>();\n const rows: FlatNode<T>[] = [];\n const walk = (nodes: readonly TreeNode<T>[], level: number, parentId: string | null, shown: boolean) => {\n for (const node of nodes) {\n const flat = { node, level, parentId };\n map.set(node.id, flat);\n if (shown) rows.push(flat);\n if (node.children) walk(node.children, level + 1, node.id, shown && expandedSet.has(node.id));\n }\n };\n walk(items, 1, null, true);\n return { byId: map, visible: rows };\n }, [items, expandedSet]);\n\n // The one tabbable row. Focus that was inside a branch that has since closed (a\n // click on its parent's chevron) moves up to the nearest ancestor still on screen,\n // so the tab stop never points at a row that is not rendered.\n const visibleIds = useMemo(() => new Set(visible.map((f) => f.node.id)), [visible]);\n const nearestVisible = (id: string | null): string | null => {\n let cur = id;\n while (cur && !visibleIds.has(cur)) cur = byId.get(cur)?.parentId ?? null;\n return cur;\n };\n const tabbable =\n nearestVisible(focusedId) ?? nearestVisible(selected) ?? visible[0]?.node.id ?? null;\n\n useEffect(() => {\n const id = pendingFocus.current;\n if (!id) return;\n pendingFocus.current = null;\n itemRefs.current.get(id)?.focus();\n });\n\n const focusRow = (id: string) => {\n setFocusedId(id);\n // After the commit: moving into a branch that just opened targets a row that is\n // not in the DOM yet.\n pendingFocus.current = id;\n itemRefs.current.get(id)?.focus();\n };\n\n const setExpanded = (next: Set<string>) => {\n const list = [...next];\n setExpandedOwn(list);\n onExpandedChange?.(list);\n };\n\n const open = (node: TreeNode<T>) => {\n if (expandedSet.has(node.id) || !isExpandable(node)) return;\n const next = new Set(expandedSet);\n next.add(node.id);\n setExpanded(next);\n if (loadChildren && node.hasChildren && !node.children?.length) {\n const pending = loadChildren(node);\n if (pending && typeof pending.then === \"function\") {\n setLoading((s) => new Set(s).add(node.id));\n const done = () =>\n setLoading((s) => {\n const n = new Set(s);\n n.delete(node.id);\n return n;\n });\n pending.then(done, done);\n }\n }\n };\n\n const close = (node: TreeNode<T>) => {\n if (!expandedSet.has(node.id)) return;\n const next = new Set(expandedSet);\n next.delete(node.id);\n setExpanded(next);\n };\n\n const toggle = (node: TreeNode<T>) => (expandedSet.has(node.id) ? close(node) : open(node));\n\n const select = (node: TreeNode<T>) => {\n if (node.disabled) return;\n if (selectionMode === \"none\") {\n toggle(node);\n return;\n }\n setSelectedOwn(node.id);\n onSelectedChange?.(node.id, node);\n };\n\n const idOf = (target: EventTarget | null): string | null =>\n (target as Element | null)?.closest?.<HTMLElement>('[role=\"treeitem\"]')?.dataset.treeId ?? null;\n\n const textOf = (flat: FlatNode<T>): string => {\n if (flat.node.textValue !== undefined) return flat.node.textValue;\n if (typeof flat.node.label === \"string\") return flat.node.label;\n const row = itemRefs.current.get(flat.node.id)?.querySelector(\"[data-tree-row]\");\n return row?.textContent ?? \"\";\n };\n\n const findByText = (fromIndex: number, text: string): string | null => {\n const needle = text.toLocaleLowerCase();\n for (let i = 1; i <= visible.length; i++) {\n const flat = visible[(fromIndex + i) % visible.length];\n if (textOf(flat).trim().toLocaleLowerCase().startsWith(needle)) return flat.node.id;\n }\n return null;\n };\n\n const handleKeyDown = (e: KeyboardEvent<HTMLUListElement>) => {\n onKeyDown?.(e);\n if (e.defaultPrevented) return;\n const id = idOf(e.target);\n const flat = id ? byId.get(id) : undefined;\n if (!flat) return;\n const { node } = flat;\n const index = visible.findIndex((f) => f.node.id === node.id);\n const expandedNow = expandedSet.has(node.id);\n // Forward / backward along the reading direction, from the row itself.\n const step = horizontalStep(e.key, e.target as Element);\n // A space inside a type-ahead run is part of the name being typed (\"current a…\"),\n // not a request to select.\n const inRun = Date.now() - typeahead.current.at < TYPEAHEAD_MS && typeahead.current.text !== \"\";\n const key = e.key === \" \" && inRun ? \"typeahead\" : e.key;\n\n let handled = true;\n if (step === 1) {\n if (!isExpandable(node)) handled = false;\n else if (!expandedNow) open(node);\n else if (node.children?.length) focusRow(node.children[0].id);\n } else if (step === -1) {\n if (expandedNow) close(node);\n else if (flat.parentId) focusRow(flat.parentId);\n else handled = false;\n } else {\n switch (key) {\n case \"ArrowDown\":\n if (index < visible.length - 1) focusRow(visible[index + 1].node.id);\n break;\n case \"ArrowUp\":\n if (index > 0) focusRow(visible[index - 1].node.id);\n break;\n case \"Home\":\n if (visible.length) focusRow(visible[0].node.id);\n break;\n case \"End\":\n if (visible.length) focusRow(visible[visible.length - 1].node.id);\n break;\n case \"Enter\":\n case \" \":\n select(node);\n break;\n case \"*\": {\n // Every sibling at this level, as the pattern suggests.\n const siblings = flat.parentId ? (byId.get(flat.parentId)?.node.children ?? []) : items;\n const next = new Set(expandedSet);\n for (const s of siblings) if (isExpandable(s)) next.add(s.id);\n setExpanded(next);\n break;\n }\n default: {\n const printable = e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey;\n if (!printable) {\n handled = false;\n break;\n }\n const now = Date.now();\n const run = now - typeahead.current.at < TYPEAHEAD_MS ? typeahead.current.text : \"\";\n typeahead.current = { text: run + e.key, at: now };\n const typed = typeahead.current.text;\n // \"aaa\" cycles through the rows starting with \"a\", as a file list does;\n // anything else extends the prefix, searched from the row after this one\n // (or from this one, when it still matches the longer prefix).\n const repeated = typed.length > 1 && [...typed].every((c) => c === typed[0]);\n const target = repeated\n ? findByText(index, typed[0])\n : typed.length > 1 && textOf(flat).trim().toLocaleLowerCase().startsWith(typed.toLocaleLowerCase())\n ? node.id\n : findByText(index, typed);\n if (target) focusRow(target);\n }\n }\n }\n if (handled) {\n e.preventDefault();\n e.stopPropagation();\n }\n };\n\n const handleClick = (e: MouseEvent<HTMLUListElement>) => {\n onClick?.(e);\n const id = idOf(e.target);\n const flat = id ? byId.get(id) : undefined;\n if (!flat) return;\n // Controls a host rendered into a row (an action in `trailing`) are their own.\n const control = (e.target as Element).closest(\"button, a[href], input, select, textarea\");\n if (control && itemRefs.current.get(flat.node.id)?.contains(control)) return;\n setFocusedId(flat.node.id);\n if ((e.target as Element).closest(\"[data-tree-toggle]\")) {\n toggle(flat.node);\n return;\n }\n select(flat.node);\n };\n\n const renderNodes = (nodes: readonly TreeNode<T>[], level: number, path: string): ReactNode =>\n nodes.map((node, i) => {\n // Named by its own row, not by its content: a treeitem's content is the whole\n // open branch below it, and \"Assets Current assets 1020 Bank …\" is not a name.\n // A path of indices, because a host's id may hold spaces an idref cannot.\n const rowId = `${baseId}-${path}${i}`;\n const expandable = isExpandable(node);\n const isOpen = expandable && expandedSet.has(node.id);\n const isSelected = selectionMode !== \"none\" && selected === node.id;\n const isLoading = loading.has(node.id);\n const state: TreeItemState = {\n level,\n expanded: isOpen,\n selected: isSelected,\n expandable,\n loading: isLoading,\n disabled: Boolean(node.disabled),\n };\n return (\n <li\n key={node.id}\n ref={(el) => {\n if (el) itemRefs.current.set(node.id, el);\n else itemRefs.current.delete(node.id);\n }}\n role=\"treeitem\"\n data-tree-id={node.id}\n aria-labelledby={rowId}\n aria-level={level}\n aria-expanded={expandable ? isOpen : undefined}\n aria-selected={selectionMode === \"none\" ? undefined : isSelected}\n aria-disabled={node.disabled || undefined}\n aria-busy={isLoading || undefined}\n tabIndex={node.id === tabbable ? 0 : -1}\n onFocus={(e) => {\n // Only the row itself: a focus event bubbling up from a nested row, or from\n // a control inside this one, is not this row being focused.\n if (e.target === e.currentTarget) setFocusedId(node.id);\n }}\n // The ring goes on this row's own box, not the <li>: the <li> contains the\n // whole open branch below it, and ringing that would outline a subtree.\n className=\"outline-none [&:focus-visible>[data-tree-row]]:ring-focus [&:focus-visible>[data-tree-row]]:ring-brand\"\n >\n <TreeRow\n id={rowId}\n level={level}\n expanded={expandable ? isOpen : undefined}\n label={node.label}\n icon={node.icon}\n trailing={node.trailing}\n indent={indent}\n guides={indentGuides}\n selected={isSelected}\n loading={isLoading}\n className={cn(\n node.disabled ? \"cursor-not-allowed opacity-50\" : \"cursor-pointer hover:bg-hover\",\n rowClassName,\n )}\n >\n {renderItem ? renderItem(node, state) : undefined}\n </TreeRow>\n {isOpen && (\n <ul role=\"group\">\n {/* Empty while lazy children load: a \"Loading…\" line in here would be\n a child that is not a treeitem, and the parent row already says\n `aria-busy` and shows the spinner. */}\n {node.children?.length ? renderNodes(node.children, level + 1, `${path}${i}-`) : null}\n </ul>\n )}\n </li>\n );\n });\n\n return (\n <ul\n {...rest}\n role=\"tree\"\n onKeyDown={handleKeyDown}\n onClick={handleClick}\n className={cn(\"flex flex-col text-sm\", className)}\n >\n {renderNodes(items, 1, \"\")}\n </ul>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/components/tree-view.tsx"],"sourcesContent":["import { useEffect, useId, useMemo, useRef, useState } from \"react\";\nimport type { ComponentPropsWithRef, KeyboardEvent, MouseEvent, ReactNode } from \"react\";\nimport { ChevronRight } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { horizontalStep } from \"../lib/direction\";\nimport { Spinner } from \"./spinner\";\nimport { FOCUS_RING } from \"./focus-ring\";\n\n/**\n * A hierarchy you walk with the keyboard: kastlan's chart of accounts (Assets › Current\n * assets › Bank › 1020 Operating account), and — through {@link TreeRow} alone — the\n * row labels of its gantt, which indent and fold like a tree but sit beside a timeline\n * and so cannot be one.\n *\n * Both apps had written it as nested `<div>`s with a chevron button per row: every\n * row a Tab stop (a 300-account chart was 300 presses to get past), no way to say\n * \"level 3 of 5, collapsed\" to a screen reader, and no ←/→ at all. This is the WAI-ARIA\n * tree pattern instead:\n *\n * - `role=\"tree\"` › `treeitem` (with `aria-level`, `aria-expanded` on a parent,\n * `aria-selected` when selectable) › `role=\"group\"` for its children — so the\n * reader announces the position and the state it needs.\n * - ONE Tab stop (roving tabindex). ↑/↓ walk the visible rows, → opens a closed\n * parent and then steps into it, ← closes an open one and then steps out to the\n * parent, Home/End jump to the ends, `*` opens every sibling, and typing a few\n * letters jumps to the next row that starts with them. ←/→ follow the reading\n * direction (`horizontalStep`): in an RTL tree the children hang off to the left,\n * and ← is what opens them.\n * - Enter / Space, or a click, select. The chevron alone toggles on click.\n *\n * `expanded` and `selected` each work controlled or uncontrolled.\n */\n\n/* ── Nodes ───────────────────────────────────────────────────────────────── */\n\nexport interface TreeNode<T = unknown> {\n /** Unique across the whole tree — it is what `expanded` and `selected` hold. */\n id: string;\n label: ReactNode;\n /** The text type-ahead matches. Defaults to the row's rendered text. */\n textValue?: string;\n /** Before the label: an account-type icon, a colour dot. */\n icon?: ReactNode;\n /** After the label, pushed to the end of the row: a balance, a count, an action. */\n trailing?: ReactNode;\n children?: readonly TreeNode<T>[];\n /**\n * The node has children that are not loaded yet — it gets a chevron, and opening it\n * calls `loadChildren`. Pass the children in `items` once they arrive.\n */\n hasChildren?: boolean;\n /** Focusable and announced as unavailable (`aria-disabled`), but not selectable. */\n disabled?: boolean;\n /** Anything of the host's own, handed back to `renderItem` and `onSelectedChange`. */\n data?: T;\n}\n\n/** What {@link TreeViewProps.renderItem} is told about a row. */\nexport interface TreeItemState {\n level: number;\n expanded: boolean;\n selected: boolean;\n /** It has children, loaded or not — it shows a chevron. */\n expandable: boolean;\n loading: boolean;\n disabled: boolean;\n}\n\n/* ── TreeRow ─────────────────────────────────────────────────────────────── */\n\nexport interface TreeRowProps extends ComponentPropsWithRef<\"div\"> {\n /** 1 for a top-level row. Sets the indent and the number of guides. */\n level: number;\n /** `true`/`false` for a row that can fold; leave it `undefined` for a leaf, which\n * gets a chevron-sized gap instead so labels at one level still line up. */\n expanded?: boolean;\n /**\n * Make the chevron a real button (with `aria-expanded`, named by the label) that\n * calls this. For a row used ON ITS OWN — kastlan's gantt labels, where each row is\n * a line of a timeline and not an item of a tree widget. Inside {@link TreeView} it\n * is omitted: the tree owns the keys, and a button per row would be a Tab stop per\n * row, which is what the tree exists to remove.\n */\n onToggle?: () => void;\n /** The toggle button's name. By default it is named by the row's label (the state\n * is `aria-expanded`'s to say); required when `children` replaces the label. */\n toggleLabel?: string;\n label: ReactNode;\n icon?: ReactNode;\n trailing?: ReactNode;\n /** Pixels per level. Default 16. */\n indent?: number;\n /** Draw a vertical hairline per ancestor level. Default `true`. */\n guides?: boolean;\n selected?: boolean;\n /** A spinner in place of the chevron, while lazy children load. */\n loading?: boolean;\n /** Replace the label + trailing part of the row (the indent and chevron stay). */\n children?: ReactNode;\n}\n\nconst DEFAULT_INDENT = 16;\n/** The chevron's box; the guide for a level runs through its centre. */\nconst CHEVRON = 16;\n/** The row's start padding, before any indent. */\nconst ROW_PAD = 4;\n\n/**\n * One row: indent, guides, chevron, icon, label, trailing — no tree semantics of its\n * own. {@link TreeView} is built from it, and a host that needs the look without the\n * widget (a gantt's label column) uses it directly.\n *\n * Laid out with logical properties throughout (`padding-inline-start`,\n * `inset-inline-start`), so the indent, the guides and the chevron all hang from the\n * reading start in an RTL layout without a second code path.\n */\nexport function TreeRow({\n level,\n expanded,\n onToggle,\n toggleLabel,\n label,\n icon,\n trailing,\n indent = DEFAULT_INDENT,\n guides = true,\n selected,\n loading,\n children,\n className,\n style,\n ...rest\n}: TreeRowProps) {\n const labelId = useId();\n const depth = Math.max(0, level - 1);\n const expandable = expanded !== undefined;\n // A right-pointing chevron that turns DOWN when open. In RTL it is mirrored to point\n // left, and turning that one clockwise would point it UP — so it turns the other way.\n const chevron = (\n <ChevronRight\n aria-hidden\n className={cn(\n \"size-4 transition-transform motion-reduce:transition-none rtl:-scale-x-100\",\n expanded && \"rotate-90 rtl:-rotate-90\",\n )}\n />\n );\n return (\n <div\n {...rest}\n data-tree-row=\"\"\n data-selected={selected || undefined}\n style={{ paddingInlineStart: ROW_PAD + depth * indent, ...style }}\n className={cn(\n \"relative flex min-h-8 items-center gap-1.5 rounded pe-2 text-sm\",\n selected ? \"bg-active font-medium text-primary\" : \"text-secondary\",\n className,\n )}\n >\n {guides &&\n Array.from({ length: depth }, (_, i) => (\n <span\n key={i}\n aria-hidden\n data-tree-guide=\"\"\n className=\"pointer-events-none absolute inset-y-0 w-px bg-[var(--border)]\"\n style={{ insetInlineStart: ROW_PAD + i * indent + CHEVRON / 2 }}\n />\n ))}\n {loading ? (\n <span className=\"flex size-4 shrink-0 items-center justify-center\">\n <Spinner label={null} className=\"size-3 border\" />\n </span>\n ) : expandable && onToggle ? (\n <button\n type=\"button\"\n aria-expanded={expanded}\n aria-label={toggleLabel}\n aria-labelledby={toggleLabel ? undefined : labelId}\n onClick={(e) => {\n e.stopPropagation();\n onToggle();\n }}\n className={cn(\n \"flex size-4 shrink-0 items-center justify-center rounded text-muted outline-none hover:text-primary\",\n FOCUS_RING,\n )}\n >\n {chevron}\n </button>\n ) : expandable ? (\n <span\n data-tree-toggle=\"\"\n className=\"flex size-4 shrink-0 cursor-pointer items-center justify-center text-muted\"\n >\n {chevron}\n </span>\n ) : (\n <span aria-hidden className=\"size-4 shrink-0\" />\n )}\n {children ?? (\n <>\n {icon && <span className=\"flex shrink-0 items-center\">{icon}</span>}\n {/* One line at Normal; from Large up it wraps and the `min-h-8` row grows\n (text size §10.17, round 0.34 T1: the sweep read \"1020 Operat…\"). */}\n <span id={labelId} data-tree-label=\"\" className=\"min-w-0 flex-1 truncate-until-large\">\n {label}\n </span>\n {trailing !== undefined && trailing !== null && (\n <span className=\"ms-auto flex shrink-0 items-center gap-1 text-xs tabular-nums text-muted\">{trailing}</span>\n )}\n </>\n )}\n </div>\n );\n}\n\n/* ── TreeView ────────────────────────────────────────────────────────────── */\n\nexport interface TreeViewProps<T = unknown> extends Omit<\n ComponentPropsWithRef<\"ul\">,\n \"onSelect\" | \"children\" | \"defaultValue\"\n> {\n items: readonly TreeNode<T>[];\n /** Open nodes, controlled. */\n expanded?: readonly string[];\n /** Open nodes on first render, uncontrolled. */\n defaultExpanded?: readonly string[];\n onExpandedChange?: (expanded: string[]) => void;\n /** The selected node's id, controlled; `null` for none. */\n selected?: string | null;\n defaultSelected?: string | null;\n onSelectedChange?: (id: string, node: TreeNode<T>) => void;\n /** `\"none\"` makes it a navigation-only tree: no `aria-selected`, Enter toggles. */\n selectionMode?: \"single\" | \"none\";\n /** The whole row content after the chevron, per node. Wins over `label`/`trailing`. */\n renderItem?: (node: TreeNode<T>, state: TreeItemState) => ReactNode;\n /**\n * Called when a node with `hasChildren` and no `children` is opened. While the\n * promise is pending the row shows a spinner and is `aria-busy`; put the children\n * into `items` before it resolves (or after — the row opens either way).\n */\n loadChildren?: (node: TreeNode<T>) => Promise<void> | void;\n /** Pixels per level. Default 16. */\n indent?: number;\n /** Vertical hairlines per level. Default `true`. */\n indentGuides?: boolean;\n /** Extra classes for each row (the `TreeRow`), e.g. a denser height. */\n rowClassName?: string;\n}\n\ninterface FlatNode<T> {\n node: TreeNode<T>;\n level: number;\n parentId: string | null;\n}\n\nconst isExpandable = (n: TreeNode<unknown>) => Boolean(n.children?.length || n.hasChildren);\n\n/** Controlled when `value` is given, otherwise state seeded from `initial`. */\nfunction useControllable<V>(value: V | undefined, initial: V): [V, (next: V) => void] {\n const [own, setOwn] = useState<V>(initial);\n const controlled = value !== undefined;\n const set = (next: V) => {\n if (!controlled) setOwn(next);\n };\n return [controlled ? value : own, set];\n}\n\n/** How long a type-ahead run stays open between keys — the APG's suggested half second. */\nconst TYPEAHEAD_MS = 500;\n\nexport function TreeView<T = unknown>({\n items,\n expanded: expandedProp,\n defaultExpanded,\n onExpandedChange,\n selected: selectedProp,\n defaultSelected,\n onSelectedChange,\n selectionMode = \"single\",\n renderItem,\n loadChildren,\n indent = DEFAULT_INDENT,\n indentGuides = true,\n rowClassName,\n className,\n onKeyDown,\n onClick,\n ...rest\n}: TreeViewProps<T>) {\n const [expandedList, setExpandedOwn] = useControllable<readonly string[]>(expandedProp, defaultExpanded ?? []);\n const [selected, setSelectedOwn] = useControllable<string | null>(selectedProp, defaultSelected ?? null);\n const expandedSet = useMemo(() => new Set(expandedList), [expandedList]);\n const [loading, setLoading] = useState<ReadonlySet<string>>(() => new Set());\n const [focusedId, setFocusedId] = useState<string | null>(null);\n const itemRefs = useRef(new Map<string, HTMLLIElement>());\n const pendingFocus = useRef<string | null>(null);\n const typeahead = useRef({ text: \"\", at: 0 });\n const baseId = useId();\n\n // Every node by id (for parents and lookups), and the rows currently on screen in\n // order — the list the arrow keys walk.\n const { byId, visible } = useMemo(() => {\n const map = new Map<string, FlatNode<T>>();\n const rows: FlatNode<T>[] = [];\n const walk = (nodes: readonly TreeNode<T>[], level: number, parentId: string | null, shown: boolean) => {\n for (const node of nodes) {\n const flat = { node, level, parentId };\n map.set(node.id, flat);\n if (shown) rows.push(flat);\n if (node.children) walk(node.children, level + 1, node.id, shown && expandedSet.has(node.id));\n }\n };\n walk(items, 1, null, true);\n return { byId: map, visible: rows };\n }, [items, expandedSet]);\n\n // The one tabbable row. Focus that was inside a branch that has since closed (a\n // click on its parent's chevron) moves up to the nearest ancestor still on screen,\n // so the tab stop never points at a row that is not rendered.\n const visibleIds = useMemo(() => new Set(visible.map((f) => f.node.id)), [visible]);\n const nearestVisible = (id: string | null): string | null => {\n let cur = id;\n while (cur && !visibleIds.has(cur)) cur = byId.get(cur)?.parentId ?? null;\n return cur;\n };\n const tabbable = nearestVisible(focusedId) ?? nearestVisible(selected) ?? visible[0]?.node.id ?? null;\n\n useEffect(() => {\n const id = pendingFocus.current;\n if (!id) return;\n pendingFocus.current = null;\n itemRefs.current.get(id)?.focus();\n });\n\n const focusRow = (id: string) => {\n setFocusedId(id);\n // After the commit: moving into a branch that just opened targets a row that is\n // not in the DOM yet.\n pendingFocus.current = id;\n itemRefs.current.get(id)?.focus();\n };\n\n const setExpanded = (next: Set<string>) => {\n const list = [...next];\n setExpandedOwn(list);\n onExpandedChange?.(list);\n };\n\n const open = (node: TreeNode<T>) => {\n if (expandedSet.has(node.id) || !isExpandable(node)) return;\n const next = new Set(expandedSet);\n next.add(node.id);\n setExpanded(next);\n if (loadChildren && node.hasChildren && !node.children?.length) {\n const pending = loadChildren(node);\n if (pending && typeof pending.then === \"function\") {\n setLoading((s) => new Set(s).add(node.id));\n const done = () =>\n setLoading((s) => {\n const n = new Set(s);\n n.delete(node.id);\n return n;\n });\n pending.then(done, done);\n }\n }\n };\n\n const close = (node: TreeNode<T>) => {\n if (!expandedSet.has(node.id)) return;\n const next = new Set(expandedSet);\n next.delete(node.id);\n setExpanded(next);\n };\n\n const toggle = (node: TreeNode<T>) => (expandedSet.has(node.id) ? close(node) : open(node));\n\n const select = (node: TreeNode<T>) => {\n if (node.disabled) return;\n if (selectionMode === \"none\") {\n toggle(node);\n return;\n }\n setSelectedOwn(node.id);\n onSelectedChange?.(node.id, node);\n };\n\n const idOf = (target: EventTarget | null): string | null =>\n (target as Element | null)?.closest?.<HTMLElement>('[role=\"treeitem\"]')?.dataset.treeId ?? null;\n\n const textOf = (flat: FlatNode<T>): string => {\n if (flat.node.textValue !== undefined) return flat.node.textValue;\n if (typeof flat.node.label === \"string\") return flat.node.label;\n const row = itemRefs.current.get(flat.node.id)?.querySelector(\"[data-tree-row]\");\n return row?.textContent ?? \"\";\n };\n\n const findByText = (fromIndex: number, text: string): string | null => {\n const needle = text.toLocaleLowerCase();\n for (let i = 1; i <= visible.length; i++) {\n const flat = visible[(fromIndex + i) % visible.length];\n if (textOf(flat).trim().toLocaleLowerCase().startsWith(needle)) return flat.node.id;\n }\n return null;\n };\n\n const handleKeyDown = (e: KeyboardEvent<HTMLUListElement>) => {\n onKeyDown?.(e);\n if (e.defaultPrevented) return;\n const id = idOf(e.target);\n const flat = id ? byId.get(id) : undefined;\n if (!flat) return;\n const { node } = flat;\n const index = visible.findIndex((f) => f.node.id === node.id);\n const expandedNow = expandedSet.has(node.id);\n // Forward / backward along the reading direction, from the row itself.\n const step = horizontalStep(e.key, e.target as Element);\n // A space inside a type-ahead run is part of the name being typed (\"current a…\"),\n // not a request to select.\n const inRun = Date.now() - typeahead.current.at < TYPEAHEAD_MS && typeahead.current.text !== \"\";\n const key = e.key === \" \" && inRun ? \"typeahead\" : e.key;\n\n let handled = true;\n if (step === 1) {\n if (!isExpandable(node)) handled = false;\n else if (!expandedNow) open(node);\n else if (node.children?.length) focusRow(node.children[0].id);\n } else if (step === -1) {\n if (expandedNow) close(node);\n else if (flat.parentId) focusRow(flat.parentId);\n else handled = false;\n } else {\n switch (key) {\n case \"ArrowDown\":\n if (index < visible.length - 1) focusRow(visible[index + 1].node.id);\n break;\n case \"ArrowUp\":\n if (index > 0) focusRow(visible[index - 1].node.id);\n break;\n case \"Home\":\n if (visible.length) focusRow(visible[0].node.id);\n break;\n case \"End\":\n if (visible.length) focusRow(visible[visible.length - 1].node.id);\n break;\n case \"Enter\":\n case \" \":\n select(node);\n break;\n case \"*\": {\n // Every sibling at this level, as the pattern suggests.\n const siblings = flat.parentId ? (byId.get(flat.parentId)?.node.children ?? []) : items;\n const next = new Set(expandedSet);\n for (const s of siblings) if (isExpandable(s)) next.add(s.id);\n setExpanded(next);\n break;\n }\n default: {\n const printable = e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey;\n if (!printable) {\n handled = false;\n break;\n }\n const now = Date.now();\n const run = now - typeahead.current.at < TYPEAHEAD_MS ? typeahead.current.text : \"\";\n typeahead.current = { text: run + e.key, at: now };\n const typed = typeahead.current.text;\n // \"aaa\" cycles through the rows starting with \"a\", as a file list does;\n // anything else extends the prefix, searched from the row after this one\n // (or from this one, when it still matches the longer prefix).\n const repeated = typed.length > 1 && [...typed].every((c) => c === typed[0]);\n const target = repeated\n ? findByText(index, typed[0])\n : typed.length > 1 && textOf(flat).trim().toLocaleLowerCase().startsWith(typed.toLocaleLowerCase())\n ? node.id\n : findByText(index, typed);\n if (target) focusRow(target);\n }\n }\n }\n if (handled) {\n e.preventDefault();\n e.stopPropagation();\n }\n };\n\n const handleClick = (e: MouseEvent<HTMLUListElement>) => {\n onClick?.(e);\n const id = idOf(e.target);\n const flat = id ? byId.get(id) : undefined;\n if (!flat) return;\n // Controls a host rendered into a row (an action in `trailing`) are their own.\n const control = (e.target as Element).closest(\"button, a[href], input, select, textarea\");\n if (control && itemRefs.current.get(flat.node.id)?.contains(control)) return;\n setFocusedId(flat.node.id);\n if ((e.target as Element).closest(\"[data-tree-toggle]\")) {\n toggle(flat.node);\n return;\n }\n select(flat.node);\n };\n\n const renderNodes = (nodes: readonly TreeNode<T>[], level: number, path: string): ReactNode =>\n nodes.map((node, i) => {\n // Named by its own row, not by its content: a treeitem's content is the whole\n // open branch below it, and \"Assets Current assets 1020 Bank …\" is not a name.\n // A path of indices, because a host's id may hold spaces an idref cannot.\n const rowId = `${baseId}-${path}${i}`;\n const expandable = isExpandable(node);\n const isOpen = expandable && expandedSet.has(node.id);\n const isSelected = selectionMode !== \"none\" && selected === node.id;\n const isLoading = loading.has(node.id);\n const state: TreeItemState = {\n level,\n expanded: isOpen,\n selected: isSelected,\n expandable,\n loading: isLoading,\n disabled: Boolean(node.disabled),\n };\n return (\n <li\n key={node.id}\n ref={(el) => {\n if (el) itemRefs.current.set(node.id, el);\n else itemRefs.current.delete(node.id);\n }}\n role=\"treeitem\"\n data-tree-id={node.id}\n aria-labelledby={rowId}\n aria-level={level}\n aria-expanded={expandable ? isOpen : undefined}\n aria-selected={selectionMode === \"none\" ? undefined : isSelected}\n aria-disabled={node.disabled || undefined}\n aria-busy={isLoading || undefined}\n tabIndex={node.id === tabbable ? 0 : -1}\n onFocus={(e) => {\n // Only the row itself: a focus event bubbling up from a nested row, or from\n // a control inside this one, is not this row being focused.\n if (e.target === e.currentTarget) setFocusedId(node.id);\n }}\n // The ring goes on this row's own box, not the <li>: the <li> contains the\n // whole open branch below it, and ringing that would outline a subtree.\n className=\"outline-none [&:focus-visible>[data-tree-row]]:ring-focus [&:focus-visible>[data-tree-row]]:ring-brand\"\n >\n <TreeRow\n id={rowId}\n level={level}\n expanded={expandable ? isOpen : undefined}\n label={node.label}\n icon={node.icon}\n trailing={node.trailing}\n indent={indent}\n guides={indentGuides}\n selected={isSelected}\n loading={isLoading}\n className={cn(\n node.disabled ? \"cursor-not-allowed opacity-50\" : \"cursor-pointer hover:bg-hover\",\n rowClassName,\n )}\n >\n {renderItem ? renderItem(node, state) : undefined}\n </TreeRow>\n {isOpen && (\n <ul role=\"group\">\n {/* Empty while lazy children load: a \"Loading…\" line in here would be\n a child that is not a treeitem, and the parent row already says\n `aria-busy` and shows the spinner. */}\n {node.children?.length ? renderNodes(node.children, level + 1, `${path}${i}-`) : null}\n </ul>\n )}\n </li>\n );\n });\n\n return (\n <ul\n {...rest}\n role=\"tree\"\n onKeyDown={handleKeyDown}\n onClick={handleClick}\n className={cn(\"flex flex-col text-sm\", className)}\n >\n {renderNodes(items, 1, \"\")}\n </ul>\n );\n}\n"],"mappings":"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","names":[]}
@@ -90,7 +90,27 @@ export interface TreemapProps extends Omit<ComponentPropsWithoutRef<"div">, "chi
90
90
  * so a caller colouring a legend beside the map is guaranteed the same list.
91
91
  */
92
92
  colors?: readonly string[];
93
+ /**
94
+ * How tiles are parted (0.51.6, keksdose dev #642, Marcel: "The borders do not look
95
+ * nice when changing from tile to tile. Can this be uniform black?" — and then, on the
96
+ * contrast rule that would go with it: follow the contrast setting).
97
+ *
98
+ * - `"auto"` (default): `"uniform"` at Standard contrast, `"contrast"` while More
99
+ * contrast is in force (`<html data-contrast="more">`: the reader chose it, or chose
100
+ * System on a device that asks for more). Followed live.
101
+ * - `"uniform"`: one 2px gutter in the page's ground colour (`--bg-page`, near-black
102
+ * in the dark theme) round every tile, and nothing else. Tiles always abut, so the
103
+ * gutter parts them; each tile is named by its label.
104
+ * - `"contrast"`: the 0.49 rule. The gutter is the card's colour (`--bg-surface`),
105
+ * and a tile whose hue is under 3:1 against the card gets a 1px ink edge inside it
106
+ * (WCAG 1.4.11, `useChartEdge`). In the dark theme that ink is light, so some tiles
107
+ * carry a white outline and their neighbours none — the unevenness #642 saw, and
108
+ * the price of the edge for a reader who asked for it.
109
+ */
110
+ edges?: TreemapEdges;
93
111
  }
112
+ /** {@link TreemapProps.edges}. */
113
+ export type TreemapEdges = "auto" | "uniform" | "contrast";
94
114
  /**
95
115
  * Fit a label to its tile: the text itself when it fits, an ellipsised head when it
96
116
  * does not, `null` when not even three characters would. Without this a long name ran
@@ -120,6 +140,8 @@ export interface TreemapCellProps {
120
140
  * the label stands in the tile's top RIGHT corner and is shaped right-to-left, so a
121
141
  * clipped name ends in its ellipsis on the reading end. Default `ltr`. */
122
142
  dir?: Direction;
143
+ /** See {@link TreemapProps.edges}. Default `auto`. */
144
+ edges?: TreemapEdges;
123
145
  }
124
146
  /**
125
147
  * One tile. Exported so a caller drawing its own recharts `<Treemap>` gets the same
@@ -132,7 +154,7 @@ export interface TreemapCellProps {
132
154
  * (`paint-order: stroke`), which defeats any inherited stroke and keeps the text solid
133
155
  * where it overlaps a lighter or darker part of its own tile.
134
156
  */
135
- export declare function TreemapCell({ depth, x, y, width, height, name, fill, labelColor, id, onNodeClick, redactNames, nodeNote, dir, }: TreemapCellProps): import("react").JSX.Element | null;
157
+ export declare function TreemapCell({ depth, x, y, width, height, name, fill, labelColor, id, onNodeClick, redactNames, nodeNote, dir, edges: edgesProp, }: TreemapCellProps): import("react").JSX.Element | null;
136
158
  /**
137
159
  * Tile chart: one rectangle per {@link TreemapNode}, sized by `value`, coloured by
138
160
  * index from the chart ramp (or by the node's own `fill`), labelled in whichever of the
@@ -144,4 +166,4 @@ export declare function TreemapCell({ depth, x, y, width, height, name, fill, la
144
166
  *
145
167
  * Needs `recharts`, like everything behind `@eifi1/ui-kit/chart`.
146
168
  */
147
- export declare function Treemap({ data, valueFormatter, height, onNodeClick, redactNames, nodeNote, maxTiles, colors, style, ...rest }: TreemapProps): import("react").JSX.Element | null;
169
+ export declare function Treemap({ data, valueFormatter, height, onNodeClick, redactNames, nodeNote, maxTiles, colors, edges, style, ...rest }: TreemapProps): import("react").JSX.Element | null;
@@ -11,6 +11,18 @@ import { cn } from "../lib/cn.js";
11
11
  import { useTextSize } from "../theme/text-size.js";
12
12
  import { chartHeightProps } from "./chart-height.js";
13
13
  import { formatWithMinus } from "../lib/minus.js";
14
+ function subscribeContrast(onChange) {
15
+ if (typeof MutationObserver === "undefined" || typeof document === "undefined") return () => {
16
+ };
17
+ const observer = new MutationObserver(onChange);
18
+ observer.observe(document.documentElement, { attributes: true, attributeFilter: ["data-contrast"] });
19
+ return () => observer.disconnect();
20
+ }
21
+ const readMoreContrast = () => typeof document !== "undefined" && document.documentElement.getAttribute("data-contrast") === "more";
22
+ function useTileEdges(edges) {
23
+ const more = useSyncExternalStore(subscribeContrast, readMoreContrast, () => false);
24
+ return edges === "auto" ? more ? "contrast" : "uniform" : edges;
25
+ }
14
26
  const CHART_TOKENS = PALETTE_SIZE;
15
27
  function readChartTokens() {
16
28
  const root = document.documentElement;
@@ -63,11 +75,13 @@ function TreemapCell({
63
75
  onNodeClick,
64
76
  redactNames,
65
77
  nodeNote,
66
- dir = "ltr"
78
+ dir = "ltr",
79
+ edges: edgesProp = "auto"
67
80
  }) {
68
81
  const tokenHex = useChartTokenHex();
69
82
  const { scale } = useTextSize();
70
83
  const edgeFor = useChartEdge();
84
+ const edges = useTileEdges(edgesProp);
71
85
  if (!depth) return null;
72
86
  const px = Math.round(x);
73
87
  const py = Math.round(y);
@@ -76,7 +90,7 @@ function TreemapCell({
76
90
  const tileFill = fill ?? "var(--chart-1)";
77
91
  const measured = measurableFill(tileFill, tokenHex);
78
92
  const ink = labelColor ?? (measured ? textOn(measured) : "currentColor");
79
- const edge = measured ? edgeFor(measured) : void 0;
93
+ const edge = edges === "contrast" && measured ? edgeFor(measured) : void 0;
80
94
  const halo = measured ? tileFill : "none";
81
95
  const labelSize = LABEL_FONT_SIZE * scale;
82
96
  const noteSize = NOTE_FONT_SIZE * scale;
@@ -110,7 +124,7 @@ function TreemapCell({
110
124
  width: pw,
111
125
  height: ph,
112
126
  fill: tileFill,
113
- stroke: "var(--bg-surface)",
127
+ stroke: edges === "contrast" ? "var(--bg-surface)" : "var(--bg-page)",
114
128
  strokeWidth: 2,
115
129
  rx: 3
116
130
  }
@@ -184,6 +198,7 @@ function Treemap({
184
198
  nodeNote,
185
199
  maxTiles,
186
200
  colors,
201
+ edges = "auto",
187
202
  style,
188
203
  ...rest
189
204
  }) {
@@ -226,7 +241,16 @@ function Treemap({
226
241
  dataKey: "size",
227
242
  fill: "var(--chart-1)",
228
243
  isAnimationActive: false,
229
- content: /* @__PURE__ */ jsx(TreemapCell, { onNodeClick, redactNames, nodeNote, dir }),
244
+ content: /* @__PURE__ */ jsx(
245
+ TreemapCell,
246
+ {
247
+ onNodeClick,
248
+ redactNames,
249
+ nodeNote,
250
+ dir,
251
+ edges
252
+ }
253
+ ),
230
254
  children: /* @__PURE__ */ jsx(ChartTooltip, { content: /* @__PURE__ */ jsx(Content, { hideLabel: true, valueFormatter: format }) })
231
255
  }
232
256
  )
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/treemap.tsx"],"sourcesContent":["// A tile chart (\"treemap\"): one rectangle per node, its AREA the node's share of what\n// is drawn. Lifted out of keksdose's reports, where five cards drew it (spending by\n// group and by category, net worth by account and by type, allocation, payees, food\n// groups) — every domain word is gone and every string it could print is a prop.\n//\n// It uses the kit's own chart shell (`ChartContainer`, `ChartTooltipContent`) and the\n// kit's own ink rule (`textOn`), so it re-skins with the palette like every other\n// chart. The one thing it cannot take from a CSS var is the tile colour: the label ink\n// is MEASURED against the fill, and a `var(--chart-3)` string measures as nothing — see\n// `useChartTokenHex` below for how the concrete hex is found instead.\nimport { useLayoutEffect, useRef, useState, useSyncExternalStore } from \"react\";\nimport type { ComponentProps, ComponentPropsWithoutRef, KeyboardEvent, ReactNode } from \"react\";\nimport { Treemap as RechartsTreemap } from \"recharts\";\nimport { ChartContainer, ChartTooltip, ChartTooltipContent, type ChartConfig } from \"./chart\";\nimport { PALETTE_HEX, PALETTE_SIZE, textOn } from \"../theme/chart-palette\";\nimport { resolveChartColor, useChartEdge } from \"./chart-edge\";\nimport { parseHex } from \"../theme/color\";\nimport { dirOf, type Direction } from \"../lib/direction\";\nimport { cn } from \"../lib/cn\";\nimport { useTextSize } from \"../theme/text-size\";\nimport { chartHeightProps, type ChartHeight } from \"./chart-height\";\nimport { formatWithMinus } from \"../lib/minus\";\n\n/**\n * One tile.\n *\n * `value`, not keksdose's `total`: a treemap's areas are not always money (keksdose\n * already sizes one by transaction COUNT), and `value` is the word every other chart\n * in recharts uses for the number a mark is sized by. Migrating is a rename at the\n * mapping step — `{ id, name, value: row.total }`.\n */\nexport interface TreemapNode {\n /** Stable key, handed back by {@link TreemapProps.onNodeClick} and\n * {@link TreemapProps.nodeNote}. Names can collide; ids must not. */\n id: string;\n name: string;\n /** The tile's area. A node whose value is not a positive finite number is NOT\n * drawn — an area cannot be negative, and recharts lays a negative size out as\n * nonsense rather than refusing it. Saying so is the caller's job: a group that\n * vanishes silently between two views of the same data is a bug report. */\n value: number;\n /** Overrides the by-index palette colour for this tile — e.g. to paint every child\n * in its parent group's colour, so the groups stay legible in one picture. A\n * concrete `#rrggbb` or a bare `var(--chart-N)` keeps the label ink measured — so\n * does `CHART_ROLES.other` (`var(--chart-muted)`), the colour for a \"rest\" bucket,\n * which should not wrap round to a hue the reader matches to a real node;\n * anything else (another var, a `color-mix()`) gets `currentColor` ink, because\n * there is nothing to measure — set `labelColor` then. */\n fill?: string;\n /** The label's ink, for a `fill` the kit cannot measure (a `var()` or `color-mix()`):\n * keksdose's recency plot fills with `color-mix(in srgb, var(--chart-1) N%,\n * transparent)`, where `currentColor` put dark page text on full-strength indigo\n * (~1.5:1). Wins over the measured ink when given. */\n labelColor?: string;\n}\n\n/**\n * Everything a `<div>` takes reaches the chart's root (`ChartContainer`), so a test id\n * or a `data-tour` anchor can find it. `children` is the chart's own.\n */\nexport interface TreemapProps extends Omit<ComponentPropsWithoutRef<\"div\">, \"children\"> {\n data: TreemapNode[];\n /**\n * Formats a node's value in the tooltip. Handed the node's SHARE of what is drawn\n * (0–1) as a second argument, because a tile is read for how big it is relative to\n * the rest, and \"€412.30\" alone makes the reader estimate that ratio off the areas.\n * The share is `undefined` when the drawn total is not positive.\n *\n * Default: the bare number through `Intl.NumberFormat` in the runtime's locale. A\n * host that has a currency or a locale passes this — the kit knows neither.\n */\n valueFormatter?: (value: number, share: number | undefined) => ReactNode;\n /** Chart height: a CSS length (`\"20rem\"`, `\"min(20rem, 50dvh)\"`), a Tailwind class,\n * or px. Default `\"20rem\"` — 320 px at Normal, growing with the text size like the\n * labels in the tiles (0.32, docs/text-size-harmonization.md §10.10). */\n height?: ChartHeight;\n /** A tile was activated — by click, or by Enter/Space on a focused tile. Setting\n * this makes every tile a focusable `role=\"button\"` named by its node name. */\n onNodeClick?: (id: string, name: string) => void;\n /**\n * The node names are the user's own data (payees, not categories): the tile labels\n * and the whole tooltip bubble get `data-private`, the same attribute the kit's\n * `Tooltip redact` and `ChartTooltipContent`'s value cell carry, so the host's one\n * privacy rule reaches them. Off by default — a category name is a label of the app.\n */\n redactNames?: boolean;\n /**\n * A second, smaller line under a tile's label — one extra figure per node that the\n * name cannot carry (a year-on-year `+12%`, `new`). Return `undefined` for a node\n * with nothing to add. Never tagged `data-private`: a ratio reveals no amount.\n *\n * ON THE TILE rather than in the tooltip, for two reasons. The tooltip identifies a\n * node by name or by raw value, so a per-node note would be keyed on an amount (two\n * nodes of equal size would swap notes). And a tooltip is a pointer affordance: a\n * phone never hovers, so a figure that only exists on hover does not exist there.\n */\n nodeNote?: (id: string) => string | undefined;\n /**\n * Draw at most this many tiles (the first N drawable nodes, in the order given —\n * sort first). Unbounded by default. A tile under ~44x20px has no room for a label,\n * and a map made of unlabelled rectangles answers nothing; around 24 is where the\n * smallest tile of a skewed series reaches that floor on a phone-width card. The\n * caller owns the \"N more not shown\" line, since it knows how many it passed.\n */\n maxTiles?: number;\n /**\n * The categorical ramp tiles take by index, as concrete hex. Default: the page's\n * live `--chart-1…8` tokens (see `useChartTokenHex`), cycled after eight like\n * `paletteFor` (0.49). Pass one when the host\n * already holds the active palette — e.g. `createPaletteStore`'s `useChartHex()` —\n * so a caller colouring a legend beside the map is guaranteed the same list.\n */\n colors?: readonly string[];\n}\n\n// ── The categorical ramp, as concrete hex ──────────────────────────────────\n//\n// The label ink is measured against the tile's fill, so the fill has to be a colour\n// and not a reference to one. keksdose read it from its palette STORE, which is an\n// app object the kit cannot import. What the kit does own is the token layer: whether\n// a palette arrives through `applyTokenSet` (inline properties on <html>) or through\n// tokens.css (`:root` / `.dark`), the resolved `--chart-N` on <html> is the colour\n// every other chart on the page is being painted with. So that is what is read, and\n// re-read whenever <html>'s class or style changes — which is exactly when a theme\n// toggle or a palette switch lands.\n\nconst CHART_TOKENS = PALETTE_SIZE;\n\n/** The resolved ramp, joined into ONE string so `useSyncExternalStore` compares a\n * primitive: a fresh array per read would never be `Object.is`-equal to the last and\n * would re-render forever. */\nfunction readChartTokens(): string {\n const root = document.documentElement;\n const fallback = root.classList.contains(\"dark\") ? PALETTE_HEX.dark : PALETTE_HEX.light;\n const style = getComputedStyle(root);\n return Array.from({ length: CHART_TOKENS }, (_, i) => {\n const v = style.getPropertyValue(`--chart-${i + 1}`).trim();\n // An unresolved token (a host that never loaded tokens.css, a test DOM) falls\n // back to the built-in ramp for the current theme rather than to no colour.\n return parseHex(v) ? v : fallback[i];\n }).join(\",\");\n}\n\nfunction subscribeChartTokens(onChange: () => void): () => void {\n const observer = new MutationObserver(onChange);\n observer.observe(document.documentElement, {\n attributes: true,\n attributeFilter: [\"class\", \"style\", \"data-theme\"],\n });\n return () => observer.disconnect();\n}\n\nconst SERVER_TOKENS = PALETTE_HEX.light.join(\",\");\n\nfunction useChartTokenHex(): string[] {\n const joined = useSyncExternalStore(subscribeChartTokens, readChartTokens, () => SERVER_TOKENS);\n return joined.split(\",\");\n}\n\nconst CHART_TOKEN_REF = /^var\\(\\s*--chart-(\\d+)\\s*\\)$/;\n\n/**\n * The hex a tile fill measures as: the fill itself when it is hex, the live token when\n * it is a bare `var(--chart-N)` (what `paletteFor` returns), otherwise null. Without\n * the token case a hand-built recharts map coloured with the kit's own `paletteFor`\n * got `currentColor` ink — dark page text on dark indigo in the light theme.\n */\nfunction measurableFill(fill: string, tokens: readonly string[]): string | null {\n if (parseHex(fill)) return fill;\n const m = CHART_TOKEN_REF.exec(fill.trim());\n const hex = m ? tokens[Number(m[1]) - 1] : undefined;\n if (hex && parseHex(hex)) return hex;\n // Any other bare token the page declares — `var(--chart-muted)` for a rest bucket,\n // `var(--chart-9)`, a money colour — read off <html> as it resolves.\n if (typeof document === \"undefined\") return null;\n return resolveChartColor(fill, getComputedStyle(document.documentElement));\n}\n\n// ── Tile ──────────────────────────────────────────────────────────────────\n\n/** The name's size in px at Normal; a tile draws it × the text size's scale (§10.10), and\n * every distance below that is measured in lines grows with it. */\nconst LABEL_FONT_SIZE = 12;\n/** The optional second line, a step down from the name so the tile reads name-first. */\nconst NOTE_FONT_SIZE = 11;\n\n/**\n * Fit a label to its tile: the text itself when it fits, an ellipsised head when it\n * does not, `null` when not even three characters would. Without this a long name ran\n * on across its neighbours and collided with THEIR labels. 0.58em is the average\n * advance of a UI sans at this weight; it only needs to be close, since the tile keeps\n * 6px of padding either side. The full name stays in the tooltip.\n */\nexport function fitLabel(name: string, boxWidth: number, fontSize: number): string | null {\n const max = Math.floor((boxWidth - 12) / (fontSize * 0.58));\n if (max < 3) return null;\n return name.length <= max ? name : `${name.slice(0, max - 1).trimEnd()}…`;\n}\n\n/** What recharts hands a `content` element for each node, plus the three options the\n * chart forwards. Everything optional because recharts clones the element it was\n * given and fills the geometry in — the element as written carries only the options. */\nexport interface TreemapCellProps {\n depth?: number;\n x?: number;\n y?: number;\n width?: number;\n height?: number;\n name?: string;\n fill?: string;\n /** See {@link TreemapNode.labelColor}; recharts hands each node's fields to its cell. */\n labelColor?: string;\n id?: string;\n onNodeClick?: (id: string, name: string) => void;\n redactNames?: boolean;\n nodeNote?: (id: string) => string | undefined;\n /** The page's reading direction, which `Treemap` reads off its own root. In `rtl`\n * the label stands in the tile's top RIGHT corner and is shaped right-to-left, so a\n * clipped name ends in its ellipsis on the reading end. Default `ltr`. */\n dir?: Direction;\n}\n\n/**\n * One tile. Exported so a caller drawing its own recharts `<Treemap>` gets the same\n * label rules.\n *\n * recharts' default cell inherits the chart-level `stroke`, an SVG presentation\n * attribute that lands on the `<svg>` root and cascades into the label — so the glyphs\n * were painted with a 1px outline ON TOP of themselves, which reads as blur. This cell\n * draws its own label with a halo in the TILE colour painted BEHIND the fill\n * (`paint-order: stroke`), which defeats any inherited stroke and keeps the text solid\n * where it overlaps a lighter or darker part of its own tile.\n */\nexport function TreemapCell({\n depth,\n x = 0,\n y = 0,\n width = 0,\n height = 0,\n name,\n fill,\n labelColor,\n id,\n onNodeClick,\n redactNames,\n nodeNote,\n dir = \"ltr\",\n}: TreemapCellProps) {\n // Before the early return: a hook has to run on every render of the cell.\n const tokenHex = useChartTokenHex();\n const { scale } = useTextSize();\n const edgeFor = useChartEdge();\n // Depth 0 is recharts' synthetic root, which spans the whole chart.\n if (!depth) return null;\n // Whole pixels: a rect on a half pixel renders its edge across two half-lit\n // columns, and the text baseline inherits that blur.\n const px = Math.round(x);\n const py = Math.round(y);\n const pw = Math.round(width);\n const ph = Math.round(height);\n const tileFill = fill ?? \"var(--chart-1)\";\n const measured = measurableFill(tileFill, tokenHex);\n const ink = labelColor ?? (measured ? textOn(measured) : \"currentColor\");\n // A tile in a hue under 3:1 against the card gets a 1px ink edge inside the gutter\n // (WCAG 1.4.11, anyplot's rule); a hue that clears it stays bare.\n const edge = measured ? edgeFor(measured) : undefined;\n // The halo only on an OPAQUE fill we could measure. A translucent fill (a\n // `color-mix(… transparent)`) composited twice — once as the tile, once as the halo\n // over it — draws a darker ring round every glyph, which read as doubled text on the\n // faded tiles. `none` still overrides the inherited chart `stroke`.\n const halo = measured ? tileFill : \"none\";\n // Below ~44x20 there is no room for a readable word, so no label at all rather than\n // a one-letter stub bleeding over the tile edge.\n const labelSize = LABEL_FONT_SIZE * scale;\n const noteSize = NOTE_FONT_SIZE * scale;\n const label = pw >= 44 * scale && ph >= 20 * scale ? fitLabel(name ?? \"\", pw, labelSize) : null;\n // Only under a label that is itself drawn — a stray \"+12%\" on an unnamed rectangle\n // names nothing — and only where a second baseline fits: the note's baseline sits\n // 29px down and an 11px glyph hangs ~2px below it, so 36 leaves the same ~5px under\n // the note that the label keeps under itself at the 20px floor.\n const rawNote = id != null && nodeNote ? nodeNote(id) : undefined;\n const note =\n label != null && rawNote != null && ph >= 36 * scale ? fitLabel(rawNote, pw, noteSize) : null;\n // The label's START edge, 6px in from the tile's. The tiles themselves are laid out\n // physically (the plot is not mirrored, see `ChartContainer`) and the chart's SVG is\n // pinned `ltr`, so the direction is set on each <text> explicitly: `start` is then\n // the right end in RTL, and the label grows leftwards from the tile's right edge.\n const textX = dir === \"rtl\" ? px + pw - 6 : px + 6;\n const activate = id != null && onNodeClick ? () => onNodeClick(id, name ?? \"\") : undefined;\n const onKeyDown = activate\n ? (e: KeyboardEvent<SVGGElement>) => {\n if (e.key !== \"Enter\" && e.key !== \" \") return;\n e.preventDefault();\n activate();\n }\n : undefined;\n return (\n <g\n onClick={activate}\n onKeyDown={onKeyDown}\n // A clickable tile is a button to a keyboard and a screen reader too; the\n // drilldown it opens was mouse-only in the app this came from. Named by the node\n // name, which is also why the group carries `data-private` under `redactNames`.\n role={activate ? \"button\" : undefined}\n tabIndex={activate ? 0 : undefined}\n aria-label={activate ? name : undefined}\n data-private={activate && redactNames ? \"\" : undefined}\n cursor={activate ? \"pointer\" : undefined}\n // CSS beats a presentation attribute, so this recolours the rect's own stroke\n // for as long as the tile has keyboard focus.\n className={activate ? \"outline-none [&:focus-visible>rect]:stroke-[var(--brand)]\" : undefined}\n >\n {/* The 2px gutter is the card showing through, not a white grid — on a dark\n surface white lines glare harder than the tiles. */}\n <rect\n x={px}\n y={py}\n width={pw}\n height={ph}\n fill={tileFill}\n stroke=\"var(--bg-surface)\"\n strokeWidth={2}\n rx={3}\n />\n {edge && pw > 3 && ph > 3 && (\n <rect\n data-edge=\"\"\n x={px + 1.5}\n y={py + 1.5}\n width={pw - 3}\n height={ph - 3}\n fill=\"none\"\n stroke={edge}\n strokeWidth={1}\n rx={2}\n style={{ pointerEvents: \"none\" }}\n />\n )}\n {label != null && (\n <text\n // A plain attribute, so the host's `[data-private]` rule reaches an SVG\n // <text> exactly as it reaches a table cell.\n data-private={redactNames ? \"\" : undefined}\n x={textX}\n y={py + 15 * scale}\n direction={dir}\n fill={ink}\n stroke={halo}\n strokeWidth={2.5}\n strokeLinejoin=\"round\"\n paintOrder=\"stroke\"\n fontSize={labelSize}\n fontWeight={600}\n style={{ pointerEvents: \"none\" }}\n >\n {label}\n </text>\n )}\n {note != null && (\n <text\n x={textX}\n y={py + 29 * scale}\n direction={dir}\n fill={ink}\n fillOpacity={0.85}\n stroke={halo}\n strokeWidth={2.5}\n strokeLinejoin=\"round\"\n paintOrder=\"stroke\"\n fontSize={noteSize}\n fontWeight={500}\n style={{ pointerEvents: \"none\" }}\n >\n {note}\n </text>\n )}\n </g>\n );\n}\n\n// ── Chart ─────────────────────────────────────────────────────────────────\n\n// Nodes carry their own `fill`, and `ChartTooltipContent` falls back to it for the\n// swatch, so no series config is needed.\nconst TREEMAP_CONFIG: ChartConfig = {};\n\n/**\n * The shared tooltip with the whole bubble tagged `data-private`, for names that are\n * the user's own data.\n *\n * The bubble, not just the name: `ChartTooltipContent` prints the name either from\n * `item.name` or from a config `label`, and only the config route could be tagged — but\n * a config entry keyed by a payee name is not a CSS identifier, and `ChartStyle` would\n * (rightly) refuse it. The bubble holds the name and the value, and the value cell is\n * already `data-private`, so tagging the wrapper hides nothing extra.\n *\n * recharts CLONES the `content` element to inject `active`/`payload`, so this has to BE\n * that element — a `<div>` wrapped around `<ChartTooltipContent />` in the JSX would\n * take those props itself and hand the tooltip nothing.\n */\nfunction RedactedTooltipContent(props: ComponentProps<typeof ChartTooltipContent>) {\n return (\n <div data-private>\n <ChartTooltipContent {...props} />\n </div>\n );\n}\n\nconst DEFAULT_NUMBER = new Intl.NumberFormat();\n\n/**\n * Tile chart: one rectangle per {@link TreemapNode}, sized by `value`, coloured by\n * index from the chart ramp (or by the node's own `fill`), labelled in whichever of the\n * kit's two inks measures better against it. Hover shows the name and the formatted\n * value; `onNodeClick` turns the tiles into buttons.\n *\n * Renders nothing when no node has a positive value — an empty box is a hole in the\n * card, and the caller's own sentence is the better answer there.\n *\n * Needs `recharts`, like everything behind `@eifi1/ui-kit/chart`.\n */\nexport function Treemap({\n data,\n valueFormatter,\n height = \"20rem\",\n onNodeClick,\n redactNames = false,\n nodeNote,\n maxTiles,\n colors,\n style,\n ...rest\n}: TreemapProps) {\n const tokenHex = useChartTokenHex();\n // Read off the DOM after mount: the `dir` a treemap sits under is an ancestor's\n // attribute, and SVG text anchoring cannot follow it by CSS (see TreemapCell). Again\n // on new data, because a map that had nothing to draw rendered no root to read.\n const rootRef = useRef<HTMLDivElement>(null);\n const [dir, setDir] = useState<Direction>(\"ltr\");\n useLayoutEffect(() => {\n setDir(dirOf(rootRef.current));\n }, [data]);\n const ramp = colors && colors.length > 0 ? colors : tokenHex;\n // Coloured by index into `data` AS GIVEN, before anything is dropped: a caller\n // painting a legend or a second map from the same list must land on the same hue\n // for the same row, and filtering first would shift every colour after a gap.\n const nodes = data\n .map((d, idx) => ({\n id: d.id,\n name: d.name,\n size: d.value,\n fill: d.fill ?? ramp[idx % ramp.length],\n // recharts hands each node's fields to its cell; a field left out of this map\n // never reaches `TreemapCell` (0.8.0 shipped `labelColor` without it — keksdose).\n labelColor: d.labelColor,\n }))\n .filter((n) => Number.isFinite(n.size) && n.size > 0)\n .slice(0, maxTiles ?? undefined);\n if (nodes.length === 0) return null;\n const box = chartHeightProps(height);\n\n // The share is of what is DRAWN, so it always reads as \"of this picture\".\n const total = nodes.reduce((acc, n) => acc + n.size, 0);\n const format = (v: number): ReactNode => {\n const share = total > 0 ? v / total : undefined;\n return valueFormatter ? valueFormatter(v, share) : formatWithMinus(DEFAULT_NUMBER, v);\n };\n const Content = redactNames ? RedactedTooltipContent : ChartTooltipContent;\n return (\n <ChartContainer\n {...rest}\n ref={rootRef}\n config={TREEMAP_CONFIG}\n className={cn(box.className, rest.className)}\n style={{ ...style, ...box.style }}\n >\n {/* No chart-level `stroke`: recharts spreads it onto the <svg> root, from where\n it inherits into every label (see TreemapCell). Each cell strokes its own rect. */}\n <RechartsTreemap\n data={nodes}\n dataKey=\"size\"\n fill=\"var(--chart-1)\"\n isAnimationActive={false}\n content={\n <TreemapCell onNodeClick={onNodeClick} redactNames={redactNames} nodeNote={nodeNote} dir={dir} />\n }\n >\n <ChartTooltip content={<Content hideLabel valueFormatter={format} />} />\n </RechartsTreemap>\n </ChartContainer>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/components/treemap.tsx"],"sourcesContent":["// A tile chart (\"treemap\"): one rectangle per node, its AREA the node's share of what\n// is drawn. Lifted out of keksdose's reports, where five cards drew it (spending by\n// group and by category, net worth by account and by type, allocation, payees, food\n// groups) — every domain word is gone and every string it could print is a prop.\n//\n// It uses the kit's own chart shell (`ChartContainer`, `ChartTooltipContent`) and the\n// kit's own ink rule (`textOn`), so it re-skins with the palette like every other\n// chart. The one thing it cannot take from a CSS var is the tile colour: the label ink\n// is MEASURED against the fill, and a `var(--chart-3)` string measures as nothing — see\n// `useChartTokenHex` below for how the concrete hex is found instead.\nimport { useLayoutEffect, useRef, useState, useSyncExternalStore } from \"react\";\nimport type { ComponentProps, ComponentPropsWithoutRef, KeyboardEvent, ReactNode } from \"react\";\nimport { Treemap as RechartsTreemap } from \"recharts\";\nimport { ChartContainer, ChartTooltip, ChartTooltipContent, type ChartConfig } from \"./chart\";\nimport { PALETTE_HEX, PALETTE_SIZE, textOn } from \"../theme/chart-palette\";\nimport { resolveChartColor, useChartEdge } from \"./chart-edge\";\nimport { parseHex } from \"../theme/color\";\nimport { dirOf, type Direction } from \"../lib/direction\";\nimport { cn } from \"../lib/cn\";\nimport { useTextSize } from \"../theme/text-size\";\nimport { chartHeightProps, type ChartHeight } from \"./chart-height\";\nimport { formatWithMinus } from \"../lib/minus\";\n\n/**\n * One tile.\n *\n * `value`, not keksdose's `total`: a treemap's areas are not always money (keksdose\n * already sizes one by transaction COUNT), and `value` is the word every other chart\n * in recharts uses for the number a mark is sized by. Migrating is a rename at the\n * mapping step — `{ id, name, value: row.total }`.\n */\nexport interface TreemapNode {\n /** Stable key, handed back by {@link TreemapProps.onNodeClick} and\n * {@link TreemapProps.nodeNote}. Names can collide; ids must not. */\n id: string;\n name: string;\n /** The tile's area. A node whose value is not a positive finite number is NOT\n * drawn — an area cannot be negative, and recharts lays a negative size out as\n * nonsense rather than refusing it. Saying so is the caller's job: a group that\n * vanishes silently between two views of the same data is a bug report. */\n value: number;\n /** Overrides the by-index palette colour for this tile — e.g. to paint every child\n * in its parent group's colour, so the groups stay legible in one picture. A\n * concrete `#rrggbb` or a bare `var(--chart-N)` keeps the label ink measured — so\n * does `CHART_ROLES.other` (`var(--chart-muted)`), the colour for a \"rest\" bucket,\n * which should not wrap round to a hue the reader matches to a real node;\n * anything else (another var, a `color-mix()`) gets `currentColor` ink, because\n * there is nothing to measure — set `labelColor` then. */\n fill?: string;\n /** The label's ink, for a `fill` the kit cannot measure (a `var()` or `color-mix()`):\n * keksdose's recency plot fills with `color-mix(in srgb, var(--chart-1) N%,\n * transparent)`, where `currentColor` put dark page text on full-strength indigo\n * (~1.5:1). Wins over the measured ink when given. */\n labelColor?: string;\n}\n\n/**\n * Everything a `<div>` takes reaches the chart's root (`ChartContainer`), so a test id\n * or a `data-tour` anchor can find it. `children` is the chart's own.\n */\nexport interface TreemapProps extends Omit<ComponentPropsWithoutRef<\"div\">, \"children\"> {\n data: TreemapNode[];\n /**\n * Formats a node's value in the tooltip. Handed the node's SHARE of what is drawn\n * (0–1) as a second argument, because a tile is read for how big it is relative to\n * the rest, and \"€412.30\" alone makes the reader estimate that ratio off the areas.\n * The share is `undefined` when the drawn total is not positive.\n *\n * Default: the bare number through `Intl.NumberFormat` in the runtime's locale. A\n * host that has a currency or a locale passes this — the kit knows neither.\n */\n valueFormatter?: (value: number, share: number | undefined) => ReactNode;\n /** Chart height: a CSS length (`\"20rem\"`, `\"min(20rem, 50dvh)\"`), a Tailwind class,\n * or px. Default `\"20rem\"` — 320 px at Normal, growing with the text size like the\n * labels in the tiles (0.32, docs/text-size-harmonization.md §10.10). */\n height?: ChartHeight;\n /** A tile was activated — by click, or by Enter/Space on a focused tile. Setting\n * this makes every tile a focusable `role=\"button\"` named by its node name. */\n onNodeClick?: (id: string, name: string) => void;\n /**\n * The node names are the user's own data (payees, not categories): the tile labels\n * and the whole tooltip bubble get `data-private`, the same attribute the kit's\n * `Tooltip redact` and `ChartTooltipContent`'s value cell carry, so the host's one\n * privacy rule reaches them. Off by default — a category name is a label of the app.\n */\n redactNames?: boolean;\n /**\n * A second, smaller line under a tile's label — one extra figure per node that the\n * name cannot carry (a year-on-year `+12%`, `new`). Return `undefined` for a node\n * with nothing to add. Never tagged `data-private`: a ratio reveals no amount.\n *\n * ON THE TILE rather than in the tooltip, for two reasons. The tooltip identifies a\n * node by name or by raw value, so a per-node note would be keyed on an amount (two\n * nodes of equal size would swap notes). And a tooltip is a pointer affordance: a\n * phone never hovers, so a figure that only exists on hover does not exist there.\n */\n nodeNote?: (id: string) => string | undefined;\n /**\n * Draw at most this many tiles (the first N drawable nodes, in the order given —\n * sort first). Unbounded by default. A tile under ~44x20px has no room for a label,\n * and a map made of unlabelled rectangles answers nothing; around 24 is where the\n * smallest tile of a skewed series reaches that floor on a phone-width card. The\n * caller owns the \"N more not shown\" line, since it knows how many it passed.\n */\n maxTiles?: number;\n /**\n * The categorical ramp tiles take by index, as concrete hex. Default: the page's\n * live `--chart-1…8` tokens (see `useChartTokenHex`), cycled after eight like\n * `paletteFor` (0.49). Pass one when the host\n * already holds the active palette — e.g. `createPaletteStore`'s `useChartHex()` —\n * so a caller colouring a legend beside the map is guaranteed the same list.\n */\n colors?: readonly string[];\n /**\n * How tiles are parted (0.51.6, keksdose dev #642, Marcel: \"The borders do not look\n * nice when changing from tile to tile. Can this be uniform black?\" — and then, on the\n * contrast rule that would go with it: follow the contrast setting).\n *\n * - `\"auto\"` (default): `\"uniform\"` at Standard contrast, `\"contrast\"` while More\n * contrast is in force (`<html data-contrast=\"more\">`: the reader chose it, or chose\n * System on a device that asks for more). Followed live.\n * - `\"uniform\"`: one 2px gutter in the page's ground colour (`--bg-page`, near-black\n * in the dark theme) round every tile, and nothing else. Tiles always abut, so the\n * gutter parts them; each tile is named by its label.\n * - `\"contrast\"`: the 0.49 rule. The gutter is the card's colour (`--bg-surface`),\n * and a tile whose hue is under 3:1 against the card gets a 1px ink edge inside it\n * (WCAG 1.4.11, `useChartEdge`). In the dark theme that ink is light, so some tiles\n * carry a white outline and their neighbours none — the unevenness #642 saw, and\n * the price of the edge for a reader who asked for it.\n */\n edges?: TreemapEdges;\n}\n\n/** {@link TreemapProps.edges}. */\nexport type TreemapEdges = \"auto\" | \"uniform\" | \"contrast\";\n\n// `<html data-contrast>`, followed live: what `edges=\"auto\"` keys on. Read here rather\n// than through theme/contrast, whose store would come into the chart entry with it.\nfunction subscribeContrast(onChange: () => void): () => void {\n if (typeof MutationObserver === \"undefined\" || typeof document === \"undefined\") return () => {};\n const observer = new MutationObserver(onChange);\n observer.observe(document.documentElement, { attributes: true, attributeFilter: [\"data-contrast\"] });\n return () => observer.disconnect();\n}\nconst readMoreContrast = () =>\n typeof document !== \"undefined\" && document.documentElement.getAttribute(\"data-contrast\") === \"more\";\n\n/** The edges a tile draws: `auto` resolved against the contrast in force. */\nfunction useTileEdges(edges: TreemapEdges): \"uniform\" | \"contrast\" {\n const more = useSyncExternalStore(subscribeContrast, readMoreContrast, () => false);\n return edges === \"auto\" ? (more ? \"contrast\" : \"uniform\") : edges;\n}\n\n// ── The categorical ramp, as concrete hex ──────────────────────────────────\n//\n// The label ink is measured against the tile's fill, so the fill has to be a colour\n// and not a reference to one. keksdose read it from its palette STORE, which is an\n// app object the kit cannot import. What the kit does own is the token layer: whether\n// a palette arrives through `applyTokenSet` (inline properties on <html>) or through\n// tokens.css (`:root` / `.dark`), the resolved `--chart-N` on <html> is the colour\n// every other chart on the page is being painted with. So that is what is read, and\n// re-read whenever <html>'s class or style changes — which is exactly when a theme\n// toggle or a palette switch lands.\n\nconst CHART_TOKENS = PALETTE_SIZE;\n\n/** The resolved ramp, joined into ONE string so `useSyncExternalStore` compares a\n * primitive: a fresh array per read would never be `Object.is`-equal to the last and\n * would re-render forever. */\nfunction readChartTokens(): string {\n const root = document.documentElement;\n const fallback = root.classList.contains(\"dark\") ? PALETTE_HEX.dark : PALETTE_HEX.light;\n const style = getComputedStyle(root);\n return Array.from({ length: CHART_TOKENS }, (_, i) => {\n const v = style.getPropertyValue(`--chart-${i + 1}`).trim();\n // An unresolved token (a host that never loaded tokens.css, a test DOM) falls\n // back to the built-in ramp for the current theme rather than to no colour.\n return parseHex(v) ? v : fallback[i];\n }).join(\",\");\n}\n\nfunction subscribeChartTokens(onChange: () => void): () => void {\n const observer = new MutationObserver(onChange);\n observer.observe(document.documentElement, {\n attributes: true,\n attributeFilter: [\"class\", \"style\", \"data-theme\"],\n });\n return () => observer.disconnect();\n}\n\nconst SERVER_TOKENS = PALETTE_HEX.light.join(\",\");\n\nfunction useChartTokenHex(): string[] {\n const joined = useSyncExternalStore(subscribeChartTokens, readChartTokens, () => SERVER_TOKENS);\n return joined.split(\",\");\n}\n\nconst CHART_TOKEN_REF = /^var\\(\\s*--chart-(\\d+)\\s*\\)$/;\n\n/**\n * The hex a tile fill measures as: the fill itself when it is hex, the live token when\n * it is a bare `var(--chart-N)` (what `paletteFor` returns), otherwise null. Without\n * the token case a hand-built recharts map coloured with the kit's own `paletteFor`\n * got `currentColor` ink — dark page text on dark indigo in the light theme.\n */\nfunction measurableFill(fill: string, tokens: readonly string[]): string | null {\n if (parseHex(fill)) return fill;\n const m = CHART_TOKEN_REF.exec(fill.trim());\n const hex = m ? tokens[Number(m[1]) - 1] : undefined;\n if (hex && parseHex(hex)) return hex;\n // Any other bare token the page declares — `var(--chart-muted)` for a rest bucket,\n // `var(--chart-9)`, a money colour — read off <html> as it resolves.\n if (typeof document === \"undefined\") return null;\n return resolveChartColor(fill, getComputedStyle(document.documentElement));\n}\n\n// ── Tile ──────────────────────────────────────────────────────────────────\n\n/** The name's size in px at Normal; a tile draws it × the text size's scale (§10.10), and\n * every distance below that is measured in lines grows with it. */\nconst LABEL_FONT_SIZE = 12;\n/** The optional second line, a step down from the name so the tile reads name-first. */\nconst NOTE_FONT_SIZE = 11;\n\n/**\n * Fit a label to its tile: the text itself when it fits, an ellipsised head when it\n * does not, `null` when not even three characters would. Without this a long name ran\n * on across its neighbours and collided with THEIR labels. 0.58em is the average\n * advance of a UI sans at this weight; it only needs to be close, since the tile keeps\n * 6px of padding either side. The full name stays in the tooltip.\n */\nexport function fitLabel(name: string, boxWidth: number, fontSize: number): string | null {\n const max = Math.floor((boxWidth - 12) / (fontSize * 0.58));\n if (max < 3) return null;\n return name.length <= max ? name : `${name.slice(0, max - 1).trimEnd()}…`;\n}\n\n/** What recharts hands a `content` element for each node, plus the three options the\n * chart forwards. Everything optional because recharts clones the element it was\n * given and fills the geometry in — the element as written carries only the options. */\nexport interface TreemapCellProps {\n depth?: number;\n x?: number;\n y?: number;\n width?: number;\n height?: number;\n name?: string;\n fill?: string;\n /** See {@link TreemapNode.labelColor}; recharts hands each node's fields to its cell. */\n labelColor?: string;\n id?: string;\n onNodeClick?: (id: string, name: string) => void;\n redactNames?: boolean;\n nodeNote?: (id: string) => string | undefined;\n /** The page's reading direction, which `Treemap` reads off its own root. In `rtl`\n * the label stands in the tile's top RIGHT corner and is shaped right-to-left, so a\n * clipped name ends in its ellipsis on the reading end. Default `ltr`. */\n dir?: Direction;\n /** See {@link TreemapProps.edges}. Default `auto`. */\n edges?: TreemapEdges;\n}\n\n/**\n * One tile. Exported so a caller drawing its own recharts `<Treemap>` gets the same\n * label rules.\n *\n * recharts' default cell inherits the chart-level `stroke`, an SVG presentation\n * attribute that lands on the `<svg>` root and cascades into the label — so the glyphs\n * were painted with a 1px outline ON TOP of themselves, which reads as blur. This cell\n * draws its own label with a halo in the TILE colour painted BEHIND the fill\n * (`paint-order: stroke`), which defeats any inherited stroke and keeps the text solid\n * where it overlaps a lighter or darker part of its own tile.\n */\nexport function TreemapCell({\n depth,\n x = 0,\n y = 0,\n width = 0,\n height = 0,\n name,\n fill,\n labelColor,\n id,\n onNodeClick,\n redactNames,\n nodeNote,\n dir = \"ltr\",\n edges: edgesProp = \"auto\",\n}: TreemapCellProps) {\n // Before the early return: a hook has to run on every render of the cell.\n const tokenHex = useChartTokenHex();\n const { scale } = useTextSize();\n const edgeFor = useChartEdge();\n const edges = useTileEdges(edgesProp);\n // Depth 0 is recharts' synthetic root, which spans the whole chart.\n if (!depth) return null;\n // Whole pixels: a rect on a half pixel renders its edge across two half-lit\n // columns, and the text baseline inherits that blur.\n const px = Math.round(x);\n const py = Math.round(y);\n const pw = Math.round(width);\n const ph = Math.round(height);\n const tileFill = fill ?? \"var(--chart-1)\";\n const measured = measurableFill(tileFill, tokenHex);\n const ink = labelColor ?? (measured ? textOn(measured) : \"currentColor\");\n // `contrast`: a tile in a hue under 3:1 against the card gets a 1px ink edge inside\n // the gutter (WCAG 1.4.11, anyplot's rule); a hue that clears it stays bare.\n // `uniform`: no per-tile edge; every tile is parted by the same gutter.\n const edge = edges === \"contrast\" && measured ? edgeFor(measured) : undefined;\n // The halo only on an OPAQUE fill we could measure. A translucent fill (a\n // `color-mix(… transparent)`) composited twice — once as the tile, once as the halo\n // over it — draws a darker ring round every glyph, which read as doubled text on the\n // faded tiles. `none` still overrides the inherited chart `stroke`.\n const halo = measured ? tileFill : \"none\";\n // Below ~44x20 there is no room for a readable word, so no label at all rather than\n // a one-letter stub bleeding over the tile edge.\n const labelSize = LABEL_FONT_SIZE * scale;\n const noteSize = NOTE_FONT_SIZE * scale;\n const label = pw >= 44 * scale && ph >= 20 * scale ? fitLabel(name ?? \"\", pw, labelSize) : null;\n // Only under a label that is itself drawn — a stray \"+12%\" on an unnamed rectangle\n // names nothing — and only where a second baseline fits: the note's baseline sits\n // 29px down and an 11px glyph hangs ~2px below it, so 36 leaves the same ~5px under\n // the note that the label keeps under itself at the 20px floor.\n const rawNote = id != null && nodeNote ? nodeNote(id) : undefined;\n const note = label != null && rawNote != null && ph >= 36 * scale ? fitLabel(rawNote, pw, noteSize) : null;\n // The label's START edge, 6px in from the tile's. The tiles themselves are laid out\n // physically (the plot is not mirrored, see `ChartContainer`) and the chart's SVG is\n // pinned `ltr`, so the direction is set on each <text> explicitly: `start` is then\n // the right end in RTL, and the label grows leftwards from the tile's right edge.\n const textX = dir === \"rtl\" ? px + pw - 6 : px + 6;\n const activate = id != null && onNodeClick ? () => onNodeClick(id, name ?? \"\") : undefined;\n const onKeyDown = activate\n ? (e: KeyboardEvent<SVGGElement>) => {\n if (e.key !== \"Enter\" && e.key !== \" \") return;\n e.preventDefault();\n activate();\n }\n : undefined;\n return (\n <g\n onClick={activate}\n onKeyDown={onKeyDown}\n // A clickable tile is a button to a keyboard and a screen reader too; the\n // drilldown it opens was mouse-only in the app this came from. Named by the node\n // name, which is also why the group carries `data-private` under `redactNames`.\n role={activate ? \"button\" : undefined}\n tabIndex={activate ? 0 : undefined}\n aria-label={activate ? name : undefined}\n data-private={activate && redactNames ? \"\" : undefined}\n cursor={activate ? \"pointer\" : undefined}\n // CSS beats a presentation attribute, so this recolours the rect's own stroke\n // for as long as the tile has keyboard focus.\n className={activate ? \"outline-none [&:focus-visible>rect]:stroke-[var(--brand)]\" : undefined}\n >\n {/* The 2px gutter: the page's ground (`uniform`, the same dark line round every\n tile in the dark theme) or the card showing through (`contrast`). Never a white\n grid: on a dark surface white lines glare harder than the tiles. */}\n <rect\n x={px}\n y={py}\n width={pw}\n height={ph}\n fill={tileFill}\n stroke={edges === \"contrast\" ? \"var(--bg-surface)\" : \"var(--bg-page)\"}\n strokeWidth={2}\n rx={3}\n />\n {edge && pw > 3 && ph > 3 && (\n <rect\n data-edge=\"\"\n x={px + 1.5}\n y={py + 1.5}\n width={pw - 3}\n height={ph - 3}\n fill=\"none\"\n stroke={edge}\n strokeWidth={1}\n rx={2}\n style={{ pointerEvents: \"none\" }}\n />\n )}\n {label != null && (\n <text\n // A plain attribute, so the host's `[data-private]` rule reaches an SVG\n // <text> exactly as it reaches a table cell.\n data-private={redactNames ? \"\" : undefined}\n x={textX}\n y={py + 15 * scale}\n direction={dir}\n fill={ink}\n stroke={halo}\n strokeWidth={2.5}\n strokeLinejoin=\"round\"\n paintOrder=\"stroke\"\n fontSize={labelSize}\n fontWeight={600}\n style={{ pointerEvents: \"none\" }}\n >\n {label}\n </text>\n )}\n {note != null && (\n <text\n x={textX}\n y={py + 29 * scale}\n direction={dir}\n fill={ink}\n fillOpacity={0.85}\n stroke={halo}\n strokeWidth={2.5}\n strokeLinejoin=\"round\"\n paintOrder=\"stroke\"\n fontSize={noteSize}\n fontWeight={500}\n style={{ pointerEvents: \"none\" }}\n >\n {note}\n </text>\n )}\n </g>\n );\n}\n\n// ── Chart ─────────────────────────────────────────────────────────────────\n\n// Nodes carry their own `fill`, and `ChartTooltipContent` falls back to it for the\n// swatch, so no series config is needed.\nconst TREEMAP_CONFIG: ChartConfig = {};\n\n/**\n * The shared tooltip with the whole bubble tagged `data-private`, for names that are\n * the user's own data.\n *\n * The bubble, not just the name: `ChartTooltipContent` prints the name either from\n * `item.name` or from a config `label`, and only the config route could be tagged — but\n * a config entry keyed by a payee name is not a CSS identifier, and `ChartStyle` would\n * (rightly) refuse it. The bubble holds the name and the value, and the value cell is\n * already `data-private`, so tagging the wrapper hides nothing extra.\n *\n * recharts CLONES the `content` element to inject `active`/`payload`, so this has to BE\n * that element — a `<div>` wrapped around `<ChartTooltipContent />` in the JSX would\n * take those props itself and hand the tooltip nothing.\n */\nfunction RedactedTooltipContent(props: ComponentProps<typeof ChartTooltipContent>) {\n return (\n <div data-private>\n <ChartTooltipContent {...props} />\n </div>\n );\n}\n\nconst DEFAULT_NUMBER = new Intl.NumberFormat();\n\n/**\n * Tile chart: one rectangle per {@link TreemapNode}, sized by `value`, coloured by\n * index from the chart ramp (or by the node's own `fill`), labelled in whichever of the\n * kit's two inks measures better against it. Hover shows the name and the formatted\n * value; `onNodeClick` turns the tiles into buttons.\n *\n * Renders nothing when no node has a positive value — an empty box is a hole in the\n * card, and the caller's own sentence is the better answer there.\n *\n * Needs `recharts`, like everything behind `@eifi1/ui-kit/chart`.\n */\nexport function Treemap({\n data,\n valueFormatter,\n height = \"20rem\",\n onNodeClick,\n redactNames = false,\n nodeNote,\n maxTiles,\n colors,\n edges = \"auto\",\n style,\n ...rest\n}: TreemapProps) {\n const tokenHex = useChartTokenHex();\n // Read off the DOM after mount: the `dir` a treemap sits under is an ancestor's\n // attribute, and SVG text anchoring cannot follow it by CSS (see TreemapCell). Again\n // on new data, because a map that had nothing to draw rendered no root to read.\n const rootRef = useRef<HTMLDivElement>(null);\n const [dir, setDir] = useState<Direction>(\"ltr\");\n useLayoutEffect(() => {\n setDir(dirOf(rootRef.current));\n }, [data]);\n const ramp = colors && colors.length > 0 ? colors : tokenHex;\n // Coloured by index into `data` AS GIVEN, before anything is dropped: a caller\n // painting a legend or a second map from the same list must land on the same hue\n // for the same row, and filtering first would shift every colour after a gap.\n const nodes = data\n .map((d, idx) => ({\n id: d.id,\n name: d.name,\n size: d.value,\n fill: d.fill ?? ramp[idx % ramp.length],\n // recharts hands each node's fields to its cell; a field left out of this map\n // never reaches `TreemapCell` (0.8.0 shipped `labelColor` without it — keksdose).\n labelColor: d.labelColor,\n }))\n .filter((n) => Number.isFinite(n.size) && n.size > 0)\n .slice(0, maxTiles ?? undefined);\n if (nodes.length === 0) return null;\n const box = chartHeightProps(height);\n\n // The share is of what is DRAWN, so it always reads as \"of this picture\".\n const total = nodes.reduce((acc, n) => acc + n.size, 0);\n const format = (v: number): ReactNode => {\n const share = total > 0 ? v / total : undefined;\n return valueFormatter ? valueFormatter(v, share) : formatWithMinus(DEFAULT_NUMBER, v);\n };\n const Content = redactNames ? RedactedTooltipContent : ChartTooltipContent;\n return (\n <ChartContainer\n {...rest}\n ref={rootRef}\n config={TREEMAP_CONFIG}\n className={cn(box.className, rest.className)}\n style={{ ...style, ...box.style }}\n >\n {/* No chart-level `stroke`: recharts spreads it onto the <svg> root, from where\n it inherits into every label (see TreemapCell). Each cell strokes its own rect. */}\n <RechartsTreemap\n data={nodes}\n dataKey=\"size\"\n fill=\"var(--chart-1)\"\n isAnimationActive={false}\n content={\n <TreemapCell\n onNodeClick={onNodeClick}\n redactNames={redactNames}\n nodeNote={nodeNote}\n dir={dir}\n edges={edges}\n />\n }\n >\n <ChartTooltip content={<Content hideLabel valueFormatter={format} />} />\n </RechartsTreemap>\n </ChartContainer>\n );\n}\n"],"mappings":"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","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/trigger-aria.ts"],"sourcesContent":["// Internal — not re-exported from the barrel. Shared by the pickers whose trigger is\n// a `role=\"combobox\"` button inside a wrapper: DatePicker, DateRangePicker, MonthPicker.\n\n/**\n * The attributes that NAME or DESCRIBE a field, which belong on its trigger — the\n * element a `<label htmlFor>`, a form library's control slot or an error message has\n * to reach. They used to land on the wrapper `<div>` with the rest of the caller's\n * props, so an external label named nothing and a form library's `aria-describedby`\n * pointed a screen reader at an element with no role (kastlan, 0.5.0).\n */\nexport interface TriggerAria {\n id?: string;\n \"aria-label\"?: string;\n \"aria-labelledby\"?: string;\n \"aria-describedby\"?: string;\n \"aria-invalid\"?: boolean | \"true\" | \"false\" | \"grammar\" | \"spelling\";\n /** 0.23.0: a form library's `required` (RhfMonthPicker) is announced on the trigger,\n * not lost on the wrapper. */\n \"aria-required\"?: boolean | \"true\" | \"false\";\n}\n\n/** Split a field's props into what its trigger takes and what its wrapper takes. */\nexport function splitTriggerAria<T extends TriggerAria>(\n props: T,\n): [TriggerAria, Omit<T, keyof TriggerAria>] {\n const {\n id,\n \"aria-label\": label,\n \"aria-labelledby\": labelledBy,\n \"aria-describedby\": describedBy,\n \"aria-invalid\": invalid,\n \"aria-required\": required,\n ...rest\n } = props;\n return [\n {\n id,\n \"aria-label\": label,\n \"aria-labelledby\": labelledBy,\n \"aria-describedby\": describedBy,\n \"aria-invalid\": invalid,\n \"aria-required\": required,\n },\n rest,\n ];\n}\n\n"],"mappings":";AAsBO,SAAS,iBACd,OAC2C;AAC3C,QAAM;AAAA,IACJ;AAAA,IACA,cAAc;AAAA,IACd,mBAAmB;AAAA,IACnB,oBAAoB;AAAA,IACpB,gBAAgB;AAAA,IAChB,iBAAiB;AAAA,IACjB,GAAG;AAAA,EACL,IAAI;AACJ,SAAO;AAAA,IACL;AAAA,MACE;AAAA,MACA,cAAc;AAAA,MACd,mBAAmB;AAAA,MACnB,oBAAoB;AAAA,MACpB,gBAAgB;AAAA,MAChB,iBAAiB;AAAA,IACnB;AAAA,IACA;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../../src/components/trigger-aria.ts"],"sourcesContent":["// Internal — not re-exported from the barrel. Shared by the pickers whose trigger is\n// a `role=\"combobox\"` button inside a wrapper: DatePicker, DateRangePicker, MonthPicker.\n\n/**\n * The attributes that NAME or DESCRIBE a field, which belong on its trigger — the\n * element a `<label htmlFor>`, a form library's control slot or an error message has\n * to reach. They used to land on the wrapper `<div>` with the rest of the caller's\n * props, so an external label named nothing and a form library's `aria-describedby`\n * pointed a screen reader at an element with no role (kastlan, 0.5.0).\n */\nexport interface TriggerAria {\n id?: string;\n \"aria-label\"?: string;\n \"aria-labelledby\"?: string;\n \"aria-describedby\"?: string;\n \"aria-invalid\"?: boolean | \"true\" | \"false\" | \"grammar\" | \"spelling\";\n /** 0.23.0: a form library's `required` (RhfMonthPicker) is announced on the trigger,\n * not lost on the wrapper. */\n \"aria-required\"?: boolean | \"true\" | \"false\";\n}\n\n/** Split a field's props into what its trigger takes and what its wrapper takes. */\nexport function splitTriggerAria<T extends TriggerAria>(props: T): [TriggerAria, Omit<T, keyof TriggerAria>] {\n const {\n id,\n \"aria-label\": label,\n \"aria-labelledby\": labelledBy,\n \"aria-describedby\": describedBy,\n \"aria-invalid\": invalid,\n \"aria-required\": required,\n ...rest\n } = props;\n return [\n {\n id,\n \"aria-label\": label,\n \"aria-labelledby\": labelledBy,\n \"aria-describedby\": describedBy,\n \"aria-invalid\": invalid,\n \"aria-required\": required,\n },\n rest,\n ];\n}\n"],"mappings":";AAsBO,SAAS,iBAAwC,OAAqD;AAC3G,QAAM;AAAA,IACJ;AAAA,IACA,cAAc;AAAA,IACd,mBAAmB;AAAA,IACnB,oBAAoB;AAAA,IACpB,gBAAgB;AAAA,IAChB,iBAAiB;AAAA,IACjB,GAAG;AAAA,EACL,IAAI;AACJ,SAAO;AAAA,IACL;AAAA,MACE;AAAA,MACA,cAAc;AAAA,MACd,mBAAmB;AAAA,MACnB,oBAAoB;AAAA,MACpB,gBAAgB;AAAA,MAChB,iBAAiB;AAAA,IACnB;AAAA,IACA;AAAA,EACF;AACF;","names":[]}
@@ -4,12 +4,7 @@ import * as ReactRouter from "react-router";
4
4
  import { useLocation, useSearchParams } from "react-router";
5
5
  import { useKitTableStorageKeyPrefix } from "../i18n/kit-labels.js";
6
6
  import { readStored, writeStored } from "../lib/safe-storage.js";
7
- import {
8
- decodeFilterValue,
9
- encodeFilterValue,
10
- isFilterActive,
11
- resolveFilter
12
- } from "./data-table-filters.js";
7
+ import { decodeFilterValue, encodeFilterValue, isFilterActive, resolveFilter } from "./data-table-filters.js";
13
8
  import { decodeSorts, encodeSorts, normalizeSorts } from "./data-table-sort.js";
14
9
  function withDefaultHidden(view, defaults) {
15
10
  const extra = defaults.filter((k) => !view.hidden.has(k) && !view.shown.has(k));
@@ -168,10 +163,7 @@ function useTableState({
168
163
  const [urlInitial] = useState(
169
164
  () => urlSync ? readTableUrlState(searchParams, columns, urlPrefix) : {}
170
165
  );
171
- const initial = useMemo(
172
- () => loadPersisted(storageKey, storageKeyPrefix),
173
- [storageKey, storageKeyPrefix]
174
- );
166
+ const initial = useMemo(() => loadPersisted(storageKey, storageKeyPrefix), [storageKey, storageKeyPrefix]);
175
167
  const [internalSorts, setInternalSorts] = useState(
176
168
  () => urlInitial.sorts !== void 0 ? urlInitial.sorts : normalizeSorts(initial.sort)
177
169
  );
@@ -247,7 +239,18 @@ function useTableState({
247
239
  if (!urlSync) return;
248
240
  if (lastUrl.current === urlSig) return;
249
241
  lastUrl.current = urlSig;
250
- writeUrlState(setSearchParams, columns, sorts, filters, pageSize, page, defaultPageSize, void 0, locationState, keys);
242
+ writeUrlState(
243
+ setSearchParams,
244
+ columns,
245
+ sorts,
246
+ filters,
247
+ pageSize,
248
+ page,
249
+ defaultPageSize,
250
+ void 0,
251
+ locationState,
252
+ keys
253
+ );
251
254
  }, [urlSync, urlSig]);
252
255
  return {
253
256
  sorts,