@eifi1/ui-kit 0.54.0 → 0.55.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (847) hide show
  1. package/README.md +90 -76
  2. package/dist/admin/plan-change.js +2 -2
  3. package/dist/admin/plan-change.js.map +1 -1
  4. package/dist/admin/user-roster.d.ts +6 -0
  5. package/dist/admin/user-roster.js +4 -0
  6. package/dist/admin/user-roster.js.map +1 -1
  7. package/dist/billing/billing-labels.d.ts +26 -0
  8. package/dist/billing/billing-labels.js +9 -0
  9. package/dist/billing/billing-labels.js.map +1 -1
  10. package/dist/billing/subscription-standing.d.ts +61 -0
  11. package/dist/billing/subscription-standing.js +51 -0
  12. package/dist/billing/subscription-standing.js.map +1 -0
  13. package/dist/billing/usage-meter.d.ts +52 -0
  14. package/dist/billing/usage-meter.js +52 -0
  15. package/dist/billing/usage-meter.js.map +1 -0
  16. package/dist/chart.d.ts +1 -0
  17. package/dist/chart.js +4 -0
  18. package/dist/chart.js.map +1 -1
  19. package/dist/components/alert-banner.js +2 -3
  20. package/dist/components/alert-banner.js.map +1 -1
  21. package/dist/components/amount-input.js +3 -15
  22. package/dist/components/amount-input.js.map +1 -1
  23. package/dist/components/autocomplete.js +2 -2
  24. package/dist/components/autocomplete.js.map +1 -1
  25. package/dist/components/bulk-action-bar.js +2 -1
  26. package/dist/components/bulk-action-bar.js.map +1 -1
  27. package/dist/components/button.d.ts +35 -1
  28. package/dist/components/button.js +5 -9
  29. package/dist/components/button.js.map +1 -1
  30. package/dist/components/calendar-heatmap.js +2 -2
  31. package/dist/components/calendar-heatmap.js.map +1 -1
  32. package/dist/components/camera-button.d.ts +89 -0
  33. package/dist/components/camera-button.js +122 -0
  34. package/dist/components/camera-button.js.map +1 -0
  35. package/dist/components/camera-capture.d.ts +66 -0
  36. package/dist/components/camera-capture.js +190 -0
  37. package/dist/components/camera-capture.js.map +1 -0
  38. package/dist/components/camera-labels.d.ts +20 -0
  39. package/dist/components/camera-labels.js +15 -0
  40. package/dist/components/camera-labels.js.map +1 -0
  41. package/dist/components/chart-edge.d.ts +9 -0
  42. package/dist/components/chart-edge.js +14 -3
  43. package/dist/components/chart-edge.js.map +1 -1
  44. package/dist/components/chart-zoom-core.js +34 -29
  45. package/dist/components/chart-zoom-core.js.map +1 -1
  46. package/dist/components/checkbox-group.js +1 -1
  47. package/dist/components/checkbox-group.js.map +1 -1
  48. package/dist/components/combobox-core.js +3 -2
  49. package/dist/components/combobox-core.js.map +1 -1
  50. package/dist/components/combobox.js +2 -2
  51. package/dist/components/combobox.js.map +1 -1
  52. package/dist/components/consent-again-notice-labels.d.ts +25 -0
  53. package/dist/components/consent-again-notice-labels.js +15 -0
  54. package/dist/components/consent-again-notice-labels.js.map +1 -0
  55. package/dist/components/consent-again-notice.d.ts +85 -0
  56. package/dist/components/consent-again-notice.js +94 -0
  57. package/dist/components/consent-again-notice.js.map +1 -0
  58. package/dist/components/country-select.js +2 -7
  59. package/dist/components/country-select.js.map +1 -1
  60. package/dist/components/currency-select.js +2 -2
  61. package/dist/components/currency-select.js.map +1 -1
  62. package/dist/components/danger-confirm.js +2 -2
  63. package/dist/components/danger-confirm.js.map +1 -1
  64. package/dist/components/data-table-column-settings.js +2 -2
  65. package/dist/components/data-table-column-settings.js.map +1 -1
  66. package/dist/components/data-table-fit.js.map +1 -1
  67. package/dist/components/data-table-virtual.js +13 -3
  68. package/dist/components/data-table-virtual.js.map +1 -1
  69. package/dist/components/date-field.js +2 -2
  70. package/dist/components/date-field.js.map +1 -1
  71. package/dist/components/date-picker.js +2 -2
  72. package/dist/components/date-picker.js.map +1 -1
  73. package/dist/components/date-range-picker.js +2 -6
  74. package/dist/components/date-range-picker.js.map +1 -1
  75. package/dist/components/dialog-frame.d.ts +3 -1
  76. package/dist/components/dialog-frame.js.map +1 -1
  77. package/dist/components/dropdown.js +7 -3
  78. package/dist/components/dropdown.js.map +1 -1
  79. package/dist/components/entity-combobox.js +2 -2
  80. package/dist/components/entity-combobox.js.map +1 -1
  81. package/dist/components/field-parts.d.ts +108 -6
  82. package/dist/components/field-parts.js +59 -6
  83. package/dist/components/field-parts.js.map +1 -1
  84. package/dist/components/field-suggestion-labels.d.ts +2 -0
  85. package/dist/components/field-suggestion-labels.js +10 -0
  86. package/dist/components/field-suggestion-labels.js.map +1 -0
  87. package/dist/components/field.js +1 -1
  88. package/dist/components/field.js.map +1 -1
  89. package/dist/components/file-button.d.ts +1 -1
  90. package/dist/components/file-button.js.map +1 -1
  91. package/dist/components/floating-panel.d.ts +7 -6
  92. package/dist/components/floating-panel.js +3 -17
  93. package/dist/components/floating-panel.js.map +1 -1
  94. package/dist/components/form-actions.d.ts +3 -2
  95. package/dist/components/form-actions.js.map +1 -1
  96. package/dist/components/hover-menu.js +3 -2
  97. package/dist/components/hover-menu.js.map +1 -1
  98. package/dist/components/icon-button.d.ts +23 -5
  99. package/dist/components/icon-button.js +6 -8
  100. package/dist/components/icon-button.js.map +1 -1
  101. package/dist/components/inline-confirm.js +2 -1
  102. package/dist/components/inline-confirm.js.map +1 -1
  103. package/dist/components/inline-entity-combobox.js +2 -2
  104. package/dist/components/inline-entity-combobox.js.map +1 -1
  105. package/dist/components/input.js +2 -2
  106. package/dist/components/input.js.map +1 -1
  107. package/dist/components/jump-calendar.js +3 -3
  108. package/dist/components/jump-calendar.js.map +1 -1
  109. package/dist/components/legal.d.ts +13 -1
  110. package/dist/components/legal.js +12 -0
  111. package/dist/components/legal.js.map +1 -1
  112. package/dist/components/legended-series-chart.d.ts +80 -0
  113. package/dist/components/legended-series-chart.js +70 -0
  114. package/dist/components/legended-series-chart.js.map +1 -0
  115. package/dist/components/lightbox.js +2 -1
  116. package/dist/components/lightbox.js.map +1 -1
  117. package/dist/components/mini-calendar.js +13 -5
  118. package/dist/components/mini-calendar.js.map +1 -1
  119. package/dist/components/modal.d.ts +23 -2
  120. package/dist/components/modal.js +24 -6
  121. package/dist/components/modal.js.map +1 -1
  122. package/dist/components/month-picker.d.ts +1 -1
  123. package/dist/components/month-picker.js +6 -6
  124. package/dist/components/month-picker.js.map +1 -1
  125. package/dist/components/multi-entity-combobox.js +2 -2
  126. package/dist/components/multi-entity-combobox.js.map +1 -1
  127. package/dist/components/multi-select.js +2 -6
  128. package/dist/components/multi-select.js.map +1 -1
  129. package/dist/components/number-input.js +2 -2
  130. package/dist/components/number-input.js.map +1 -1
  131. package/dist/components/page-header-layout.d.ts +18 -0
  132. package/dist/components/page-header-layout.js +23 -0
  133. package/dist/components/page-header-layout.js.map +1 -0
  134. package/dist/components/page-header-skeleton.d.ts +41 -0
  135. package/dist/components/page-header-skeleton.js +52 -0
  136. package/dist/components/page-header-skeleton.js.map +1 -0
  137. package/dist/components/page-header.js +4 -17
  138. package/dist/components/page-header.js.map +1 -1
  139. package/dist/components/popover.js +7 -2
  140. package/dist/components/popover.js.map +1 -1
  141. package/dist/components/release-notes-labels.d.ts +16 -0
  142. package/dist/components/release-notes-labels.js +18 -0
  143. package/dist/components/release-notes-labels.js.map +1 -0
  144. package/dist/components/release-notes.d.ts +65 -0
  145. package/dist/components/release-notes.js +65 -0
  146. package/dist/components/release-notes.js.map +1 -0
  147. package/dist/components/select.js +2 -2
  148. package/dist/components/select.js.map +1 -1
  149. package/dist/components/series-chart-labels.d.ts +9 -0
  150. package/dist/components/series-chart-labels.js +3 -1
  151. package/dist/components/series-chart-labels.js.map +1 -1
  152. package/dist/components/series-chart.d.ts +5 -0
  153. package/dist/components/series-chart.js +89 -36
  154. package/dist/components/series-chart.js.map +1 -1
  155. package/dist/components/toggle-group.js +3 -2
  156. package/dist/components/toggle-group.js.map +1 -1
  157. package/dist/components/treemap-layout.js.map +1 -1
  158. package/dist/components/treemap.d.ts +17 -15
  159. package/dist/components/treemap.js +167 -85
  160. package/dist/components/treemap.js.map +1 -1
  161. package/dist/eslint/index.js +1 -1
  162. package/dist/eslint/role-utilities.d.ts +2 -2
  163. package/dist/eslint/role-utilities.js.map +1 -1
  164. package/dist/feedback/feedback-inbox-page.d.ts +260 -0
  165. package/dist/feedback/feedback-inbox-page.js +207 -0
  166. package/dist/feedback/feedback-inbox-page.js.map +1 -0
  167. package/dist/feedback/feedback-vocabulary.js +2 -2
  168. package/dist/feedback/feedback-vocabulary.js.map +1 -1
  169. package/dist/feedback.d.ts +1 -0
  170. package/dist/feedback.js +6 -0
  171. package/dist/feedback.js.map +1 -1
  172. package/dist/hooks/use-debounce.d.ts +32 -5
  173. package/dist/hooks/use-debounce.js +6 -5
  174. package/dist/hooks/use-debounce.js.map +1 -1
  175. package/dist/hooks/use-file-picker.d.ts +3 -1
  176. package/dist/hooks/use-file-picker.js.map +1 -1
  177. package/dist/hooks/use-focus-trap.d.ts +19 -3
  178. package/dist/hooks/use-focus-trap.js +15 -5
  179. package/dist/hooks/use-focus-trap.js.map +1 -1
  180. package/dist/hooks/use-has-camera.d.ts +36 -0
  181. package/dist/hooks/use-has-camera.js +53 -0
  182. package/dist/hooks/use-has-camera.js.map +1 -0
  183. package/dist/hooks/use-hotkey.js +1 -10
  184. package/dist/hooks/use-hotkey.js.map +1 -1
  185. package/dist/hooks/use-overlay-history.d.ts +6 -1
  186. package/dist/hooks/use-overlay-history.js +10 -2
  187. package/dist/hooks/use-overlay-history.js.map +1 -1
  188. package/dist/i18n/defaults.js +11 -1
  189. package/dist/i18n/defaults.js.map +1 -1
  190. package/dist/i18n/german.js +75 -3
  191. package/dist/i18n/german.js.map +1 -1
  192. package/dist/i18n/kit-labels.d.ts +20 -0
  193. package/dist/i18n/kit-labels.js.map +1 -1
  194. package/dist/i18n/locales/en.js +92 -4
  195. package/dist/i18n/locales/en.js.map +3 -3
  196. package/dist/i18n/locales/es.js +71 -3
  197. package/dist/i18n/locales/es.js.map +1 -1
  198. package/dist/i18n/locales/fr.js +72 -2
  199. package/dist/i18n/locales/fr.js.map +1 -1
  200. package/dist/i18n/locales/hu.js +74 -3
  201. package/dist/i18n/locales/hu.js.map +1 -1
  202. package/dist/i18n/locales/it.js +74 -3
  203. package/dist/i18n/locales/it.js.map +1 -1
  204. package/dist/i18n/locales/zh.js +71 -3
  205. package/dist/i18n/locales/zh.js.map +1 -1
  206. package/dist/i18n/review.js +22 -1
  207. package/dist/i18n/review.js.map +1 -1
  208. package/dist/index.d.ts +24 -5
  209. package/dist/index.js +35 -1
  210. package/dist/index.js.map +1 -1
  211. package/dist/landing/landing-labels.d.ts +8 -0
  212. package/dist/landing/landing-labels.js +2 -1
  213. package/dist/landing/landing-labels.js.map +1 -1
  214. package/dist/lib/changelog.d.ts +130 -0
  215. package/dist/lib/changelog.js +137 -0
  216. package/dist/lib/changelog.js.map +1 -0
  217. package/dist/lib/clipboard-minus.js +2 -2
  218. package/dist/lib/clipboard-minus.js.map +1 -1
  219. package/dist/lib/dates.d.ts +29 -3
  220. package/dist/lib/dates.js +41 -12
  221. package/dist/lib/dates.js.map +1 -1
  222. package/dist/lib/editable-content.d.ts +33 -0
  223. package/dist/lib/editable-content.js +15 -0
  224. package/dist/lib/editable-content.js.map +1 -0
  225. package/dist/lib/focus-left.d.ts +47 -1
  226. package/dist/lib/focus-left.js +11 -2
  227. package/dist/lib/focus-left.js.map +1 -1
  228. package/dist/lib/server-wake.d.ts +49 -9
  229. package/dist/lib/server-wake.js +25 -14
  230. package/dist/lib/server-wake.js.map +1 -1
  231. package/dist/lib/session-refresh.d.ts +53 -0
  232. package/dist/lib/session-refresh.js +27 -1
  233. package/dist/lib/session-refresh.js.map +1 -1
  234. package/dist/rhf/fields.js +3 -3
  235. package/dist/rhf/fields.js.map +1 -1
  236. package/dist/shell/app-shell.d.ts +25 -1
  237. package/dist/shell/app-shell.js +3 -1
  238. package/dist/shell/app-shell.js.map +1 -1
  239. package/dist/shell/dev-menu.d.ts +3 -0
  240. package/dist/shell/dev-menu.js +4 -2
  241. package/dist/shell/dev-menu.js.map +1 -1
  242. package/dist/shell/held-changes-banner-labels.d.ts +23 -0
  243. package/dist/shell/held-changes-banner-labels.js +14 -0
  244. package/dist/shell/held-changes-banner-labels.js.map +1 -0
  245. package/dist/shell/held-changes-banner.d.ts +75 -0
  246. package/dist/shell/held-changes-banner.js +106 -0
  247. package/dist/shell/held-changes-banner.js.map +1 -0
  248. package/dist/shell/sync-status-indicator.d.ts +27 -12
  249. package/dist/shell/sync-status-indicator.js +13 -4
  250. package/dist/shell/sync-status-indicator.js.map +1 -1
  251. package/dist/shell/top-bar-parts-labels.d.ts +5 -0
  252. package/dist/shell/top-bar-parts-labels.js +3 -1
  253. package/dist/shell/top-bar-parts-labels.js.map +1 -1
  254. package/dist/shell.d.ts +2 -0
  255. package/dist/shell.js +3 -0
  256. package/dist/shell.js.map +1 -1
  257. package/dist/sync/engine.d.ts +11 -2
  258. package/dist/sync/engine.js +13 -5
  259. package/dist/sync/engine.js.map +1 -1
  260. package/dist/sync/hooks.d.ts +3 -2
  261. package/dist/sync/hooks.js +4 -2
  262. package/dist/sync/hooks.js.map +1 -1
  263. package/dist/testing/css-variables.generated.d.ts +2 -2
  264. package/dist/testing/css-variables.generated.js +1 -1
  265. package/dist/testing/css-variables.generated.js.map +1 -1
  266. package/dist/theme/chart-palette.d.ts +18 -0
  267. package/dist/theme/chart-palette.js +17 -1
  268. package/dist/theme/chart-palette.js.map +1 -1
  269. package/dist/theme/contrast-tokens.d.ts +3 -3
  270. package/dist/theme/contrast-tokens.js.map +1 -1
  271. package/dist/theme/contrast.d.ts +4 -4
  272. package/dist/theme/contrast.js.map +1 -1
  273. package/dist/tour/tour-overlay.js +2 -1
  274. package/dist/tour/tour-overlay.js.map +1 -1
  275. package/package.json +5 -9
  276. package/tokens.css +19 -54
  277. package/src/account/account-parts.ts +0 -47
  278. package/src/account/account-settings-labels.ts +0 -240
  279. package/src/account/account-settings.tsx +0 -698
  280. package/src/account/data-export-setting-labels.ts +0 -20
  281. package/src/account/data-export-setting.tsx +0 -200
  282. package/src/account/delete-account-setting-labels.ts +0 -33
  283. package/src/account/delete-account-setting.tsx +0 -342
  284. package/src/account/email-change-setting-labels.ts +0 -31
  285. package/src/account/email-change-setting.tsx +0 -443
  286. package/src/account/passkeys-setting.tsx +0 -445
  287. package/src/account/sessions-setting-labels.ts +0 -24
  288. package/src/account/sessions-setting.tsx +0 -283
  289. package/src/admin/admin-action-confirm-labels.ts +0 -19
  290. package/src/admin/admin-action-confirm.tsx +0 -509
  291. package/src/admin/admin-action-log-labels.ts +0 -83
  292. package/src/admin/admin-action-log.tsx +0 -464
  293. package/src/admin/admin-area-labels.ts +0 -114
  294. package/src/admin/admin-forbidden.tsx +0 -75
  295. package/src/admin/admin-overview.tsx +0 -258
  296. package/src/admin/admin-parts.ts +0 -68
  297. package/src/admin/admin-sections.tsx +0 -208
  298. package/src/admin/admin-tool-links.tsx +0 -90
  299. package/src/admin/invitations-panel-labels.ts +0 -31
  300. package/src/admin/invitations-panel.tsx +0 -516
  301. package/src/admin/mail-test-card-labels.ts +0 -19
  302. package/src/admin/mail-test-card.tsx +0 -189
  303. package/src/admin/plan-change-labels.ts +0 -5
  304. package/src/admin/plan-change.tsx +0 -474
  305. package/src/admin/reviewer-scope-editor-labels.ts +0 -16
  306. package/src/admin/reviewer-scope-editor.tsx +0 -224
  307. package/src/admin/role-select-labels.ts +0 -16
  308. package/src/admin/role-select.tsx +0 -282
  309. package/src/admin/transfer-ownership-dialog-labels.ts +0 -20
  310. package/src/admin/transfer-ownership-dialog.tsx +0 -162
  311. package/src/admin/user-roster-labels.ts +0 -17
  312. package/src/admin/user-roster.tsx +0 -559
  313. package/src/auth/accept-invitation-labels.ts +0 -21
  314. package/src/auth/accept-invitation.tsx +0 -210
  315. package/src/auth/auth-errors-labels.ts +0 -23
  316. package/src/auth/auth-errors.ts +0 -439
  317. package/src/auth/complete-name-dialog-labels.ts +0 -16
  318. package/src/auth/complete-name-dialog.tsx +0 -253
  319. package/src/auth/email-tag.ts +0 -75
  320. package/src/auth/forgot-password-form-labels.ts +0 -20
  321. package/src/auth/forgot-password-form.tsx +0 -221
  322. package/src/auth/form-rules.ts +0 -40
  323. package/src/auth/new-password-fields.tsx +0 -85
  324. package/src/auth/not-found-page-labels.ts +0 -12
  325. package/src/auth/not-found-page.tsx +0 -95
  326. package/src/auth/register-form-labels.ts +0 -31
  327. package/src/auth/register-form.tsx +0 -456
  328. package/src/auth/reset-password-form-labels.ts +0 -27
  329. package/src/auth/reset-password-form.tsx +0 -341
  330. package/src/auth/sign-in-form-labels.ts +0 -44
  331. package/src/auth/sign-in-form.tsx +0 -858
  332. package/src/auth/status-parts.tsx +0 -132
  333. package/src/auth/verify-email-labels.ts +0 -26
  334. package/src/auth/verify-email.tsx +0 -445
  335. package/src/billing/billing-banner.tsx +0 -258
  336. package/src/billing/billing-labels.ts +0 -381
  337. package/src/billing/billing-lock.ts +0 -108
  338. package/src/billing/billing-overview.ts +0 -157
  339. package/src/billing/billing-standing.ts +0 -80
  340. package/src/billing/cancel-request.ts +0 -97
  341. package/src/billing/cancel-subscription.tsx +0 -98
  342. package/src/billing/checkout-processing.ts +0 -394
  343. package/src/billing/checkout-return.ts +0 -90
  344. package/src/billing/pay-page.ts +0 -330
  345. package/src/billing/plan-card.tsx +0 -562
  346. package/src/billing/plan-change-labels.ts +0 -66
  347. package/src/billing/plan-limit-notice.tsx +0 -148
  348. package/src/billing/plan-limit-toast.tsx +0 -90
  349. package/src/billing/plan-limit.ts +0 -52
  350. package/src/billing/plan-price.ts +0 -98
  351. package/src/billing/plan-switch.tsx +0 -216
  352. package/src/billing/provider-page.ts +0 -84
  353. package/src/billing/subscription-actions.tsx +0 -247
  354. package/src/billing/subscription-status-chip.tsx +0 -95
  355. package/src/chart.ts +0 -103
  356. package/src/components/account-chips-labels.ts +0 -16
  357. package/src/components/account-chips.tsx +0 -474
  358. package/src/components/alert-banner.tsx +0 -670
  359. package/src/components/amount-input.tsx +0 -816
  360. package/src/components/appearance-labels.ts +0 -63
  361. package/src/components/authed-image-labels.ts +0 -11
  362. package/src/components/authed-image.tsx +0 -209
  363. package/src/components/autocomplete.tsx +0 -645
  364. package/src/components/breadcrumbs-labels.ts +0 -10
  365. package/src/components/breadcrumbs.tsx +0 -205
  366. package/src/components/bulk-action-bar-labels.ts +0 -11
  367. package/src/components/bulk-action-bar.tsx +0 -443
  368. package/src/components/button-group.tsx +0 -223
  369. package/src/components/button-look.ts +0 -256
  370. package/src/components/button.tsx +0 -339
  371. package/src/components/calculator.tsx +0 -341
  372. package/src/components/calendar-heatmap-labels.ts +0 -15
  373. package/src/components/calendar-heatmap.tsx +0 -600
  374. package/src/components/card.tsx +0 -377
  375. package/src/components/chart-edge.ts +0 -106
  376. package/src/components/chart-figure.tsx +0 -35
  377. package/src/components/chart-height.ts +0 -30
  378. package/src/components/chart-zoom-core.tsx +0 -627
  379. package/src/components/chart-zoom.tsx +0 -22
  380. package/src/components/chart.tsx +0 -554
  381. package/src/components/checkbox-group.tsx +0 -247
  382. package/src/components/checkbox.tsx +0 -346
  383. package/src/components/chip-input.tsx +0 -370
  384. package/src/components/chip-labels.ts +0 -13
  385. package/src/components/chip.tsx +0 -843
  386. package/src/components/choice-card.tsx +0 -544
  387. package/src/components/column-mapper-labels.ts +0 -38
  388. package/src/components/column-mapper.tsx +0 -655
  389. package/src/components/combobox-core.tsx +0 -1321
  390. package/src/components/combobox-suggestions.tsx +0 -131
  391. package/src/components/combobox.tsx +0 -716
  392. package/src/components/confirm-context.ts +0 -7
  393. package/src/components/confirm-dialog-labels.ts +0 -13
  394. package/src/components/confirm-dialog.tsx +0 -341
  395. package/src/components/copy-button-labels.ts +0 -13
  396. package/src/components/copy-button.tsx +0 -192
  397. package/src/components/country-select-labels.ts +0 -11
  398. package/src/components/country-select.tsx +0 -539
  399. package/src/components/currency-option-list.tsx +0 -188
  400. package/src/components/currency-select.tsx +0 -343
  401. package/src/components/danger-confirm-labels.ts +0 -20
  402. package/src/components/danger-confirm.tsx +0 -987
  403. package/src/components/data-table-cells.tsx +0 -118
  404. package/src/components/data-table-column-settings.tsx +0 -198
  405. package/src/components/data-table-filter-popover.tsx +0 -289
  406. package/src/components/data-table-filters.ts +0 -315
  407. package/src/components/data-table-fit.ts +0 -301
  408. package/src/components/data-table-groups.tsx +0 -288
  409. package/src/components/data-table-head.tsx +0 -280
  410. package/src/components/data-table-labels.ts +0 -198
  411. package/src/components/data-table-leading-row.tsx +0 -58
  412. package/src/components/data-table-mobile.tsx +0 -1117
  413. package/src/components/data-table-pagination.tsx +0 -202
  414. package/src/components/data-table-resize.ts +0 -188
  415. package/src/components/data-table-row-actions.tsx +0 -302
  416. package/src/components/data-table-row.tsx +0 -496
  417. package/src/components/data-table-selection.ts +0 -71
  418. package/src/components/data-table-shared.tsx +0 -287
  419. package/src/components/data-table-sort.ts +0 -163
  420. package/src/components/data-table-virtual.ts +0 -576
  421. package/src/components/data-table.tsx +0 -2598
  422. package/src/components/date-field.tsx +0 -684
  423. package/src/components/date-format-setting.tsx +0 -64
  424. package/src/components/date-picker.tsx +0 -404
  425. package/src/components/date-range-picker.tsx +0 -518
  426. package/src/components/description-list-labels.ts +0 -9
  427. package/src/components/description-list.tsx +0 -386
  428. package/src/components/dialog-frame-labels.ts +0 -7
  429. package/src/components/dialog-frame.tsx +0 -284
  430. package/src/components/disclosure.tsx +0 -476
  431. package/src/components/dropdown.tsx +0 -461
  432. package/src/components/empty-state.tsx +0 -136
  433. package/src/components/entity-combobox.tsx +0 -422
  434. package/src/components/error-boundary-labels.ts +0 -20
  435. package/src/components/error-boundary.tsx +0 -675
  436. package/src/components/facing-pair.ts +0 -93
  437. package/src/components/field-base-labels.ts +0 -20
  438. package/src/components/field-base.tsx +0 -656
  439. package/src/components/field-parts.tsx +0 -825
  440. package/src/components/field-strip.tsx +0 -129
  441. package/src/components/field.tsx +0 -206
  442. package/src/components/file-button.tsx +0 -295
  443. package/src/components/file-dropzone.tsx +0 -559
  444. package/src/components/floating-panel-labels.ts +0 -10
  445. package/src/components/floating-panel.tsx +0 -828
  446. package/src/components/focus-ring.ts +0 -16
  447. package/src/components/form-actions-labels.ts +0 -11
  448. package/src/components/form-actions.tsx +0 -578
  449. package/src/components/full-bleed-dialog.tsx +0 -294
  450. package/src/components/grouped-picker.tsx +0 -184
  451. package/src/components/help-popover.tsx +0 -117
  452. package/src/components/hover-menu.tsx +0 -343
  453. package/src/components/iban-input-labels.ts +0 -14
  454. package/src/components/iban-input.tsx +0 -236
  455. package/src/components/icon-button.tsx +0 -959
  456. package/src/components/icon-picker-labels.ts +0 -13
  457. package/src/components/icon-picker.tsx +0 -232
  458. package/src/components/image-grid-labels.ts +0 -11
  459. package/src/components/image-grid.tsx +0 -202
  460. package/src/components/inline-confirm.tsx +0 -116
  461. package/src/components/inline-edit-field-labels.ts +0 -11
  462. package/src/components/inline-edit-field.tsx +0 -552
  463. package/src/components/inline-entity-combobox.tsx +0 -849
  464. package/src/components/input.tsx +0 -394
  465. package/src/components/jump-calendar.tsx +0 -265
  466. package/src/components/language-select.tsx +0 -93
  467. package/src/components/legal-labels.ts +0 -105
  468. package/src/components/legal-page.tsx +0 -711
  469. package/src/components/legal.tsx +0 -668
  470. package/src/components/lightbox-labels.ts +0 -18
  471. package/src/components/lightbox.tsx +0 -399
  472. package/src/components/line-items-labels.ts +0 -14
  473. package/src/components/line-items.tsx +0 -602
  474. package/src/components/list-labels.ts +0 -12
  475. package/src/components/list.tsx +0 -870
  476. package/src/components/loading-state.tsx +0 -78
  477. package/src/components/measured-grid-labels.ts +0 -25
  478. package/src/components/measured-grid.tsx +0 -837
  479. package/src/components/menu-item.tsx +0 -241
  480. package/src/components/mini-calendar-labels.ts +0 -17
  481. package/src/components/mini-calendar.tsx +0 -792
  482. package/src/components/modal.tsx +0 -534
  483. package/src/components/money-draft.ts +0 -83
  484. package/src/components/money-field.tsx +0 -76
  485. package/src/components/month-grid.tsx +0 -229
  486. package/src/components/month-picker-labels.ts +0 -22
  487. package/src/components/month-picker.tsx +0 -1087
  488. package/src/components/multi-entity-combobox.tsx +0 -369
  489. package/src/components/multi-select.tsx +0 -376
  490. package/src/components/nav-pills.tsx +0 -198
  491. package/src/components/number-field.tsx +0 -327
  492. package/src/components/number-input.tsx +0 -559
  493. package/src/components/numeric-field.ts +0 -169
  494. package/src/components/numpad-sheet.tsx +0 -285
  495. package/src/components/one-time-code-input.tsx +0 -270
  496. package/src/components/page-contents-labels.ts +0 -9
  497. package/src/components/page-contents.tsx +0 -282
  498. package/src/components/page-header-context.ts +0 -8
  499. package/src/components/page-header.tsx +0 -298
  500. package/src/components/password-strength-labels.ts +0 -22
  501. package/src/components/password-strength.tsx +0 -241
  502. package/src/components/phone-input-labels.ts +0 -10
  503. package/src/components/phone-input.tsx +0 -282
  504. package/src/components/picker-sheet.tsx +0 -330
  505. package/src/components/pie-chart-labels.ts +0 -40
  506. package/src/components/pie-chart.tsx +0 -480
  507. package/src/components/popover-labels.ts +0 -14
  508. package/src/components/popover.tsx +0 -268
  509. package/src/components/progress-bar-labels.ts +0 -10
  510. package/src/components/progress-bar.tsx +0 -479
  511. package/src/components/qr-code.tsx +0 -76
  512. package/src/components/reauth-dialog-labels.ts +0 -13
  513. package/src/components/reauth-dialog.tsx +0 -265
  514. package/src/components/row-actions-labels.ts +0 -12
  515. package/src/components/row-actions.tsx +0 -795
  516. package/src/components/scroll-area.tsx +0 -123
  517. package/src/components/search-field.tsx +0 -192
  518. package/src/components/select.tsx +0 -294
  519. package/src/components/separator.tsx +0 -33
  520. package/src/components/series-chart-axes.ts +0 -342
  521. package/src/components/series-chart-budget.ts +0 -150
  522. package/src/components/series-chart-labels.ts +0 -33
  523. package/src/components/series-chart-thin.ts +0 -180
  524. package/src/components/series-chart-ticks.ts +0 -194
  525. package/src/components/series-chart.tsx +0 -1906
  526. package/src/components/server-wake-labels.ts +0 -11
  527. package/src/components/server-wake.tsx +0 -129
  528. package/src/components/settings-fields.tsx +0 -237
  529. package/src/components/share-card-labels.ts +0 -30
  530. package/src/components/share-card.tsx +0 -675
  531. package/src/components/signature-pad-labels.ts +0 -24
  532. package/src/components/signature-pad.tsx +0 -747
  533. package/src/components/signed-amount-labels.ts +0 -10
  534. package/src/components/signed-amount.tsx +0 -336
  535. package/src/components/skeleton.tsx +0 -80
  536. package/src/components/slider.tsx +0 -338
  537. package/src/components/sparkline-labels.ts +0 -14
  538. package/src/components/sparkline.tsx +0 -322
  539. package/src/components/spinner.tsx +0 -81
  540. package/src/components/stat-tile-labels.ts +0 -15
  541. package/src/components/stat-tile.tsx +0 -754
  542. package/src/components/status-dot.tsx +0 -170
  543. package/src/components/swatch-picker-labels.ts +0 -10
  544. package/src/components/swatch-picker.tsx +0 -283
  545. package/src/components/swipeable-row.tsx +0 -386
  546. package/src/components/switch.tsx +0 -273
  547. package/src/components/table-skeleton.tsx +0 -136
  548. package/src/components/table.tsx +0 -647
  549. package/src/components/tabs-labels.ts +0 -13
  550. package/src/components/tabs.tsx +0 -693
  551. package/src/components/text-link.tsx +0 -359
  552. package/src/components/text.tsx +0 -150
  553. package/src/components/textarea.tsx +0 -330
  554. package/src/components/tile-radio.tsx +0 -332
  555. package/src/components/time-input.tsx +0 -121
  556. package/src/components/toast-labels.ts +0 -12
  557. package/src/components/toast.tsx +0 -519
  558. package/src/components/toggle-group.tsx +0 -752
  559. package/src/components/toggle-legend.tsx +0 -394
  560. package/src/components/tooltip.tsx +0 -1149
  561. package/src/components/translation-review-editor.tsx +0 -290
  562. package/src/components/translation-review-labels.ts +0 -181
  563. package/src/components/translation-review-parts.tsx +0 -272
  564. package/src/components/translation-review.tsx +0 -1240
  565. package/src/components/tree-view.tsx +0 -589
  566. package/src/components/treemap-labels.ts +0 -47
  567. package/src/components/treemap-layout.ts +0 -210
  568. package/src/components/treemap.tsx +0 -1184
  569. package/src/components/trigger-aria.ts +0 -44
  570. package/src/components/use-anchor-dir.ts +0 -20
  571. package/src/components/use-mobile-reveal.ts +0 -62
  572. package/src/components/use-table-state.ts +0 -753
  573. package/src/components/user-avatar.tsx +0 -116
  574. package/src/components/wizard-stepper.tsx +0 -42
  575. package/src/components/write-lock-labels.ts +0 -10
  576. package/src/components/write-lock.tsx +0 -253
  577. package/src/currencies.ts +0 -90
  578. package/src/data-table.ts +0 -42
  579. package/src/demo/demo-banner.tsx +0 -153
  580. package/src/demo/demo-countdown.ts +0 -136
  581. package/src/demo/demo-ended.tsx +0 -90
  582. package/src/demo/demo-labels.ts +0 -78
  583. package/src/demo/demo-session.ts +0 -32
  584. package/src/demo/demo-start.tsx +0 -156
  585. package/src/eslint/index.ts +0 -161
  586. package/src/eslint/role-utilities.generated.ts +0 -146
  587. package/src/eslint/role-utilities.ts +0 -233
  588. package/src/feedback/feedback-attachment-download.tsx +0 -77
  589. package/src/feedback/feedback-attachment-labels.ts +0 -37
  590. package/src/feedback/feedback-attachment.tsx +0 -1239
  591. package/src/feedback/feedback-capture.ts +0 -287
  592. package/src/feedback/feedback-comment-thread.tsx +0 -353
  593. package/src/feedback/feedback-context-labels.ts +0 -11
  594. package/src/feedback/feedback-context.tsx +0 -245
  595. package/src/feedback/feedback-crash.ts +0 -547
  596. package/src/feedback/feedback-diagnostics.tsx +0 -41
  597. package/src/feedback/feedback-dialog-labels.ts +0 -29
  598. package/src/feedback/feedback-dialog.tsx +0 -452
  599. package/src/feedback/feedback-entry-file.tsx +0 -150
  600. package/src/feedback/feedback-inbox.tsx +0 -552
  601. package/src/feedback/feedback-labels.ts +0 -144
  602. package/src/feedback/feedback-menu-labels.ts +0 -12
  603. package/src/feedback/feedback-menu.tsx +0 -177
  604. package/src/feedback/feedback-mutation.ts +0 -20
  605. package/src/feedback/feedback-page-link.ts +0 -90
  606. package/src/feedback/feedback-public-labels.ts +0 -81
  607. package/src/feedback/feedback-public-list.tsx +0 -524
  608. package/src/feedback/feedback-public-switch.tsx +0 -102
  609. package/src/feedback/feedback-public-types.ts +0 -101
  610. package/src/feedback/feedback-record.ts +0 -428
  611. package/src/feedback/feedback-replies-context.ts +0 -9
  612. package/src/feedback/feedback-replies.tsx +0 -343
  613. package/src/feedback/feedback-rework-entry-chip.tsx +0 -18
  614. package/src/feedback/feedback-row-detail-labels.ts +0 -31
  615. package/src/feedback/feedback-row-detail.tsx +0 -871
  616. package/src/feedback/feedback-status-undo.ts +0 -109
  617. package/src/feedback/feedback-submit-dialog.tsx +0 -131
  618. package/src/feedback/feedback-submit.tsx +0 -270
  619. package/src/feedback/feedback-swipe.tsx +0 -192
  620. package/src/feedback/feedback-table-labels.ts +0 -27
  621. package/src/feedback/feedback-table.tsx +0 -912
  622. package/src/feedback/feedback-thread-labels.ts +0 -22
  623. package/src/feedback/feedback-thread-parts-labels.ts +0 -41
  624. package/src/feedback/feedback-thread.tsx +0 -905
  625. package/src/feedback/feedback-vocabulary.ts +0 -263
  626. package/src/feedback/feedback-vote-button.tsx +0 -124
  627. package/src/feedback.ts +0 -116
  628. package/src/hooks/use-anchored-panel.ts +0 -396
  629. package/src/hooks/use-anchored-rect.ts +0 -77
  630. package/src/hooks/use-announce.ts +0 -85
  631. package/src/hooks/use-authed-src.ts +0 -167
  632. package/src/hooks/use-body-scroll-lock.ts +0 -119
  633. package/src/hooks/use-breakpoint.ts +0 -133
  634. package/src/hooks/use-close-transition.ts +0 -107
  635. package/src/hooks/use-copy-to-clipboard.ts +0 -122
  636. package/src/hooks/use-debounce.ts +0 -114
  637. package/src/hooks/use-dismiss.ts +0 -371
  638. package/src/hooks/use-document-title.ts +0 -33
  639. package/src/hooks/use-file-drop.ts +0 -121
  640. package/src/hooks/use-file-picker-labels.ts +0 -28
  641. package/src/hooks/use-file-picker.tsx +0 -359
  642. package/src/hooks/use-focus-trap.ts +0 -199
  643. package/src/hooks/use-hotkey.ts +0 -212
  644. package/src/hooks/use-large-text.ts +0 -41
  645. package/src/hooks/use-long-press.ts +0 -140
  646. package/src/hooks/use-media-query.ts +0 -39
  647. package/src/hooks/use-mounted.ts +0 -39
  648. package/src/hooks/use-noindex.ts +0 -37
  649. package/src/hooks/use-overlay-history.ts +0 -714
  650. package/src/hooks/use-row-swipe.ts +0 -207
  651. package/src/hooks/use-search-param-state.ts +0 -434
  652. package/src/hooks/use-windowed-rows.ts +0 -225
  653. package/src/i18n/defaults.ts +0 -267
  654. package/src/i18n/german.ts +0 -2059
  655. package/src/i18n/kit-labels.tsx +0 -1003
  656. package/src/i18n/languages.ts +0 -349
  657. package/src/i18n/locales/de-CH.ts +0 -24
  658. package/src/i18n/locales/en.ts +0 -267
  659. package/src/i18n/locales/es.ts +0 -1999
  660. package/src/i18n/locales/fr.ts +0 -2037
  661. package/src/i18n/locales/hu.ts +0 -2004
  662. package/src/i18n/locales/it.ts +0 -2014
  663. package/src/i18n/locales/zh.ts +0 -1912
  664. package/src/i18n/plurals.ts +0 -86
  665. package/src/i18n/review.ts +0 -475
  666. package/src/i18n/shared-labels.ts +0 -138
  667. package/src/i18n/swiss.ts +0 -29
  668. package/src/index.ts +0 -1191
  669. package/src/landing/access.ts +0 -93
  670. package/src/landing/landing-actions.tsx +0 -97
  671. package/src/landing/landing-labels.ts +0 -59
  672. package/src/landing/landing-sections.tsx +0 -341
  673. package/src/landing/page-seo.ts +0 -261
  674. package/src/landing/public-header.tsx +0 -121
  675. package/src/landing/public-page.tsx +0 -106
  676. package/src/landing/routing.tsx +0 -225
  677. package/src/lib/broadcast.ts +0 -52
  678. package/src/lib/calc.ts +0 -323
  679. package/src/lib/chip-edge-snap.ts +0 -212
  680. package/src/lib/clipboard-minus.ts +0 -107
  681. package/src/lib/clipping.ts +0 -34
  682. package/src/lib/cn.ts +0 -28
  683. package/src/lib/column-mapping.ts +0 -234
  684. package/src/lib/countries.ts +0 -91
  685. package/src/lib/data-attributes.ts +0 -14
  686. package/src/lib/date-format.ts +0 -227
  687. package/src/lib/dates.ts +0 -288
  688. package/src/lib/day-format.ts +0 -96
  689. package/src/lib/decimal-marks.ts +0 -108
  690. package/src/lib/describe-failure.ts +0 -18
  691. package/src/lib/device-settings.ts +0 -147
  692. package/src/lib/direction.ts +0 -74
  693. package/src/lib/document-navigation.ts +0 -17
  694. package/src/lib/document-title.ts +0 -41
  695. package/src/lib/download.ts +0 -172
  696. package/src/lib/float-clearance.ts +0 -70
  697. package/src/lib/floating-stack.ts +0 -70
  698. package/src/lib/focus-left.ts +0 -156
  699. package/src/lib/format.ts +0 -585
  700. package/src/lib/http-status.ts +0 -38
  701. package/src/lib/iban.ts +0 -251
  702. package/src/lib/ime.ts +0 -24
  703. package/src/lib/intl-cache.ts +0 -27
  704. package/src/lib/iso-day.ts +0 -13
  705. package/src/lib/lazy-with-fallback.ts +0 -82
  706. package/src/lib/logger.ts +0 -69
  707. package/src/lib/minus.ts +0 -50
  708. package/src/lib/path-pattern.ts +0 -55
  709. package/src/lib/person-name.ts +0 -155
  710. package/src/lib/phone.ts +0 -208
  711. package/src/lib/plain-text.ts +0 -22
  712. package/src/lib/presence.ts +0 -18
  713. package/src/lib/qr-encode.ts +0 -444
  714. package/src/lib/quiet-focus.ts +0 -32
  715. package/src/lib/reduced-motion.ts +0 -22
  716. package/src/lib/refs.ts +0 -22
  717. package/src/lib/round.ts +0 -43
  718. package/src/lib/safe-storage.ts +0 -119
  719. package/src/lib/server-wake.ts +0 -315
  720. package/src/lib/session-refresh.ts +0 -472
  721. package/src/lib/share-format.ts +0 -18
  722. package/src/lib/status-tone.ts +0 -43
  723. package/src/lib/strip-fade.ts +0 -287
  724. package/src/lib/table-text.ts +0 -554
  725. package/src/lib/text-case.ts +0 -19
  726. package/src/lib/text-match.ts +0 -169
  727. package/src/lib/thenable.ts +0 -19
  728. package/src/lib/translation-review.ts +0 -769
  729. package/src/notifications/device-zone-report.ts +0 -67
  730. package/src/notifications/feedback-notification-types-labels.ts +0 -33
  731. package/src/notifications/feedback-notification-types.ts +0 -71
  732. package/src/notifications/notification-bell.tsx +0 -119
  733. package/src/notifications/notification-inbox.tsx +0 -309
  734. package/src/notifications/notification-parts.tsx +0 -81
  735. package/src/notifications/notification-types-setting.tsx +0 -406
  736. package/src/notifications/notification-types.ts +0 -89
  737. package/src/notifications/notifications-labels.ts +0 -287
  738. package/src/notifications/push-device-setting.tsx +0 -230
  739. package/src/notifications/push-devices-setting.tsx +0 -374
  740. package/src/notifications/push-worker.ts +0 -168
  741. package/src/notifications/quiet-hours-setting.tsx +0 -238
  742. package/src/notifications/unsubscribe-panel.tsx +0 -200
  743. package/src/notifications/use-push-device.ts +0 -448
  744. package/src/offline/local-first.ts +0 -100
  745. package/src/offline/pwa-update-prompt-labels.ts +0 -9
  746. package/src/offline/pwa-update-prompt.tsx +0 -78
  747. package/src/offline/query-persistence.ts +0 -391
  748. package/src/offline/service-worker-updates.ts +0 -81
  749. package/src/offline/shared-computer-labels.ts +0 -8
  750. package/src/offline/shared-computer.tsx +0 -87
  751. package/src/offline/sign-out-pending-dialog-labels.ts +0 -21
  752. package/src/offline/sign-out-pending-dialog.tsx +0 -135
  753. package/src/offline.ts +0 -37
  754. package/src/push-worker.ts +0 -25
  755. package/src/rhf/fields.tsx +0 -2187
  756. package/src/rhf/form-core.tsx +0 -324
  757. package/src/rhf/form.tsx +0 -12
  758. package/src/rhf/line-items.tsx +0 -135
  759. package/src/rhf/use-rhf-wizard-step.ts +0 -104
  760. package/src/rhf.ts +0 -21
  761. package/src/search/command-palette-labels.ts +0 -16
  762. package/src/search/command-palette.tsx +0 -624
  763. package/src/search/global-search-labels.ts +0 -13
  764. package/src/search/global-search.tsx +0 -660
  765. package/src/search/search-index.ts +0 -283
  766. package/src/search/use-command-key.ts +0 -23
  767. package/src/search.ts +0 -14
  768. package/src/settings/device-reset-setting-labels.ts +0 -20
  769. package/src/settings/device-reset-setting.tsx +0 -116
  770. package/src/settings/settings-card-classes.ts +0 -11
  771. package/src/settings/settings-catalogue.ts +0 -150
  772. package/src/settings/settings-context.ts +0 -33
  773. package/src/settings/settings-heading.tsx +0 -67
  774. package/src/settings/settings-labels.ts +0 -87
  775. package/src/settings/settings-layout.tsx +0 -471
  776. package/src/settings/settings-section.tsx +0 -108
  777. package/src/settings/use-account-appearance.ts +0 -148
  778. package/src/settings/use-account-language.ts +0 -146
  779. package/src/settings/use-settings-focus.ts +0 -128
  780. package/src/settings/use-settings-route.ts +0 -258
  781. package/src/shell/account-menu-language.tsx +0 -130
  782. package/src/shell/account-menu-text-size.tsx +0 -80
  783. package/src/shell/admin-menu.tsx +0 -183
  784. package/src/shell/app-shell-labels.ts +0 -11
  785. package/src/shell/app-shell.tsx +0 -1560
  786. package/src/shell/auth-layout.tsx +0 -186
  787. package/src/shell/company-switcher-labels.ts +0 -13
  788. package/src/shell/company-switcher.tsx +0 -136
  789. package/src/shell/dev-menu.tsx +0 -257
  790. package/src/shell/fill-height.tsx +0 -45
  791. package/src/shell/language-code.ts +0 -5
  792. package/src/shell/option-switcher-menu.tsx +0 -84
  793. package/src/shell/sync-status-indicator.tsx +0 -303
  794. package/src/shell/top-bar-actions.tsx +0 -87
  795. package/src/shell/top-bar-brand.tsx +0 -78
  796. package/src/shell/top-bar-parts-labels.ts +0 -148
  797. package/src/shell/top-bar.tsx +0 -40
  798. package/src/shell/topbar-action-menu.tsx +0 -567
  799. package/src/shell/topbar-controls.tsx +0 -269
  800. package/src/shell/tour-menu.tsx +0 -187
  801. package/src/shell.ts +0 -108
  802. package/src/sync/db.ts +0 -129
  803. package/src/sync/engine.ts +0 -1011
  804. package/src/sync/hooks.ts +0 -98
  805. package/src/sync/testing.ts +0 -73
  806. package/src/sync/wire.ts +0 -61
  807. package/src/sync.ts +0 -37
  808. package/src/table-text.ts +0 -8
  809. package/src/testing/css-variables.generated.ts +0 -112
  810. package/src/testing/css-variables.ts +0 -189
  811. package/src/testing/index.ts +0 -29
  812. package/src/testing/react.ts +0 -58
  813. package/src/testing/tour.ts +0 -41
  814. package/src/testing/viewport.ts +0 -357
  815. package/src/theme/chart-palette.ts +0 -232
  816. package/src/theme/color.ts +0 -272
  817. package/src/theme/contrast-tokens.ts +0 -268
  818. package/src/theme/contrast.ts +0 -250
  819. package/src/theme/palette-derive.ts +0 -825
  820. package/src/theme/palette-presets.ts +0 -544
  821. package/src/theme/palette-store.ts +0 -108
  822. package/src/theme/text-size.ts +0 -289
  823. package/src/theme/theme-store.ts +0 -157
  824. package/src/tour/tour-catalogue-labels.ts +0 -75
  825. package/src/tour/tour-catalogue.tsx +0 -335
  826. package/src/tour/tour-dock.ts +0 -134
  827. package/src/tour/tour-engine.ts +0 -37
  828. package/src/tour/tour-gates.ts +0 -218
  829. package/src/tour/tour-guide-button.tsx +0 -64
  830. package/src/tour/tour-guide-labels.ts +0 -15
  831. package/src/tour/tour-info.ts +0 -144
  832. package/src/tour/tour-labels.ts +0 -23
  833. package/src/tour/tour-overlay.tsx +0 -1170
  834. package/src/tour/tour.tsx +0 -750
  835. package/src/tour/use-tour-progress.ts +0 -671
  836. package/src/tour/use-tour-url-sync.ts +0 -266
  837. package/src/tour.ts +0 -21
  838. package/src/vite-env.d.ts +0 -23
  839. package/src/wizard/stepper-nav.tsx +0 -360
  840. package/src/wizard/types.ts +0 -275
  841. package/src/wizard/use-wizard.ts +0 -560
  842. package/src/wizard/validation.ts +0 -39
  843. package/src/wizard/wizard-context.tsx +0 -74
  844. package/src/wizard/wizard-labels.ts +0 -29
  845. package/src/wizard/wizard-step.tsx +0 -76
  846. package/src/wizard/wizard-summary.tsx +0 -84
  847. package/src/wizard.ts +0 -15
@@ -1,2187 +0,0 @@
1
- /**
2
- * Bound fields: one line per field of a react-hook-form form. `@eifi1/ui-kit/rhf`.
3
- *
4
- * ```tsx
5
- * <Form {...form}>
6
- * <RhfTextField name="name" label="Name" hint="As on the lease" required
7
- * rules={{ required: "Enter a name" }} />
8
- * <RhfMoneyField name="rent" label="Net rent" currency="CHF" />
9
- * <RhfDateField name="start" label="Start" />
10
- * <RhfSelect name="type" label="Type" options={TYPES} />
11
- * <RhfCombobox name="tenantId" label="Tenant" options={tenants} clearable />
12
- * <RhfInlineEntityCombobox name="accountId" label="Account" options={accounts} />
13
- * <RhfCheckbox name="isDefault" label="Default account" />
14
- * <RhfTimeInput name="meetingTime" label="Time" />
15
- * <RhfDateRangePicker fromName="periodFrom" toName="periodTo" label="Period" />
16
- * <RhfToggleGroup name="interval" label="Interval" options={INTERVALS} />
17
- * <RhfCountrySelect name="country" label="Country" preferred={["CH", "DE"]} />
18
- * <RhfMonthPicker name="fiscalYear" label="Fiscal year" mode="year" valueAsNumber />
19
- * </Form>
20
- * ```
21
- *
22
- * Each folds the `FormField → FormItem → FormLabel → FormControl → field →
23
- * FormDescription → FormMessage` scaffold (see ./form-core) into one element, the way
24
- * kastlan's shared/components/form/fields.tsx does for its forms — with what that file
25
- * left out:
26
- *
27
- * - **The red border.** Every field gets `invalid` from the field state, so an errored
28
- * field PAINTS as well as announces. kastlan's number, money, date and entity fields
29
- * passed only `aria-invalid` through `FormControl`, or nothing, and `Input`, `Select`
30
- * and `Textarea` paint only from the prop (see the note in ./form-core).
31
- * - **Focus on error.** `field.ref` reaches the focusable element itself — the input,
32
- * a picker's trigger, a toggle group whose `focus()` lands on its option (since 0.34
33
- * every kit field takes a ref; the number field and the pickers were reached by an id
34
- * lookup before, API-3) — so `handleSubmit` and `trigger(…, { shouldFocus })` land the
35
- * caret on the first field in error, and `setFocus`'s `shouldSelect` selects.
36
- * - **`control` is optional**: under `<Form {...form}>` the fields find the form
37
- * through react-hook-form's context. Pass it for the field's value types
38
- * (`control={form.control}` checks `name` against the form's values).
39
- *
40
- * The common props: `name`, `control`, `label` (the label above the field;
41
- * omitted, none — then give the field an `aria-label`), `hint` (standing advice under
42
- * the field, attached to it with `aria-describedby`), `required` (the label's mark,
43
- * and `aria-required` where the control takes it — validation is `rules`' or the
44
- * resolver's), `rules`, `disabled` (the control only — the value is still submitted;
45
- * `excludeWhenDisabled` for react-hook-form's drop-the-value semantics), and
46
- * `className` for the item's box.
47
- *
48
- * {@link RhfField} is the shell they are all built on, for a control the kit does not
49
- * ship (an address autocomplete): it takes a `render` and wires the rest.
50
- */
51
- import { useLayoutEffect, useMemo, useRef, type FocusEvent, type ReactElement, type ReactNode } from "react";
52
- import {
53
- useController,
54
- type ControllerFieldState,
55
- type ControllerProps,
56
- type ControllerRenderProps,
57
- type FieldPath,
58
- type FieldValues,
59
- } from "react-hook-form";
60
- import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, useFormField } from "./form-core";
61
- import { Input, type InputProps } from "../components/input";
62
- import { Select, type SelectProps } from "../components/select";
63
- import { Textarea, type TextareaProps } from "../components/textarea";
64
- import { NumberField, type NumberFieldProps } from "../components/number-field";
65
- import { AmountInput } from "../components/amount-input";
66
- import type { MoneyFieldProps } from "../components/money-field";
67
- import { useMoneyDraft } from "../components/money-draft";
68
- import {
69
- DatePicker,
70
- DateRangePicker,
71
- type DatePickerProps,
72
- type DateRangePickerProps,
73
- } from "../components/date-picker";
74
- import { TimeInput, type TimeInputProps } from "../components/time-input";
75
- import { IbanInput, type IbanInputProps } from "../components/iban-input";
76
- import { PhoneInput, type PhoneInputProps } from "../components/phone-input";
77
- import { CountrySelect, type CountrySelectProps } from "../components/country-select";
78
- import { MonthPicker, type MonthPickerProps } from "../components/month-picker";
79
- import {
80
- ToggleGroup,
81
- type ToggleGroupBaseProps,
82
- type ToggleGroupClearableProps,
83
- type ToggleGroupRequiredProps,
84
- } from "../components/toggle-group";
85
- import { Checkbox, type CheckboxProps } from "../components/checkbox";
86
- import { EntityCombobox, type EntityComboboxProps } from "../components/entity-combobox";
87
- import {
88
- Combobox,
89
- InlineEntityCombobox,
90
- type ComboboxProps,
91
- type InlineEntityComboboxProps,
92
- } from "../components/combobox";
93
- import type { ComboClearValue } from "../components/combobox-core";
94
- import { fieldHelpMark, mergeRefs, useInlineHint } from "../components/field-parts";
95
- import { useKitLocale } from "../i18n/kit-labels";
96
- import { cn } from "../lib/cn";
97
- import { decimalMark, readTyped } from "../lib/decimal-marks";
98
- import { hasContent } from "../lib/presence";
99
-
100
- // ── the shell ────────────────────────────────────────────────────────────────
101
-
102
- /** What every bound field takes. */
103
- export interface RhfFieldBaseProps<
104
- TFieldValues extends FieldValues = FieldValues,
105
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
106
- TTransformed = TFieldValues,
107
- > {
108
- name: TName;
109
- /** `form.control`. Optional under `<Form {...form}>`; pass it to type-check `name`. */
110
- control?: ControllerProps<TFieldValues, TName, TTransformed>["control"];
111
- /** react-hook-form's `rules` (`required`, `min`, `validate` …). */
112
- rules?: ControllerProps<TFieldValues, TName, TTransformed>["rules"];
113
- /** The label above the field. Omitted: no label — name the control with
114
- * an `aria-label` instead. */
115
- label?: ReactNode;
116
- /** Standing advice under the field ("As printed on the card"), attached to the
117
- * control with `aria-describedby`. A `FieldHint` is its words at Large and on a
118
- * touch screen (§4, DUP-2), not a lone "?"; a `HelpPopover` stays a "?". Beside a
119
- * `help` it is that caption at every size. */
120
- hint?: ReactNode;
121
- /** Help the field is usable without — why, how to choose — behind the field's one
122
- * "?" after the label: a `HelpPopover` named "About “<label>”" (round 0.36,
123
- * docs/text-size-harmonization.md §4); pass a `HelpPopover` to name it yourself.
124
- * `hint` stays the visible fact under the field. */
125
- help?: ReactNode;
126
- /** Draws the label's required mark and sets `aria-required` where the control
127
- * takes it. Whether the field IS required is `rules`' or the resolver's to say. */
128
- required?: boolean;
129
- /** Disables the control and KEEPS the value: a field locked to a preset (a lease's
130
- * preselected unit, a fixed period) still submits it, and `rules` still run.
131
- * react-hook-form's own Controller `disabled` drops the value from `handleSubmit`'s
132
- * data instead, which a locked preset must never do (kastlan, 0.12). To get that,
133
- * add `excludeWhenDisabled`. A form-wide `useForm({ disabled })` still disables
134
- * every field the RHF way. */
135
- disabled?: boolean;
136
- /** With `disabled`: disable through react-hook-form, so the value is left out of
137
- * the submitted data and validation skips it, like a disabled native input in a
138
- * plain form post. Default `false`. */
139
- excludeWhenDisabled?: boolean;
140
- /** Classes for the item's box (a `grid gap-1`). */
141
- className?: string;
142
- }
143
-
144
- /** What {@link RhfField}'s `render` receives. */
145
- export interface RhfFieldRenderContext<
146
- TFieldValues extends FieldValues = FieldValues,
147
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
148
- > {
149
- field: ControllerRenderProps<TFieldValues, TName>;
150
- fieldState: ControllerFieldState;
151
- /** The field has an error: pass it to the control's `invalid` so it paints. */
152
- invalid: boolean;
153
- /** The error's message, if any. */
154
- error: string | undefined;
155
- /** The control's id, which the label points at. The rendered element has it already
156
- * (unless `asControl` is false); this is for a control that routes it elsewhere. */
157
- id: string;
158
- /** The label's id, for a control that is named with `aria-labelledby`. */
159
- labelId: string;
160
- /** The ids of the rendered hint and message, if any. */
161
- describedBy: string | undefined;
162
- }
163
-
164
- export interface RhfFieldProps<
165
- TFieldValues extends FieldValues = FieldValues,
166
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
167
- TTransformed = TFieldValues,
168
- > extends RhfFieldBaseProps<TFieldValues, TName, TTransformed> {
169
- /** The control. Given the field's `id`, `aria-describedby` and `aria-invalid` unless
170
- * `asControl` is false. */
171
- render: (ctx: RhfFieldRenderContext<TFieldValues, TName>) => ReactElement;
172
- /** `false`: render the control as is, for one that wires its own ids from the
173
- * context. Default `true`. */
174
- asControl?: boolean;
175
- /** `false`: no error message under the field, for a control that renders its own (its
176
- * `error` prop). Default `true`. */
177
- message?: boolean;
178
- /** `false`: no label above the field, for a control that carries its label itself (a
179
- * checkbox). Default `true`. */
180
- showLabel?: boolean;
181
- }
182
-
183
- /** The label, with an id the control can be named by. Inside the item, so it can read
184
- * the item's id. */
185
- function RhfLabel({ required, children }: { required?: boolean; children: ReactNode }) {
186
- const { id } = useFormField();
187
- return (
188
- <FormLabel id={`${id}-label`} required={required}>
189
- {children}
190
- </FormLabel>
191
- );
192
- }
193
-
194
- function ControlSlot<TFieldValues extends FieldValues, TName extends FieldPath<TFieldValues>>({
195
- field,
196
- fieldState,
197
- render,
198
- asControl,
199
- }: {
200
- field: ControllerRenderProps<TFieldValues, TName>;
201
- fieldState: ControllerFieldState;
202
- render: RhfFieldProps<TFieldValues, TName>["render"];
203
- asControl: boolean;
204
- }) {
205
- const { id, formItemId, describedBy } = useFormField();
206
- const element = render({
207
- field,
208
- fieldState,
209
- invalid: fieldState.invalid,
210
- error: fieldState.error?.message,
211
- id: formItemId,
212
- labelId: `${id}-label`,
213
- describedBy,
214
- });
215
- return asControl ? <FormControl>{element}</FormControl> : element;
216
- }
217
-
218
- /**
219
- * The shell every bound field is built on: label, control, hint and message around
220
- * a `render` of your own.
221
- *
222
- * ```tsx
223
- * <RhfField name="address" label="Address" render={({ field, invalid }) => (
224
- * <AddressAutocomplete value={field.value ?? ""} onChange={field.onChange} invalid={invalid} />
225
- * )} />
226
- * ```
227
- */
228
- export function RhfField<
229
- TFieldValues extends FieldValues = FieldValues,
230
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
231
- TTransformed = TFieldValues,
232
- >({
233
- name,
234
- control,
235
- rules,
236
- label,
237
- hint,
238
- help,
239
- required,
240
- disabled,
241
- excludeWhenDisabled,
242
- className,
243
- render,
244
- asControl = true,
245
- message = true,
246
- showLabel = true,
247
- }: RhfFieldProps<TFieldValues, TName, TTransformed>) {
248
- // §4: a FieldHint is a fact, so at Large and on a touch screen it is its words under
249
- // the field, not a lone "?" there (DUP-2, refactor audit 2026-10). A HelpPopover stays
250
- // a "?" at every size. With a `help` (0.36) its "?" follows the label and is the
251
- // field's only one, so a FieldHint is its words at every size.
252
- const helpMark = fieldHelpMark(help, label);
253
- const shownHint = useInlineHint(hint, helpMark !== undefined);
254
- const labelEl = showLabel && hasContent(label) && <RhfLabel required={required}>{label}</RhfLabel>;
255
- return (
256
- <FormField
257
- control={control}
258
- name={name}
259
- rules={rules}
260
- disabled={excludeWhenDisabled ? disabled : undefined}
261
- render={({ field, fieldState }) => (
262
- <FormItem className={className}>
263
- {helpMark === undefined ? (
264
- labelEl
265
- ) : (
266
- <div className="flex items-center gap-1">
267
- {labelEl}
268
- <span className="flex shrink-0 items-center">{helpMark}</span>
269
- </div>
270
- )}
271
- <ControlSlot
272
- field={disabled && !field.disabled ? { ...field, disabled: true } : field}
273
- fieldState={fieldState}
274
- render={render}
275
- asControl={asControl}
276
- />
277
- {hasContent(shownHint) && <FormDescription>{shownHint}</FormDescription>}
278
- {message && <FormMessage />}
279
- </FormItem>
280
- )}
281
- />
282
- );
283
- }
284
-
285
- // ── text ─────────────────────────────────────────────────────────────────────
286
-
287
- type OwnInputProps = Omit<
288
- InputProps,
289
- | "name"
290
- | "value"
291
- | "defaultValue"
292
- | "onChange"
293
- | "onBlur"
294
- | "ref"
295
- | "label"
296
- | "error"
297
- | "invalid"
298
- | "className"
299
- | "disabled"
300
- | "required"
301
- | "inputClassName"
302
- > & {
303
- /**
304
- * Classes for the `<input>` — `className` is the item's box (kastlan 42: a
305
- * `font-mono` IBAN, a `tabular-nums` reference number). {@link RhfTextarea} and
306
- * {@link RhfNumberField} had it; this one only reached `Input`'s prop of the same
307
- * name by accident of the rest-spread, undocumented and one refactor from being
308
- * dropped. Declared, it is part of the contract the three share.
309
- */
310
- inputClassName?: string;
311
- };
312
-
313
- export type RhfTextFieldProps<
314
- TFieldValues extends FieldValues = FieldValues,
315
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
316
- TTransformed = TFieldValues,
317
- > = RhfFieldBaseProps<TFieldValues, TName, TTransformed> & OwnInputProps;
318
-
319
- /** A text {@link Input} — `type` for email, tel, password, url. Stores the string. */
320
- export function RhfTextField<
321
- TFieldValues extends FieldValues = FieldValues,
322
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
323
- TTransformed = TFieldValues,
324
- >({
325
- name,
326
- control,
327
- rules,
328
- label,
329
- hint,
330
- help,
331
- required,
332
- disabled,
333
- excludeWhenDisabled,
334
- className,
335
- ...inputProps
336
- }: RhfTextFieldProps<TFieldValues, TName, TTransformed>) {
337
- return (
338
- <RhfField
339
- {...{ name, control, rules, label, hint, help, required, disabled, excludeWhenDisabled, className }}
340
- render={({ field, invalid }) => (
341
- <Input
342
- aria-required={required || undefined}
343
- {...inputProps}
344
- name={field.name}
345
- ref={field.ref}
346
- value={(field.value ?? "") as string}
347
- onChange={field.onChange}
348
- onBlur={field.onBlur}
349
- disabled={field.disabled}
350
- invalid={invalid}
351
- />
352
- )}
353
- />
354
- );
355
- }
356
-
357
- type OwnTextareaProps = Omit<
358
- TextareaProps,
359
- | "name"
360
- | "value"
361
- | "defaultValue"
362
- | "onChange"
363
- | "onBlur"
364
- | "ref"
365
- | "label"
366
- | "error"
367
- | "invalid"
368
- | "className"
369
- | "disabled"
370
- | "required"
371
- > & {
372
- /** Classes for the `<textarea>`. */
373
- inputClassName?: string;
374
- };
375
-
376
- export type RhfTextareaProps<
377
- TFieldValues extends FieldValues = FieldValues,
378
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
379
- TTransformed = TFieldValues,
380
- > = RhfFieldBaseProps<TFieldValues, TName, TTransformed> & OwnTextareaProps;
381
-
382
- /** A multi-line {@link Textarea}; takes `rows`. Stores the string. */
383
- export function RhfTextarea<
384
- TFieldValues extends FieldValues = FieldValues,
385
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
386
- TTransformed = TFieldValues,
387
- >({
388
- name,
389
- control,
390
- rules,
391
- label,
392
- hint,
393
- help,
394
- required,
395
- disabled,
396
- excludeWhenDisabled,
397
- className,
398
- inputClassName,
399
- ...areaProps
400
- }: RhfTextareaProps<TFieldValues, TName, TTransformed>) {
401
- return (
402
- <RhfField
403
- {...{ name, control, rules, label, hint, help, required, disabled, excludeWhenDisabled, className }}
404
- render={({ field, invalid }) => (
405
- <Textarea
406
- aria-required={required || undefined}
407
- {...areaProps}
408
- className={inputClassName}
409
- name={field.name}
410
- ref={field.ref}
411
- value={(field.value ?? "") as string}
412
- onChange={field.onChange}
413
- onBlur={field.onBlur}
414
- disabled={field.disabled}
415
- invalid={invalid}
416
- />
417
- )}
418
- />
419
- );
420
- }
421
-
422
- // ── numbers ──────────────────────────────────────────────────────────────────
423
-
424
- /**
425
- * A form value → the number field's `number | null`. Defaults hold `""`, `null`, a
426
- * number, or a numeric string: a plain JS spelling ("12.5", "1e-7", what an API sends)
427
- * as it is, anything else the way the field reads typing in `mark` — "1,234.5", or
428
- * "1.234,56" in a comma locale (DUP-9, refactor audit 2026-10: a single
429
- * `replace(",", ".")` read "1,234,567" as NaN, the U-14 pattern).
430
- */
431
- function toNumber(value: unknown, mark: "," | "."): number | null {
432
- if (value === null || value === undefined || value === "") return null;
433
- if (typeof value === "number") return Number.isFinite(value) ? value : null;
434
- const text = String(value).trim();
435
- const plain = text === "" ? NaN : Number(text);
436
- const typed = readTyped(text, mark);
437
- const n = Number.isFinite(plain) ? plain : typed === "" ? NaN : Number(typed);
438
- return Number.isFinite(n) ? n : null;
439
- }
440
-
441
- /** What a twin sets on its field itself — the value and its change, what the form
442
- * decides (`disabled`, `invalid`, the error, the ids) and what the shell draws (the
443
- * label, the hint, the item's `className`). Everything else of the field's passes
444
- * through (DUP-9, refactor audit 2026-10: a `Pick` allow-list dropped what the field
445
- * gained without a word). */
446
- type BoundByForm =
447
- | "value"
448
- | "defaultValue"
449
- | "onChange"
450
- | "onBlur"
451
- | "ref"
452
- | "name"
453
- | "disabled"
454
- | "required"
455
- | "invalid"
456
- | "error"
457
- | "label"
458
- | "hint"
459
- | "className"
460
- | "aria-describedby"
461
- | "aria-invalid"
462
- | "aria-required";
463
-
464
- export type RhfNumberFieldProps<
465
- TFieldValues extends FieldValues = FieldValues,
466
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
467
- TTransformed = TFieldValues,
468
- > = RhfFieldBaseProps<TFieldValues, TName, TTransformed> &
469
- // NumberField's own (the lock since 0.33.1, `onValueChange`, …), less the commit and
470
- // the id the shell owns.
471
- Omit<NumberFieldProps, BoundByForm | "onCommit" | "nullable" | "id"> & {
472
- /** What an emptied field stores. Default `null`; `""` for a schema written
473
- * against a native number input (`z.coerce.number()` over `""`). */
474
- emptyValue?: null | "";
475
- /** `false`: an emptied field snaps back to its last number instead of storing
476
- * `emptyValue`. Default `true`. */
477
- nullable?: boolean;
478
- };
479
-
480
- /**
481
- * The kit's {@link NumberField}: a parsed, rounded (`digits`) and clamped (`min` /
482
- * `max`) number, committed on blur or Enter in the locale's decimal mark. Stores a
483
- * number, or `emptyValue` when emptied.
484
- */
485
- export function RhfNumberField<
486
- TFieldValues extends FieldValues = FieldValues,
487
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
488
- TTransformed = TFieldValues,
489
- >({
490
- name,
491
- control,
492
- rules,
493
- label,
494
- hint,
495
- help,
496
- required,
497
- disabled,
498
- excludeWhenDisabled,
499
- className,
500
- emptyValue = null,
501
- nullable = true,
502
- ...numberProps
503
- }: RhfNumberFieldProps<TFieldValues, TName, TTransformed>) {
504
- return (
505
- <RhfField
506
- {...{ name, control, rules, label, hint, help, required, disabled, excludeWhenDisabled, className }}
507
- render={({ field, invalid, id }) => (
508
- // FormControl clones this element with `id` / `aria-describedby` /
509
- // `aria-invalid`, which NumberControl hands on to the field.
510
- <NumberControl
511
- field={field as unknown as ControllerRenderProps}
512
- focusRef={field.ref}
513
- invalid={invalid}
514
- required={required}
515
- id={id}
516
- emptyValue={emptyValue}
517
- nullable={nullable}
518
- numberProps={numberProps}
519
- />
520
- )}
521
- />
522
- );
523
- }
524
-
525
- function NumberControl({
526
- field,
527
- focusRef,
528
- invalid,
529
- required,
530
- id,
531
- emptyValue,
532
- nullable,
533
- numberProps,
534
- ...aria
535
- }: {
536
- field: ControllerRenderProps;
537
- /** `field.ref`, handed over on its own so the compiler reads `field` as data. */
538
- focusRef: ControllerRenderProps["ref"];
539
- invalid: boolean;
540
- required?: boolean;
541
- id: string;
542
- emptyValue: null | "";
543
- nullable: boolean;
544
- numberProps: Omit<RhfNumberFieldProps, keyof RhfFieldBaseProps | "emptyValue" | "nullable">;
545
- "aria-describedby"?: string;
546
- "aria-invalid"?: NumberFieldProps["aria-invalid"];
547
- }) {
548
- // The field's own mark, for a stored text written the way it is typed.
549
- const mark = decimalMark(useKitLocale(numberProps.locale));
550
- return (
551
- <NumberField
552
- {...numberProps}
553
- // The <input> itself (API-3, refactor audit 2026-10): focus on error, and
554
- // `setFocus`'s `shouldSelect`, which an id-found stand-in had no `select()` for.
555
- ref={focusRef}
556
- aria-describedby={aria["aria-describedby"]}
557
- aria-invalid={aria["aria-invalid"]}
558
- aria-required={required || undefined}
559
- id={id}
560
- value={toNumber(field.value, mark)}
561
- onCommit={(n) => {
562
- field.onChange(n ?? emptyValue);
563
- field.onBlur();
564
- }}
565
- nullable={nullable}
566
- disabled={field.disabled}
567
- invalid={invalid}
568
- />
569
- );
570
- }
571
-
572
- export type RhfIntegerFieldProps<
573
- TFieldValues extends FieldValues = FieldValues,
574
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
575
- TTransformed = TFieldValues,
576
- > = RhfNumberFieldProps<TFieldValues, TName, TTransformed>;
577
-
578
- /**
579
- * {@link RhfNumberField} for a whole number — a floor, a room count, a year, a
580
- * notice period in months. Every such call site in kastlan (item 41) spelled out the
581
- * same three props, `digits={0} calculator={false} emptyValue=""`, and a preset
582
- * written thirty times is one that is eventually written wrong once:
583
- *
584
- * - `digits={0}`: rounds on commit, so "2.5" rooms settles to 3 instead of reaching
585
- * an `Integer` column and failing server-side;
586
- * - `calculator={false}`: a count is typed, not worked out — the trigger is noise
587
- * beside a two-digit field;
588
- * - `emptyValue=""`: kastlan's integer schemas are `z.coerce.number()` over `""`,
589
- * written against the native number inputs these fields replaced.
590
- *
591
- * Each is only a default: pass the prop to override it (`emptyValue={null}` for a
592
- * nullable column). Everything else is {@link RhfNumberField}'s.
593
- */
594
- export function RhfIntegerField<
595
- TFieldValues extends FieldValues = FieldValues,
596
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
597
- TTransformed = TFieldValues,
598
- >(props: RhfIntegerFieldProps<TFieldValues, TName, TTransformed>) {
599
- // `??` / `=== undefined` rather than a spread over the defaults, so a caller
600
- // forwarding its own optional prop (`digits={props.digits}`) still gets the preset,
601
- // while an explicit `emptyValue={null}` keeps its null.
602
- return (
603
- <RhfNumberField
604
- {...props}
605
- digits={props.digits ?? 0}
606
- calculator={props.calculator ?? false}
607
- emptyValue={props.emptyValue === undefined ? "" : props.emptyValue}
608
- />
609
- );
610
- }
611
-
612
- // ── money ────────────────────────────────────────────────────────────────────
613
-
614
- export type RhfMoneyFieldProps<
615
- TFieldValues extends FieldValues = FieldValues,
616
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
617
- TTransformed = TFieldValues,
618
- > = RhfFieldBaseProps<TFieldValues, TName, TTransformed> &
619
- // MoneyField's — AmountInput's, with the lock, `calculator` (keksdose K6), `labels`,
620
- // `currencyNames` and `variant` the hand-written list here had dropped (DUP-9) — less
621
- // what the form binds and the commit, which is the form's store.
622
- Omit<MoneyFieldProps, BoundByForm | "onCommit" | "id"> & {
623
- /**
624
- * What the form stores: `"number"` (default) — `12.5`, or `emptyValue` while the
625
- * field is empty — or `"string"`, the field's own text ("12.50") for a schema that
626
- * parses it itself. A half-typed sum ("12+") leaves the stored number as it was
627
- * until it resolves; a lone "-" left on blur goes back to it, as on MoneyField.
628
- */
629
- valueAs?: "number" | "string";
630
- /** What an emptied field stores when `valueAs` is `"number"`. Default `null`. */
631
- emptyValue?: null | "";
632
- };
633
-
634
- function MoneyControl({
635
- field,
636
- invalid,
637
- required,
638
- id,
639
- describedBy,
640
- props,
641
- }: {
642
- field: ControllerRenderProps;
643
- invalid: boolean;
644
- required?: boolean;
645
- id: string;
646
- describedBy: string | undefined;
647
- props: Omit<RhfMoneyFieldProps, keyof RhfFieldBaseProps>;
648
- }) {
649
- const { valueAs = "number", emptyValue = null, ...amountProps } = props;
650
- // MoneyField's draft and settle (DUP-9): the text being typed, re-read from the form
651
- // only when its value moves away from what the draft says, and a lone "-" back on the
652
- // stored amount when the figure settles.
653
- const money = useMoneyDraft(field.value, valueAs);
654
- const input = useRef<HTMLInputElement | null>(null);
655
- // AmountInput reports no blur, and react-hook-form needs one to mark the field
656
- // touched: listen on its <input>.
657
- const onBlur = field.onBlur;
658
- useLayoutEffect(() => {
659
- const el = input.current;
660
- if (!el) return;
661
- el.addEventListener("blur", onBlur);
662
- return () => el.removeEventListener("blur", onBlur);
663
- }, [onBlur]);
664
- return (
665
- <div>
666
- <AmountInput
667
- {...amountProps}
668
- id={id}
669
- aria-describedby={describedBy}
670
- aria-required={required || undefined}
671
- ref={(el) => {
672
- input.current = el;
673
- field.ref(el);
674
- }}
675
- value={money.draft}
676
- disabled={field.disabled}
677
- invalid={invalid}
678
- onChange={(text) => {
679
- const parsed = money.change(text);
680
- if (valueAs === "string") {
681
- field.onChange(text);
682
- return;
683
- }
684
- if (parsed === undefined) return;
685
- field.onChange(parsed ?? emptyValue);
686
- }}
687
- onCommit={money.settle}
688
- />
689
- </div>
690
- );
691
- }
692
-
693
- /**
694
- * The kit's {@link AmountInput}: a money figure with its currency chip and the
695
- * calculator ("1200+80" resolves on blur or Enter). Stores a number by default — see
696
- * `valueAs`.
697
- */
698
- export function RhfMoneyField<
699
- TFieldValues extends FieldValues = FieldValues,
700
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
701
- TTransformed = TFieldValues,
702
- >({
703
- name,
704
- control,
705
- rules,
706
- label,
707
- hint,
708
- help,
709
- required,
710
- disabled,
711
- excludeWhenDisabled,
712
- className,
713
- ...amountProps
714
- }: RhfMoneyFieldProps<TFieldValues, TName, TTransformed>) {
715
- return (
716
- <RhfField
717
- {...{ name, control, rules, label, hint, help, required, disabled, excludeWhenDisabled, className }}
718
- asControl={false}
719
- render={({ field, invalid, id, describedBy }) => (
720
- <MoneyControl
721
- field={field as unknown as ControllerRenderProps}
722
- invalid={invalid}
723
- required={required}
724
- id={id}
725
- describedBy={describedBy}
726
- props={amountProps}
727
- />
728
- )}
729
- />
730
- );
731
- }
732
-
733
- // ── date ─────────────────────────────────────────────────────────────────────
734
-
735
- export type RhfDateFieldProps<
736
- TFieldValues extends FieldValues = FieldValues,
737
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
738
- TTransformed = TFieldValues,
739
- > = RhfFieldBaseProps<TFieldValues, TName, TTransformed> &
740
- // `error`: the form's message is shown by the binding (FormMessage); the picker's own
741
- // `error` (0.22) would show it a second time.
742
- Omit<DatePickerProps, "value" | "onChange" | "label" | "invalid" | "disabled" | "className" | "id" | "error"> & {
743
- /** Classes for the picker. Default `w-full`. */
744
- inputClassName?: string;
745
- /** What a cleared date stores. Default `""`, the empty ISO string; `null` for a
746
- * nullable schema. */
747
- emptyValue?: "" | null;
748
- };
749
-
750
- function DateControl({
751
- field,
752
- focusRef,
753
- invalid,
754
- required,
755
- id,
756
- emptyValue,
757
- inputClassName,
758
- pickerProps,
759
- ...aria
760
- }: {
761
- field: ControllerRenderProps;
762
- /** `field.ref`, handed over on its own so the compiler reads `field` as data. */
763
- focusRef: ControllerRenderProps["ref"];
764
- invalid: boolean;
765
- required?: boolean;
766
- id: string;
767
- emptyValue: "" | null;
768
- inputClassName?: string;
769
- pickerProps: Omit<DatePickerProps, "value" | "onChange" | "label" | "invalid" | "disabled" | "className" | "id">;
770
- "aria-describedby"?: string;
771
- "aria-invalid"?: DatePickerProps["aria-invalid"];
772
- }) {
773
- const value: unknown = field.value;
774
- return (
775
- <DatePicker
776
- {...pickerProps}
777
- {...aria}
778
- // The trigger itself (API-3, refactor audit 2026-10), not an id lookup.
779
- ref={focusRef}
780
- aria-required={required || undefined}
781
- id={id}
782
- value={typeof value === "string" ? value : ""}
783
- onChange={(iso) => {
784
- field.onChange(iso === "" ? emptyValue : iso);
785
- field.onBlur();
786
- }}
787
- disabled={field.disabled}
788
- invalid={invalid}
789
- className={inputClassName ?? "w-full"}
790
- />
791
- );
792
- }
793
-
794
- /**
795
- * The kit's {@link DatePicker}, holding an ISO `"YYYY-MM-DD"` string. `commit` and
796
- * `disabledReason` are the picker's own (0.52, refactor audit 2026-10-30, API-3): a date
797
- * that saves itself stays focusable under a lock and says why.
798
- */
799
- export function RhfDateField<
800
- TFieldValues extends FieldValues = FieldValues,
801
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
802
- TTransformed = TFieldValues,
803
- >({
804
- name,
805
- control,
806
- rules,
807
- label,
808
- hint,
809
- help,
810
- required,
811
- disabled,
812
- excludeWhenDisabled,
813
- className,
814
- inputClassName,
815
- emptyValue = "",
816
- ...pickerProps
817
- }: RhfDateFieldProps<TFieldValues, TName, TTransformed>) {
818
- return (
819
- <RhfField
820
- {...{ name, control, rules, label, hint, help, required, disabled, excludeWhenDisabled, className }}
821
- render={({ field, invalid, id }) => (
822
- // FormControl clones this element with `aria-describedby` / `aria-invalid`,
823
- // which DateControl hands on to the picker's trigger.
824
- <DateControl
825
- field={field as unknown as ControllerRenderProps}
826
- focusRef={field.ref}
827
- invalid={invalid}
828
- required={required}
829
- id={id}
830
- emptyValue={emptyValue}
831
- inputClassName={inputClassName}
832
- pickerProps={pickerProps}
833
- />
834
- )}
835
- />
836
- );
837
- }
838
-
839
- // ── date range ───────────────────────────────────────────────────────────────
840
-
841
- type OwnDateRangeProps = Omit<
842
- DateRangePickerProps,
843
- "from" | "to" | "onChange" | "label" | "invalid" | "disabled" | "className" | "id" | "error"
844
- >;
845
-
846
- export type RhfDateRangePickerProps<
847
- TFieldValues extends FieldValues = FieldValues,
848
- TFromName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
849
- TToName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
850
- TTransformed = TFieldValues,
851
- > = Omit<RhfFieldBaseProps<TFieldValues, TFromName, TTransformed>, "name" | "rules"> &
852
- OwnDateRangeProps & {
853
- /** The form field holding the range's first day, an ISO `"YYYY-MM-DD"`. */
854
- fromName: TFromName;
855
- /** The form field holding the range's last day. */
856
- toName: TToName;
857
- /** react-hook-form's `rules` for the start (`required`, a `validate` …). */
858
- fromRules?: ControllerProps<TFieldValues, TFromName, TTransformed>["rules"];
859
- /** react-hook-form's `rules` for the end — the place for "not before the start",
860
- * which can read the start with `getValues`: the start is always written first. */
861
- toRules?: ControllerProps<TFieldValues, TToName, TTransformed>["rules"];
862
- /** Classes for the picker. Default `w-full`. */
863
- inputClassName?: string;
864
- /** What an empty end stores — a clear, or the half-made range between the first
865
- * and second click. Default `""`, the empty ISO string; `null` for a nullable
866
- * schema. */
867
- emptyValue?: "" | null;
868
- };
869
-
870
- /**
871
- * The kit's {@link DateRangePicker} over TWO form fields — `fromName` and `toName` —
872
- * the way a period is stored: as two columns, each its own schema key, not one
873
- * `{ from, to }` object (kastlan 4).
874
- *
875
- * ```tsx
876
- * <RhfDateRangePicker fromName="periodFrom" toName="periodTo" label="Billing period"
877
- * presets={presets} clearable />
878
- * ```
879
- *
880
- * Why it is not {@link RhfField}: a `Controller` binds one name, and the range is
881
- * two. Built by hand — an `RhfField` on the start and a second `useController` for
882
- * the end, which is what kastlan's period fields needed — it works until the END is
883
- * the one in error: the shell's label, message and `aria-invalid` all read the
884
- * start's state alone, so "End before start" (a `refine` with `path: ["periodTo"]`,
885
- * where such a rule goes) never shows. Here both states are read:
886
- *
887
- * - **Either field's error is shown and paints.** The label turns, the trigger is
888
- * `aria-invalid` and wears the danger border when EITHER field has an error, and
889
- * both messages are listed under the field (once, if the two say the same thing),
890
- * each pointed at by the trigger's `aria-describedby`.
891
- * - **One change writes both.** The picker reports the range whole, so the start and
892
- * then the end are written on every change — the end too when only the start
893
- * moved, so a cross-field rule on the end re-runs and an "End before start" clears
894
- * the moment the start is fixed. A clear (`clearable`'s ×) empties both.
895
- * - **Focus on error** lands on the trigger whichever of the two is in error.
896
- * - **Touched** once a range is complete or cleared — not on the first click of a
897
- * pick, which would mark (and, with `mode: "onTouched"`, validate) an end the user
898
- * is still choosing.
899
- *
900
- * Without `apply` (the picker's default) the first calendar click is reported on its
901
- * own, so the form holds `(from, emptyValue)` until the second; with `apply` nothing
902
- * reaches the form until Apply. `apply` was `commit="apply"` until 0.52 (gone in 0.53):
903
- * `commit` is now the write lock's, as on every other binding — with
904
- * `disabledReason`, the picker's own (refactor audit 2026-10-30, API-3).
905
- *
906
- * The common props (`label`, `hint`, `required`, `disabled`, `excludeWhenDisabled`,
907
- * `control`, `className`) apply to the pair; `rules` are per field.
908
- */
909
- export function RhfDateRangePicker<
910
- TFieldValues extends FieldValues = FieldValues,
911
- TFromName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
912
- TToName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
913
- TTransformed = TFieldValues,
914
- >({
915
- fromName,
916
- toName,
917
- control,
918
- fromRules,
919
- toRules,
920
- label,
921
- hint,
922
- help,
923
- required,
924
- disabled,
925
- excludeWhenDisabled,
926
- className,
927
- inputClassName,
928
- emptyValue = "",
929
- ...pickerProps
930
- }: RhfDateRangePickerProps<TFieldValues, TFromName, TToName, TTransformed>) {
931
- const rhfDisabled = excludeWhenDisabled ? disabled : undefined;
932
- // The end, read here; the start is the FormField below, which gives the label, hint
933
- // and ids their context. This component re-renders on the end's state, and with it
934
- // the FormField's render.
935
- const to = useController<TFieldValues, TToName, TTransformed>({
936
- name: toName,
937
- control,
938
- rules: toRules,
939
- disabled: rhfDisabled,
940
- });
941
- return (
942
- <FormField
943
- control={control}
944
- name={fromName}
945
- rules={fromRules}
946
- disabled={rhfDisabled}
947
- render={({ field, fieldState }) => (
948
- <FormItem className={className}>
949
- <RangeBody
950
- from={field as unknown as ControllerRenderProps}
951
- fromState={fieldState}
952
- to={to.field as unknown as ControllerRenderProps}
953
- toState={to.fieldState}
954
- label={label}
955
- hint={hint}
956
- help={help}
957
- required={required}
958
- disabled={Boolean(disabled)}
959
- emptyValue={emptyValue}
960
- inputClassName={inputClassName}
961
- pickerProps={pickerProps}
962
- />
963
- </FormItem>
964
- )}
965
- />
966
- );
967
- }
968
-
969
- function RangeBody({
970
- from,
971
- fromState,
972
- to,
973
- toState,
974
- label,
975
- hint,
976
- help,
977
- required,
978
- disabled,
979
- emptyValue,
980
- inputClassName,
981
- pickerProps,
982
- }: {
983
- from: ControllerRenderProps;
984
- fromState: ControllerFieldState;
985
- to: ControllerRenderProps;
986
- toState: ControllerFieldState;
987
- label: ReactNode;
988
- hint: ReactNode;
989
- help: ReactNode;
990
- required?: boolean;
991
- disabled: boolean;
992
- emptyValue: "" | null;
993
- inputClassName?: string;
994
- pickerProps: OwnDateRangeProps;
995
- }) {
996
- const { id, formItemId, formMessageId, describedBy } = useFormField();
997
- // RhfField's rule: a FieldHint is its words at Large and on touch (DUP-2) — and at
998
- // every size beside a `help`, whose "?" follows the label (0.36).
999
- const helpMark = fieldHelpMark(help, label);
1000
- const shownHint = useInlineHint(hint, helpMark !== undefined);
1001
- // Both fields' refs go to the one trigger (API-3, refactor audit 2026-10), so
1002
- // `shouldFocusError` lands there whichever of the two is first in error.
1003
- const triggerRef = useMemo(() => mergeRefs(from.ref, to.ref), [from.ref, to.ref]);
1004
- const invalid = fromState.invalid || toState.invalid;
1005
- const messages = [fromState.error?.message, toState.error?.message].filter(
1006
- (m, i, all): m is string => typeof m === "string" && m !== "" && all.indexOf(m) === i,
1007
- );
1008
- const messageIds = messages.map((_, i) => `${formMessageId}-${i}`);
1009
- const fromValue: unknown = from.value;
1010
- const toValue: unknown = to.value;
1011
- const labelEl = hasContent(label) && (
1012
- // FormLabel colours from the START's error alone (its context is that field); the
1013
- // pair's state says it here.
1014
- <FormLabel id={`${id}-label`} required={required} data-error={invalid || undefined}>
1015
- {label}
1016
- </FormLabel>
1017
- );
1018
- return (
1019
- <>
1020
- {helpMark === undefined ? (
1021
- labelEl
1022
- ) : (
1023
- <div className="flex items-center gap-1">
1024
- {labelEl}
1025
- <span className="flex shrink-0 items-center">{helpMark}</span>
1026
- </div>
1027
- )}
1028
- <DateRangePicker
1029
- {...pickerProps}
1030
- ref={triggerRef}
1031
- id={formItemId}
1032
- // Merged, never replaced — a caller's own reference first, as FormControl does.
1033
- aria-describedby={
1034
- [pickerProps["aria-describedby"], describedBy, ...messageIds].filter(Boolean).join(" ") || undefined
1035
- }
1036
- aria-invalid={invalid || undefined}
1037
- aria-required={required || undefined}
1038
- from={typeof fromValue === "string" ? fromValue : ""}
1039
- to={typeof toValue === "string" ? toValue : ""}
1040
- onChange={(f, t) => {
1041
- from.onChange(f === "" ? emptyValue : f);
1042
- to.onChange(t === "" ? emptyValue : t);
1043
- if (t !== "" || f === "") {
1044
- from.onBlur();
1045
- to.onBlur();
1046
- }
1047
- }}
1048
- disabled={disabled || from.disabled || to.disabled}
1049
- invalid={invalid}
1050
- className={inputClassName ?? "w-full"}
1051
- />
1052
- {hasContent(shownHint) && <FormDescription>{shownHint}</FormDescription>}
1053
- {messages.map((message, i) => (
1054
- // FormMessage's look and its rule: no `role="alert"` — read when focus reaches
1055
- // the trigger, which names these in its description.
1056
- <p key={message} id={messageIds[i]} data-slot="form-message" className="text-caption leading-tight text-danger">
1057
- {message}
1058
- </p>
1059
- ))}
1060
- </>
1061
- );
1062
- }
1063
-
1064
- // ── time ─────────────────────────────────────────────────────────────────────
1065
-
1066
- export type RhfTimeInputProps<
1067
- TFieldValues extends FieldValues = FieldValues,
1068
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
1069
- TTransformed = TFieldValues,
1070
- > = RhfFieldBaseProps<TFieldValues, TName, TTransformed> &
1071
- Omit<
1072
- TimeInputProps,
1073
- | "name"
1074
- | "value"
1075
- | "defaultValue"
1076
- | "onChange"
1077
- | "onValueChange"
1078
- | "onBlur"
1079
- | "label"
1080
- | "error"
1081
- | "invalid"
1082
- | "className"
1083
- | "disabled"
1084
- | "required"
1085
- > & {
1086
- /** What a cleared time stores. Default `""`; `null` for a nullable schema. */
1087
- emptyValue?: "" | null;
1088
- };
1089
-
1090
- /**
1091
- * The kit's {@link TimeInput}: a time of day as `"HH:mm"` (`"HH:mm:ss"` when `step`
1092
- * asks for seconds), normalised before it reaches the form, so the stored value
1093
- * compares and sorts as a string. kastlan 4: the owner meeting's time was an
1094
- * {@link RhfField} with a hand-wired `TimeInput` in its render — `ref`, `name`, the
1095
- * `?? ""`, `onValueChange`, `onBlur`, `invalid` — the seven lines every bound field
1096
- * folds into one.
1097
- *
1098
- * `min` / `max` paint an out-of-window time (see TimeInput) but do not fail the form:
1099
- * validation stays the schema's or `rules`'. `rules={{ validate: (v) =>
1100
- * isTimeInRange(v, min, max) || "…" }}` says it with the same window.
1101
- */
1102
- export function RhfTimeInput<
1103
- TFieldValues extends FieldValues = FieldValues,
1104
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
1105
- TTransformed = TFieldValues,
1106
- >({
1107
- name,
1108
- control,
1109
- rules,
1110
- label,
1111
- hint,
1112
- help,
1113
- required,
1114
- disabled,
1115
- excludeWhenDisabled,
1116
- className,
1117
- emptyValue = "",
1118
- ...timeProps
1119
- }: RhfTimeInputProps<TFieldValues, TName, TTransformed>) {
1120
- return (
1121
- <RhfField
1122
- {...{ name, control, rules, label, hint, help, required, disabled, excludeWhenDisabled, className }}
1123
- render={({ field, invalid }) => {
1124
- const value: unknown = field.value;
1125
- return (
1126
- <TimeInput
1127
- aria-required={required || undefined}
1128
- {...timeProps}
1129
- name={field.name}
1130
- ref={field.ref}
1131
- value={typeof value === "string" ? value : ""}
1132
- onValueChange={(v) => field.onChange(v === "" ? emptyValue : v)}
1133
- onBlur={field.onBlur}
1134
- disabled={field.disabled}
1135
- invalid={invalid}
1136
- />
1137
- );
1138
- }}
1139
- />
1140
- );
1141
- }
1142
-
1143
- // ── IBAN and phone (0.22) ────────────────────────────────────────────────────
1144
-
1145
- type OwnTextishProps<P> = Omit<
1146
- P,
1147
- | "name"
1148
- | "value"
1149
- | "defaultValue"
1150
- | "onChange"
1151
- | "onValueChange"
1152
- | "onBlur"
1153
- | "label"
1154
- | "error"
1155
- | "invalid"
1156
- | "className"
1157
- | "disabled"
1158
- | "required"
1159
- >;
1160
-
1161
- export type RhfIbanInputProps<
1162
- TFieldValues extends FieldValues = FieldValues,
1163
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
1164
- TTransformed = TFieldValues,
1165
- > = RhfFieldBaseProps<TFieldValues, TName, TTransformed> & OwnTextishProps<IbanInputProps>;
1166
-
1167
- /**
1168
- * {@link IbanInput} bound to one field, which stores the COMPACT upper-case IBAN (what
1169
- * kastlan's server checks and its QR bill prints). The field's own checksum and
1170
- * `kind` messages show while the form has nothing to say; a form error (the schema's
1171
- * or `rules`') replaces them, as a caller's `error` does on the bare field. kastlan
1172
- * asked for the field; it binds everything through react-hook-form.
1173
- */
1174
- export function RhfIbanInput<
1175
- TFieldValues extends FieldValues = FieldValues,
1176
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
1177
- TTransformed = TFieldValues,
1178
- >({
1179
- name,
1180
- control,
1181
- rules,
1182
- label,
1183
- hint,
1184
- help,
1185
- required,
1186
- disabled,
1187
- excludeWhenDisabled,
1188
- className,
1189
- ...ibanProps
1190
- }: RhfIbanInputProps<TFieldValues, TName, TTransformed>) {
1191
- return (
1192
- <RhfField
1193
- {...{ name, control, rules, label, hint, help, required, disabled, excludeWhenDisabled, className }}
1194
- render={({ field, invalid }) => {
1195
- const value: unknown = field.value;
1196
- return (
1197
- <IbanInput
1198
- aria-required={required || undefined}
1199
- {...ibanProps}
1200
- name={field.name}
1201
- ref={field.ref}
1202
- value={typeof value === "string" ? value : ""}
1203
- onValueChange={field.onChange}
1204
- onBlur={field.onBlur}
1205
- disabled={field.disabled}
1206
- invalid={invalid}
1207
- />
1208
- );
1209
- }}
1210
- />
1211
- );
1212
- }
1213
-
1214
- export type RhfPhoneInputProps<
1215
- TFieldValues extends FieldValues = FieldValues,
1216
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
1217
- TTransformed = TFieldValues,
1218
- > = RhfFieldBaseProps<TFieldValues, TName, TTransformed> & OwnTextishProps<PhoneInputProps>;
1219
-
1220
- /**
1221
- * {@link PhoneInput} bound to one field, which stores E.164 when the number reads as
1222
- * one and the typed text unchanged otherwise — kastlan's contacts keep their old free
1223
- * text until someone edits it. Validation (required, a pattern for E.164 only) is the
1224
- * schema's or `rules`'.
1225
- */
1226
- export function RhfPhoneInput<
1227
- TFieldValues extends FieldValues = FieldValues,
1228
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
1229
- TTransformed = TFieldValues,
1230
- >({
1231
- name,
1232
- control,
1233
- rules,
1234
- label,
1235
- hint,
1236
- help,
1237
- required,
1238
- disabled,
1239
- excludeWhenDisabled,
1240
- className,
1241
- ...phoneProps
1242
- }: RhfPhoneInputProps<TFieldValues, TName, TTransformed>) {
1243
- return (
1244
- <RhfField
1245
- {...{ name, control, rules, label, hint, help, required, disabled, excludeWhenDisabled, className }}
1246
- render={({ field, invalid }) => {
1247
- const value: unknown = field.value;
1248
- return (
1249
- <PhoneInput
1250
- aria-required={required || undefined}
1251
- {...phoneProps}
1252
- name={field.name}
1253
- ref={field.ref}
1254
- value={typeof value === "string" ? value : ""}
1255
- onValueChange={field.onChange}
1256
- onBlur={field.onBlur}
1257
- disabled={field.disabled}
1258
- invalid={invalid}
1259
- />
1260
- );
1261
- }}
1262
- />
1263
- );
1264
- }
1265
-
1266
- // ── country and month (0.23) ─────────────────────────────────────────────────
1267
-
1268
- type OwnCountrySelectProps = Omit<
1269
- CountrySelectProps<boolean>,
1270
- | "value"
1271
- | "onChange"
1272
- | "onBlur"
1273
- | "label"
1274
- | "hint"
1275
- | "error"
1276
- | "invalid"
1277
- | "disabled"
1278
- | "className"
1279
- | "id"
1280
- | "ref"
1281
- | "clearable"
1282
- >;
1283
-
1284
- export type RhfCountrySelectProps<
1285
- TFieldValues extends FieldValues = FieldValues,
1286
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
1287
- TTransformed = TFieldValues,
1288
- > = RhfFieldBaseProps<TFieldValues, TName, TTransformed> &
1289
- OwnCountrySelectProps & {
1290
- /** Offer the picker's clear "×" (and Delete on the trigger) while a country is
1291
- * chosen. A clear stores `clearValue`. */
1292
- clearable?: boolean;
1293
- /** What a clear stores. Default `null`; `""` for a schema that spells "no
1294
- * country" as an empty string (a zod `z.string().max(2)`, kastlan's). The picker
1295
- * reads either back as empty. */
1296
- clearValue?: ComboClearValue;
1297
- /** Classes for the picker — `className` is the item's box. */
1298
- selectClassName?: string;
1299
- };
1300
-
1301
- /**
1302
- * {@link CountrySelect} bound to one field, which stores the ISO 3166-1 alpha-2 code
1303
- * ("CH", always upper-case), or `clearValue` once cleared (`clearable`). kastlan asked
1304
- * for it (0.22 adoption): its address form wired a `CountrySelect` through
1305
- * {@link RhfField}'s render, and with no ref on the picker react-hook-form's
1306
- * focus-on-error had nothing to focus — the one field on the form a failed submit
1307
- * could not take you to.
1308
- *
1309
- * Bound the way {@link RhfIbanInput} and {@link RhfPhoneInput} are: `field.ref` reaches
1310
- * the trigger (the picker's `ref`, 0.23), the form's error paints the trigger and is
1311
- * the shell's message under it, the hint is the shell's description — so neither is
1312
- * ever handed to the picker, and its box never changes when one comes or goes. The
1313
- * form's label names the trigger together with the chosen country, and is the phone
1314
- * sheet's title. `commit` and `disabledReason` are the picker's own: a country that
1315
- * saves itself stays focusable under a lock and says why.
1316
- *
1317
- * ```tsx
1318
- * <RhfCountrySelect name="country" label="Country" required
1319
- * preferred={["CH", "LI", "DE", "AT", "FR", "IT"]} rules={{ required: "Choose a country" }} />
1320
- * ```
1321
- */
1322
- export function RhfCountrySelect<
1323
- TFieldValues extends FieldValues = FieldValues,
1324
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
1325
- TTransformed = TFieldValues,
1326
- >({
1327
- name,
1328
- control,
1329
- rules,
1330
- label,
1331
- hint,
1332
- help,
1333
- required,
1334
- disabled,
1335
- excludeWhenDisabled,
1336
- className,
1337
- clearable,
1338
- clearValue = null,
1339
- selectClassName,
1340
- labels,
1341
- ...countryProps
1342
- }: RhfCountrySelectProps<TFieldValues, TName, TTransformed>) {
1343
- const labelled = hasContent(label);
1344
- // A string label is also what the field IS — the picker's `country` word, which
1345
- // titles the phone sheet. A caller's own `labels.country` still wins.
1346
- const ownLabels = typeof label === "string" ? { country: label, ...labels } : labels;
1347
- return (
1348
- <RhfField
1349
- {...{ name, control, rules, label, hint, help, required, disabled, excludeWhenDisabled, className }}
1350
- render={({ field, invalid, labelId }) => {
1351
- const value: unknown = field.value;
1352
- return (
1353
- // FormControl clones this element with `id` / `aria-describedby` /
1354
- // `aria-invalid`, which the picker routes to its trigger.
1355
- <CountrySelect<boolean>
1356
- aria-required={required || undefined}
1357
- // The form's label, then the country (see the picker's `aria-labelledby`).
1358
- aria-labelledby={labelled ? labelId : undefined}
1359
- {...countryProps}
1360
- labels={ownLabels}
1361
- className={selectClassName}
1362
- ref={field.ref}
1363
- value={typeof value === "string" ? value : null}
1364
- clearable={clearable}
1365
- onChange={(code) => field.onChange(code ?? clearValue)}
1366
- // Focus leaving the picker (the wrapper's focusout) marks the field touched.
1367
- onBlur={field.onBlur}
1368
- disabled={field.disabled}
1369
- invalid={invalid}
1370
- />
1371
- );
1372
- }}
1373
- />
1374
- );
1375
- }
1376
-
1377
- type OwnMonthPickerProps = Omit<
1378
- MonthPickerProps,
1379
- "value" | "onChange" | "onBlur" | "label" | "hint" | "error" | "invalid" | "disabled" | "className" | "id" | "mode"
1380
- >;
1381
-
1382
- /** `mode` picks what the form stores, and `valueAsNumber` is only offered for a year. */
1383
- type RhfMonthPickerMode =
1384
- | {
1385
- /** A month, stored as `"YYYY-MM"` (the default). */
1386
- mode?: "month";
1387
- valueAsNumber?: undefined;
1388
- }
1389
- | {
1390
- /** A year, stored as `"YYYY"` — or as a number with `valueAsNumber`. */
1391
- mode: "year";
1392
- /** Store the year as a number (`2026`) rather than `"2026"` — kastlan's budget
1393
- * `fiscal_year` is an integer column. A number in the form reads back as its
1394
- * year. */
1395
- valueAsNumber?: boolean;
1396
- };
1397
-
1398
- export type RhfMonthPickerProps<
1399
- TFieldValues extends FieldValues = FieldValues,
1400
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
1401
- TTransformed = TFieldValues,
1402
- > = RhfFieldBaseProps<TFieldValues, TName, TTransformed> &
1403
- OwnMonthPickerProps & {
1404
- /** Classes for the picker — `className` is the item's box. As {@link RhfDateField}'s. */
1405
- inputClassName?: string;
1406
- } & RhfMonthPickerMode;
1407
-
1408
- function MonthControl({
1409
- field,
1410
- focusRef,
1411
- invalid,
1412
- id,
1413
- valueAsNumber,
1414
- mode,
1415
- inputClassName,
1416
- pickerProps,
1417
- required,
1418
- ...aria
1419
- }: {
1420
- field: ControllerRenderProps;
1421
- /** `field.ref`, handed over on its own so the compiler reads `field` as data. */
1422
- focusRef: ControllerRenderProps["ref"];
1423
- invalid: boolean;
1424
- id: string;
1425
- valueAsNumber?: boolean;
1426
- mode?: "month" | "year";
1427
- inputClassName?: string;
1428
- pickerProps: OwnMonthPickerProps;
1429
- required?: boolean;
1430
- "aria-describedby"?: string;
1431
- "aria-invalid"?: MonthPickerProps["aria-invalid"];
1432
- }) {
1433
- const value: unknown = field.value;
1434
- return (
1435
- <MonthPicker
1436
- {...pickerProps}
1437
- {...aria}
1438
- // The trigger itself (API-3, refactor audit 2026-10) — RhfDateField's way.
1439
- ref={focusRef}
1440
- // MonthPicker routes `aria-required` to its trigger since 0.23.0.
1441
- aria-required={required || undefined}
1442
- id={id}
1443
- mode={mode}
1444
- value={value === null || value === undefined ? "" : String(value)}
1445
- onChange={(key) => {
1446
- field.onChange(mode === "year" && valueAsNumber ? Number(key) : key);
1447
- // A pick is the whole interaction — there is no text to leave — so it touches.
1448
- field.onBlur();
1449
- }}
1450
- disabled={field.disabled}
1451
- invalid={invalid}
1452
- className={inputClassName}
1453
- />
1454
- );
1455
- }
1456
-
1457
- /**
1458
- * {@link MonthPicker} bound to one field: a month as `"YYYY-MM"`, or with `mode="year"` a
1459
- * year as `"YYYY"` (`valueAsNumber` stores `2026`). kastlan asked for it with
1460
- * {@link RhfCountrySelect}: its budget wizard's fiscal year was a `MonthPicker
1461
- * mode="year"` inside {@link RhfField}'s render — the `String()` in, the `Number()`
1462
- * out, `invalid` by hand — and a failed submit could not focus it, because the picker
1463
- * forwards no ref.
1464
- *
1465
- * Bound as {@link RhfDateField} is: `field.ref` is the picker's `ref`, its trigger (an id
1466
- * lookup until 0.34, API-3), so focus-on-error lands there; the form's error paints the
1467
- * trigger and is the shell's message; the hint is the shell's description; neither is
1468
- * handed to the picker, so its box never changes. Picking marks the field touched.
1469
- * `commit` and `disabledReason` are the picker's own (0.52, API-3).
1470
- *
1471
- * ```tsx
1472
- * <RhfMonthPicker name="fiscalYear" label="Fiscal year" mode="year" valueAsNumber
1473
- * min="2020" max="2029" rules={{ required: "Choose a year" }} />
1474
- * ```
1475
- */
1476
- export function RhfMonthPicker<
1477
- TFieldValues extends FieldValues = FieldValues,
1478
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
1479
- TTransformed = TFieldValues,
1480
- >({
1481
- name,
1482
- control,
1483
- rules,
1484
- label,
1485
- hint,
1486
- help,
1487
- required,
1488
- disabled,
1489
- excludeWhenDisabled,
1490
- className,
1491
- inputClassName,
1492
- mode,
1493
- valueAsNumber,
1494
- ...pickerProps
1495
- }: RhfMonthPickerProps<TFieldValues, TName, TTransformed>) {
1496
- return (
1497
- <RhfField
1498
- {...{ name, control, rules, label, hint, help, required, disabled, excludeWhenDisabled, className }}
1499
- render={({ field, invalid, id }) => (
1500
- // FormControl clones this element with `aria-describedby` / `aria-invalid`,
1501
- // which MonthControl hands on to the picker's trigger.
1502
- <MonthControl
1503
- field={field as unknown as ControllerRenderProps}
1504
- focusRef={field.ref}
1505
- invalid={invalid}
1506
- id={id}
1507
- mode={mode}
1508
- valueAsNumber={valueAsNumber}
1509
- inputClassName={inputClassName}
1510
- pickerProps={pickerProps}
1511
- required={required}
1512
- />
1513
- )}
1514
- />
1515
- );
1516
- }
1517
-
1518
- // ── select ───────────────────────────────────────────────────────────────────
1519
-
1520
- export interface RhfSelectOption {
1521
- value: string | number;
1522
- label: ReactNode;
1523
- disabled?: boolean;
1524
- }
1525
-
1526
- export type RhfSelectProps<
1527
- TFieldValues extends FieldValues = FieldValues,
1528
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
1529
- TTransformed = TFieldValues,
1530
- > = RhfFieldBaseProps<TFieldValues, TName, TTransformed> &
1531
- // Select's own — its lock (`commit`, `disabledReason`), `size`, … — which a `Pick`
1532
- // list dropped until 0.34 (DUP-9). One value, so not `multiple`.
1533
- Omit<SelectProps, BoundByForm | "children" | "multiple" | "selectClassName"> & {
1534
- /** The options, as data. Or pass `<option>` children. */
1535
- options?: readonly RhfSelectOption[];
1536
- children?: ReactNode;
1537
- /** A first, empty option ("Choose…"), selected while the value is empty. */
1538
- placeholder?: string;
1539
- /** Store `Number(value)` rather than the option's string. An empty choice still
1540
- * stores `""`. */
1541
- valueAsNumber?: boolean;
1542
- /** Classes for the select. Default `w-full`. */
1543
- selectClassName?: string;
1544
- };
1545
-
1546
- /** A native {@link Select}, from `options` or `<option>` children. */
1547
- export function RhfSelect<
1548
- TFieldValues extends FieldValues = FieldValues,
1549
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
1550
- TTransformed = TFieldValues,
1551
- >({
1552
- name,
1553
- control,
1554
- rules,
1555
- label,
1556
- hint,
1557
- help,
1558
- required,
1559
- disabled,
1560
- excludeWhenDisabled,
1561
- className,
1562
- options,
1563
- children,
1564
- placeholder,
1565
- valueAsNumber,
1566
- selectClassName,
1567
- ...selectProps
1568
- }: RhfSelectProps<TFieldValues, TName, TTransformed>) {
1569
- return (
1570
- <RhfField
1571
- {...{ name, control, rules, label, hint, help, required, disabled, excludeWhenDisabled, className }}
1572
- render={({ field, invalid }) => (
1573
- <Select
1574
- aria-required={required || undefined}
1575
- {...selectProps}
1576
- className={cn("w-full", selectClassName)}
1577
- name={field.name}
1578
- ref={field.ref}
1579
- value={(field.value ?? "") as string | number}
1580
- onChange={(e) => {
1581
- const raw = e.target.value;
1582
- field.onChange(valueAsNumber && raw !== "" ? Number(raw) : raw);
1583
- }}
1584
- onBlur={field.onBlur}
1585
- disabled={field.disabled}
1586
- invalid={invalid}
1587
- >
1588
- {placeholder !== undefined && <option value="">{placeholder}</option>}
1589
- {options
1590
- ? options.map((o) => (
1591
- <option key={String(o.value)} value={o.value} disabled={o.disabled}>
1592
- {o.label}
1593
- </option>
1594
- ))
1595
- : children}
1596
- </Select>
1597
- )}
1598
- />
1599
- );
1600
- }
1601
-
1602
- // ── checkbox ─────────────────────────────────────────────────────────────────
1603
-
1604
- export type RhfCheckboxProps<
1605
- TFieldValues extends FieldValues = FieldValues,
1606
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
1607
- TTransformed = TFieldValues,
1608
- > =
1609
- // No `help` (0.36): a box has no label line — its label sits beside it, inside the
1610
- // `<label>` a "?" cannot join — and Checkbox takes none.
1611
- Omit<RhfFieldBaseProps<TFieldValues, TName, TTransformed>, "help"> &
1612
- // Checkbox's own — its lock (`commit`, `disabledReason`) among them, which a `Pick`
1613
- // list dropped until 0.34 (DUP-9) — less the box's state the form holds, and its
1614
- // `description`, which is the twin's `hint`.
1615
- Omit<CheckboxProps, BoundByForm | "checked" | "defaultChecked" | "onCheckedChange" | "description">;
1616
-
1617
- /**
1618
- * The kit's {@link Checkbox}, label beside the box and `hint` under it (the
1619
- * checkbox's own `description`). Stores a boolean. `required` is the native one here
1620
- * — see Checkbox — so a `<form>` refuses to submit it unticked.
1621
- */
1622
- export function RhfCheckbox<
1623
- TFieldValues extends FieldValues = FieldValues,
1624
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
1625
- TTransformed = TFieldValues,
1626
- >({
1627
- name,
1628
- control,
1629
- rules,
1630
- label,
1631
- hint,
1632
- required,
1633
- disabled,
1634
- excludeWhenDisabled,
1635
- className,
1636
- ...boxProps
1637
- }: RhfCheckboxProps<TFieldValues, TName, TTransformed>) {
1638
- // RhfField's rule for the box's own description: a FieldHint is its words at Large
1639
- // and on touch (DUP-2).
1640
- const description = useInlineHint(hint);
1641
- return (
1642
- <RhfField
1643
- {...{ name, control, rules, required, disabled, excludeWhenDisabled, className }}
1644
- showLabel={false}
1645
- render={({ field, invalid }) => (
1646
- <Checkbox
1647
- {...boxProps}
1648
- ref={field.ref}
1649
- name={field.name}
1650
- checked={Boolean(field.value)}
1651
- onCheckedChange={field.onChange}
1652
- onBlur={field.onBlur}
1653
- disabled={field.disabled}
1654
- required={required}
1655
- invalid={invalid}
1656
- label={label}
1657
- description={description}
1658
- />
1659
- )}
1660
- />
1661
- );
1662
- }
1663
-
1664
- // ── toggle group ─────────────────────────────────────────────────────────────
1665
-
1666
- /** The two shapes a {@link ToggleGroup} comes in, carried over: `allowEmpty` picks
1667
- * whether a press on the chosen option clears it, and types the caption to match. */
1668
- type RhfToggleGroupMode<T extends string> =
1669
- | {
1670
- /** One option is always the answer (the default): a press on the chosen
1671
- * option re-sends it. */
1672
- allowEmpty?: false;
1673
- semantics?: ToggleGroupRequiredProps<T>["semantics"];
1674
- caption?: ToggleGroupRequiredProps<T>["caption"];
1675
- emptyValue?: undefined;
1676
- }
1677
- | {
1678
- /** A press on the chosen option clears it, and the form stores `emptyValue`. */
1679
- allowEmpty: true;
1680
- semantics?: ToggleGroupClearableProps<T>["semantics"];
1681
- caption?: ToggleGroupClearableProps<T>["caption"];
1682
- /** What a cleared group stores. Default `null`; `""` for a schema that spells
1683
- * "no choice" as an empty string. */
1684
- emptyValue?: null | "";
1685
- };
1686
-
1687
- export type RhfToggleGroupProps<
1688
- TFieldValues extends FieldValues = FieldValues,
1689
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
1690
- T extends string = string,
1691
- TTransformed = TFieldValues,
1692
- > = RhfFieldBaseProps<TFieldValues, TName, TTransformed> &
1693
- // The group's own (its `disabledReasonDisplay` was not on the `Pick` list this was
1694
- // until 0.34, DUP-9), less what the form binds, the ids, and the label's placement:
1695
- // the label is the form's, above.
1696
- Omit<ToggleGroupBaseProps<T>, BoundByForm | "id" | "aria-labelledby" | "labelPlacement" | "chromeClassName"> & {
1697
- /** Classes for the group itself — `className` is the item's box. */
1698
- groupClassName?: string;
1699
- } & RhfToggleGroupMode<T>;
1700
-
1701
- /**
1702
- * The kit's {@link ToggleGroup} bound to one field: a choice between a few answers
1703
- * ("Monthly / Quarterly / Yearly"), stored as the chosen option's `value`. kastlan 4
1704
- * asked for it with {@link RhfTimeInput} and {@link RhfDateRangePicker}: the controls
1705
- * its forms still had to wire by hand through {@link RhfField}'s render.
1706
- *
1707
- * The label is the field's own label above the bare group, as on every other
1708
- * bound field — the `labelPlacement="above"` shape, not the group's own field chrome —
1709
- * and it names the group (`aria-labelledby`); the hint and the message describe it,
1710
- * and an error paints the group's border. Focus on error goes to the option a
1711
- * keyboard user would land on (the chosen one, else the first); touched is set when
1712
- * focus leaves the group, not when it moves between options.
1713
- *
1714
- * Required by default, as the group is: a press on the chosen option keeps it. With
1715
- * `allowEmpty`, a second press clears the choice and the form stores `emptyValue`
1716
- * (`null` by default) — the options then become toggle buttons, see ToggleGroup. A
1717
- * value that matches no option (an empty default) leaves nothing pressed; that the
1718
- * field still needs answering is the schema's or `rules`' to say.
1719
- */
1720
- export function RhfToggleGroup<
1721
- TFieldValues extends FieldValues = FieldValues,
1722
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
1723
- T extends string = string,
1724
- TTransformed = TFieldValues,
1725
- >({
1726
- name,
1727
- control,
1728
- rules,
1729
- label,
1730
- hint,
1731
- help,
1732
- required,
1733
- disabled,
1734
- excludeWhenDisabled,
1735
- className,
1736
- ...groupProps
1737
- }: RhfToggleGroupProps<TFieldValues, TName, T, TTransformed>) {
1738
- const labelled = hasContent(label);
1739
- return (
1740
- <RhfField
1741
- {...{ name, control, rules, label, hint, help, required, disabled, excludeWhenDisabled, className }}
1742
- asControl={false}
1743
- render={({ field, invalid, id, labelId, describedBy }) => (
1744
- <ToggleControl<T>
1745
- field={field as unknown as ControllerRenderProps}
1746
- invalid={invalid}
1747
- required={required}
1748
- id={id}
1749
- labelledBy={labelled ? labelId : undefined}
1750
- describedBy={describedBy}
1751
- groupProps={groupProps}
1752
- />
1753
- )}
1754
- />
1755
- );
1756
- }
1757
-
1758
- function ToggleControl<T extends string>({
1759
- field,
1760
- invalid,
1761
- required,
1762
- id,
1763
- labelledBy,
1764
- describedBy,
1765
- groupProps,
1766
- }: {
1767
- field: ControllerRenderProps;
1768
- invalid: boolean;
1769
- required?: boolean;
1770
- id: string;
1771
- labelledBy: string | undefined;
1772
- describedBy: string | undefined;
1773
- groupProps: Omit<RhfToggleGroupProps<FieldValues, string, T>, keyof RhfFieldBaseProps>;
1774
- }) {
1775
- const { groupClassName, emptyValue, allowEmpty, semantics, caption, ...rest } = groupProps;
1776
- // React's onBlur is focusout: it fires on every move between options as well, and
1777
- // only leaving the group is a blur of the FIELD.
1778
- const onBlur = (e: FocusEvent<HTMLDivElement>) => {
1779
- if (!e.currentTarget.contains(e.relatedTarget as Node | null)) field.onBlur();
1780
- };
1781
- const raw: unknown = field.value;
1782
- const value = raw === null || raw === undefined || raw === "" ? null : (String(raw) as T);
1783
- const common = {
1784
- ...rest,
1785
- // The group (API-3, refactor audit 2026-10): its `focus()` lands on the radio shape's
1786
- // tab stop, or the pressed shape's pressed option — the rule this adapter carried
1787
- // as an id lookup until ToggleGroup took a ref.
1788
- ref: field.ref,
1789
- id,
1790
- className: groupClassName,
1791
- "aria-labelledby": labelledBy,
1792
- "aria-describedby": describedBy,
1793
- "aria-invalid": invalid || undefined,
1794
- disabled: field.disabled,
1795
- onBlur,
1796
- };
1797
- // The props' union pairs `allowEmpty` with its caption's type; taken apart above it no
1798
- // longer narrows, so each branch says which half it is.
1799
- if (allowEmpty) {
1800
- return (
1801
- <ToggleGroup<T>
1802
- {...common}
1803
- allowEmpty
1804
- caption={caption as ToggleGroupClearableProps<T>["caption"]}
1805
- value={value}
1806
- onChange={(v) => field.onChange(v ?? emptyValue ?? null)}
1807
- />
1808
- );
1809
- }
1810
- return (
1811
- <ToggleGroup<T>
1812
- {...common}
1813
- allowEmpty={false}
1814
- semantics={semantics}
1815
- caption={caption as ToggleGroupRequiredProps<T>["caption"]}
1816
- // A radiogroup takes `aria-required`; a group of toggle buttons does not.
1817
- aria-required={required && semantics !== "pressed" ? true : undefined}
1818
- // An empty default matches no option: nothing pressed, the first option the tab
1819
- // stop.
1820
- value={(value ?? "") as T}
1821
- onChange={(v) => field.onChange(v)}
1822
- />
1823
- );
1824
- }
1825
-
1826
- // ── comboboxes ───────────────────────────────────────────────────────────────
1827
-
1828
- export type RhfComboboxProps<
1829
- TFieldValues extends FieldValues = FieldValues,
1830
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
1831
- V extends string | number = string | number,
1832
- TTransformed = TFieldValues,
1833
- > = RhfFieldBaseProps<TFieldValues, TName, TTransformed> &
1834
- // The picker's own, as an `Omit` since 0.34 (DUP-9) rather than the `Pick` list it
1835
- // was, less what the form binds and the clear value, which is the twin's below.
1836
- Omit<EntityComboboxProps<V>, BoundByForm | "id" | "clearValue"> & {
1837
- /** What a clear stores — the picker's `clearValue`. Default `null`; `""` for a
1838
- * schema that spells "no choice" as an empty string. */
1839
- clearValue?: ComboClearValue;
1840
- /** Classes for the picker. */
1841
- comboClassName?: string;
1842
- };
1843
-
1844
- function EntityControl<V extends string | number>({
1845
- field,
1846
- invalid,
1847
- required,
1848
- error,
1849
- id,
1850
- describedBy,
1851
- clearValue,
1852
- comboClassName,
1853
- sheetTitle,
1854
- focusRef,
1855
- comboProps,
1856
- }: {
1857
- field: ControllerRenderProps;
1858
- invalid: boolean;
1859
- required?: boolean;
1860
- error: string | undefined;
1861
- id: string;
1862
- describedBy: string | undefined;
1863
- clearValue: ComboClearValue;
1864
- comboClassName?: string;
1865
- sheetTitle: ReactNode;
1866
- /** `field.ref`, handed over on its own so the compiler reads `field` as data. */
1867
- focusRef: ControllerRenderProps["ref"];
1868
- comboProps: Omit<RhfComboboxProps<FieldValues, string, V>, keyof RhfFieldBaseProps | "clearValue" | "comboClassName">;
1869
- }) {
1870
- const box = useRef<HTMLDivElement>(null);
1871
- // EntityCombobox puts `id` and the ARIA below on its trigger, so the label's
1872
- // `htmlFor` names it and the hint describes it; `field.ref` reaches the trigger
1873
- // through the picker's own `ref` (0.24), as RhfCountrySelect's does.
1874
- // EntityCombobox reports no blur: focus leaving the picker marks the field touched.
1875
- const onBlur = field.onBlur;
1876
- useLayoutEffect(() => {
1877
- const el = box.current;
1878
- if (!el) return;
1879
- el.addEventListener("focusout", onBlur);
1880
- return () => el.removeEventListener("focusout", onBlur);
1881
- }, [onBlur]);
1882
- const value: unknown = field.value;
1883
- return (
1884
- <div ref={box}>
1885
- <EntityCombobox<V, ComboClearValue>
1886
- // The form's label titles the phone sheet: the picker is not handed `label`,
1887
- // which the FormLabel above already draws.
1888
- sheetTitle={sheetTitle}
1889
- {...comboProps}
1890
- ref={focusRef}
1891
- className={comboClassName}
1892
- id={id}
1893
- aria-describedby={describedBy}
1894
- aria-required={required || undefined}
1895
- value={(value ?? null) as V | ComboClearValue | null}
1896
- clearValue={clearValue}
1897
- onChange={(v) => field.onChange(v)}
1898
- disabled={field.disabled}
1899
- invalid={invalid}
1900
- // Its own message, which it merges into the trigger's aria-describedby after
1901
- // the hint.
1902
- error={error}
1903
- />
1904
- </div>
1905
- );
1906
- }
1907
-
1908
- /**
1909
- * The kit's {@link EntityCombobox}: an id-keyed pick from `options` or `loadOptions`.
1910
- * Stores the option's `value`, or `clearValue` on a clear (only offered with
1911
- * `clearable`).
1912
- *
1913
- * 0.24: `field.ref` goes through the picker's own `ref` to its trigger (it used to be
1914
- * a focus handle that found the trigger by id — the same element, now by the route
1915
- * every other binding takes); the write lock (`commit`, `disabledReason`), the create
1916
- * row's `createEmptyLabel` / `createCommit`, `closeLabel` and `aria-label` pass
1917
- * through; and the form's label titles the phone sheet, which fell back to the
1918
- * placeholder. For the INLINE picker — a text input, the account field typed into —
1919
- * see {@link RhfInlineEntityCombobox}.
1920
- */
1921
- export function RhfCombobox<
1922
- TFieldValues extends FieldValues = FieldValues,
1923
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
1924
- V extends string | number = string | number,
1925
- TTransformed = TFieldValues,
1926
- >({
1927
- name,
1928
- control,
1929
- rules,
1930
- label,
1931
- hint,
1932
- help,
1933
- required,
1934
- disabled,
1935
- excludeWhenDisabled,
1936
- className,
1937
- clearValue = null,
1938
- comboClassName,
1939
- ...comboProps
1940
- }: RhfComboboxProps<TFieldValues, TName, V, TTransformed>) {
1941
- return (
1942
- <RhfField
1943
- {...{ name, control, rules, label, hint, help, required, disabled, excludeWhenDisabled, className }}
1944
- asControl={false}
1945
- message={false}
1946
- render={({ field, invalid, error, id, describedBy }) => (
1947
- <EntityControl<V>
1948
- field={field as unknown as ControllerRenderProps}
1949
- invalid={invalid}
1950
- required={required}
1951
- error={error}
1952
- id={id}
1953
- describedBy={describedBy}
1954
- clearValue={clearValue}
1955
- comboClassName={comboClassName}
1956
- sheetTitle={hasContent(label) ? label : undefined}
1957
- focusRef={field.ref}
1958
- comboProps={comboProps}
1959
- />
1960
- )}
1961
- />
1962
- );
1963
- }
1964
-
1965
- type OwnInlineEntityComboboxProps<V extends string | number> = Omit<
1966
- InlineEntityComboboxProps<V, ComboClearValue>,
1967
- | "value"
1968
- | "defaultValue"
1969
- | "onChange"
1970
- | "onBlur"
1971
- | "label"
1972
- | "hint"
1973
- | "error"
1974
- | "invalid"
1975
- | "disabled"
1976
- | "className"
1977
- | "id"
1978
- | "ref"
1979
- | "clearValue"
1980
- >;
1981
-
1982
- export type RhfInlineEntityComboboxProps<
1983
- TFieldValues extends FieldValues = FieldValues,
1984
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
1985
- V extends string | number = string | number,
1986
- TTransformed = TFieldValues,
1987
- > = RhfFieldBaseProps<TFieldValues, TName, TTransformed> &
1988
- OwnInlineEntityComboboxProps<V> & {
1989
- /** What a clear stores — emptying the text, or the "×" with `clearable`. Default
1990
- * `null`; `""` for a schema that spells "no choice" as an empty string. The
1991
- * picker reads either back as empty. */
1992
- clearValue?: ComboClearValue;
1993
- /** Classes for the picker — `className` is the item's box. As {@link RhfCombobox}'s. */
1994
- comboClassName?: string;
1995
- };
1996
-
1997
- /**
1998
- * {@link InlineEntityCombobox} bound to one field, which stores the chosen option's
1999
- * `value` (an id), or `clearValue` once the text is emptied or the "×" pressed.
2000
- *
2001
- * kastlan asked for it (0.24): its account picker IS an `InlineEntityCombobox` — the
2002
- * account number is typed straight into the field — and the budget line-item form
2003
- * wired it through {@link RhfField}'s render. The picker took no `ref`, so react-hook-
2004
- * form's focus-on-error had nothing to focus: a line item saved without an account
2005
- * failed its submit and left the caret where it was, the one field on the form a
2006
- * failed submit could not take you to. (The same gap {@link RhfCountrySelect} closed
2007
- * for the address form's country in 0.23.)
2008
- *
2009
- * Bound the way {@link RhfCountrySelect} is: `field.ref` reaches the `<input>` (the
2010
- * picker's `ref`, 0.24), the form's error paints the input and is the shell's message
2011
- * under it, and the hint is the shell's description — so neither is ever handed to the
2012
- * picker, and its box never changes when one comes or goes. The form's label names
2013
- * the input through `<label for>` and titles the phone sheet (`sheetTitle`); the picker
2014
- * draws no floating label of its own. Focus leaving the picker marks the field
2015
- * touched.
2016
- *
2017
- * Everything else is the picker's and passes through: `commit` and `disabledReason`
2018
- * (a picker whose choice saves stays focusable under a lock and says why), `clearable`,
2019
- * `autoFocus`, `aria-label` for an unlabelled cell, and the create row — `onCreate`,
2020
- * `createLabel`, `createEmptyLabel`, `createCommit`. Making the record is the
2021
- * caller's, and so is storing its id once it exists:
2022
- *
2023
- * ```tsx
2024
- * <RhfInlineEntityCombobox<LineItem, "account_id", number>
2025
- * name="account_id" label="Account" required options={accounts}
2026
- * rules={{ required: "Choose an account" }}
2027
- * onCreate={async (name) => form.setValue("account_id", (await createAccount(name)).id)} />
2028
- * ```
2029
- *
2030
- * For the button-shaped picker with an async `loadOptions`, see {@link RhfCombobox}.
2031
- */
2032
- export function RhfInlineEntityCombobox<
2033
- TFieldValues extends FieldValues = FieldValues,
2034
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
2035
- V extends string | number = string | number,
2036
- TTransformed = TFieldValues,
2037
- >({
2038
- name,
2039
- control,
2040
- rules,
2041
- label,
2042
- hint,
2043
- help,
2044
- required,
2045
- disabled,
2046
- excludeWhenDisabled,
2047
- className,
2048
- clearValue = null,
2049
- comboClassName,
2050
- ...comboProps
2051
- }: RhfInlineEntityComboboxProps<TFieldValues, TName, V, TTransformed>) {
2052
- return (
2053
- <RhfField
2054
- {...{ name, control, rules, label, hint, help, required, disabled, excludeWhenDisabled, className }}
2055
- render={({ field, invalid }) => {
2056
- const value: unknown = field.value;
2057
- return (
2058
- // FormControl clones this element with `id` / `aria-describedby` /
2059
- // `aria-invalid`, which the picker routes to its <input>.
2060
- <InlineEntityCombobox<V, ComboClearValue>
2061
- aria-required={required || undefined}
2062
- // The form's label is the phone sheet's heading — the picker is not handed
2063
- // `label`, which the FormLabel above already draws. A caller's own wins.
2064
- sheetTitle={hasContent(label) ? label : undefined}
2065
- {...comboProps}
2066
- className={comboClassName}
2067
- ref={field.ref}
2068
- value={(value ?? null) as V | ComboClearValue | null}
2069
- clearValue={clearValue}
2070
- onChange={(v) => field.onChange(v)}
2071
- // The wrapper's blur, which the <input>'s bubbles to after the picker has
2072
- // judged any loose text — so the value is in before the field is touched.
2073
- onBlur={field.onBlur}
2074
- disabled={field.disabled}
2075
- invalid={invalid}
2076
- />
2077
- );
2078
- }}
2079
- />
2080
- );
2081
- }
2082
-
2083
- export type RhfTextComboboxProps<
2084
- TFieldValues extends FieldValues = FieldValues,
2085
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
2086
- TTransformed = TFieldValues,
2087
- > = RhfFieldBaseProps<TFieldValues, TName, TTransformed> &
2088
- // The combobox's own, as an `Omit` since 0.34 (DUP-9) rather than the `Pick` list it
2089
- // was, less what the form binds.
2090
- Omit<ComboboxProps, BoundByForm | "id"> & {
2091
- /** Classes for the combobox. */
2092
- comboClassName?: string;
2093
- };
2094
-
2095
- /**
2096
- * The kit's free-text {@link Combobox}: type anything, or pick one of `options`.
2097
- * Stores the string.
2098
- */
2099
- export function RhfTextCombobox<
2100
- TFieldValues extends FieldValues = FieldValues,
2101
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
2102
- TTransformed = TFieldValues,
2103
- >({
2104
- name,
2105
- control,
2106
- rules,
2107
- label,
2108
- hint,
2109
- help,
2110
- required,
2111
- disabled,
2112
- excludeWhenDisabled,
2113
- className,
2114
- comboClassName,
2115
- ...comboProps
2116
- }: RhfTextComboboxProps<TFieldValues, TName, TTransformed>) {
2117
- return (
2118
- <RhfField
2119
- {...{ name, control, rules, label, hint, help, required, disabled, excludeWhenDisabled, className }}
2120
- asControl={false}
2121
- message={false}
2122
- render={({ field, invalid, error, id, describedBy }) => (
2123
- <TextComboControl
2124
- field={field as unknown as ControllerRenderProps}
2125
- invalid={invalid}
2126
- required={required}
2127
- error={error}
2128
- id={id}
2129
- describedBy={describedBy}
2130
- className={comboClassName}
2131
- sheetTitle={hasContent(label) ? label : undefined}
2132
- focusRef={field.ref}
2133
- comboProps={comboProps}
2134
- />
2135
- )}
2136
- />
2137
- );
2138
- }
2139
-
2140
- function TextComboControl({
2141
- field,
2142
- invalid,
2143
- required,
2144
- error,
2145
- id,
2146
- describedBy,
2147
- className,
2148
- sheetTitle,
2149
- focusRef,
2150
- comboProps,
2151
- }: {
2152
- field: ControllerRenderProps;
2153
- invalid: boolean;
2154
- required?: boolean;
2155
- error: string | undefined;
2156
- id: string;
2157
- describedBy: string | undefined;
2158
- className?: string;
2159
- sheetTitle: ReactNode;
2160
- /** `field.ref` — see EntityControl. */
2161
- focusRef: ControllerRenderProps["ref"];
2162
- comboProps: Omit<RhfTextComboboxProps, keyof RhfFieldBaseProps | "comboClassName">;
2163
- }) {
2164
- // The combobox routes `id` and its ARIA to its <input>, so the label's `htmlFor`
2165
- // names it and the hint describes it; `field.ref` reaches the <input> through the
2166
- // combobox's own `ref` (0.24). The error is its own `error` prop, which it merges
2167
- // in after the hint.
2168
- const value: unknown = field.value;
2169
- return (
2170
- <Combobox
2171
- // The form's label titles the phone sheet (see EntityControl).
2172
- sheetTitle={sheetTitle}
2173
- {...comboProps}
2174
- ref={focusRef}
2175
- id={id}
2176
- aria-describedby={describedBy}
2177
- aria-required={required || undefined}
2178
- className={className}
2179
- value={typeof value === "string" ? value : value == null ? "" : String(value)}
2180
- onChange={field.onChange}
2181
- onBlur={field.onBlur}
2182
- disabled={field.disabled}
2183
- invalid={invalid}
2184
- error={error}
2185
- />
2186
- );
2187
- }