@eifi1/ui-kit 0.51.7 → 0.52.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 (746) hide show
  1. package/README.md +141 -23
  2. package/dist/account/account-parts.d.ts +10 -8
  3. package/dist/account/account-parts.js +1 -18
  4. package/dist/account/account-parts.js.map +1 -1
  5. package/dist/account/data-export-setting-labels.js +1 -1
  6. package/dist/account/data-export-setting-labels.js.map +1 -1
  7. package/dist/account/delete-account-setting-labels.js +1 -1
  8. package/dist/account/delete-account-setting-labels.js.map +1 -1
  9. package/dist/account/email-change-setting-labels.js +1 -1
  10. package/dist/account/email-change-setting-labels.js.map +1 -1
  11. package/dist/account/passkeys-setting.d.ts +9 -2
  12. package/dist/account/passkeys-setting.js +41 -29
  13. package/dist/account/passkeys-setting.js.map +1 -1
  14. package/dist/account/sessions-setting.d.ts +3 -2
  15. package/dist/account/sessions-setting.js +10 -8
  16. package/dist/account/sessions-setting.js.map +1 -1
  17. package/dist/admin/admin-action-confirm.js +1 -3
  18. package/dist/admin/admin-action-confirm.js.map +1 -1
  19. package/dist/admin/admin-parts.d.ts +6 -3
  20. package/dist/admin/admin-parts.js +6 -21
  21. package/dist/admin/admin-parts.js.map +1 -1
  22. package/dist/admin/mail-test-card-labels.js +1 -1
  23. package/dist/admin/mail-test-card-labels.js.map +1 -1
  24. package/dist/admin/plan-change-labels.d.ts +2 -2
  25. package/dist/admin/plan-change-labels.js +1 -17
  26. package/dist/admin/plan-change-labels.js.map +1 -1
  27. package/dist/admin/plan-change.d.ts +3 -42
  28. package/dist/admin/plan-change.js +1 -1
  29. package/dist/admin/plan-change.js.map +1 -1
  30. package/dist/admin/reviewer-scope-editor.js +3 -3
  31. package/dist/admin/reviewer-scope-editor.js.map +1 -1
  32. package/dist/auth/auth-errors-labels.d.ts +11 -0
  33. package/dist/auth/auth-errors-labels.js +13 -0
  34. package/dist/auth/auth-errors-labels.js.map +1 -0
  35. package/dist/auth/auth-errors.d.ts +7 -23
  36. package/dist/auth/auth-errors.js +2 -15
  37. package/dist/auth/auth-errors.js.map +1 -1
  38. package/dist/auth/complete-name-dialog.js +2 -1
  39. package/dist/auth/complete-name-dialog.js.map +1 -1
  40. package/dist/auth/forgot-password-form-labels.js +1 -1
  41. package/dist/auth/forgot-password-form-labels.js.map +1 -1
  42. package/dist/auth/forgot-password-form.js +7 -2
  43. package/dist/auth/forgot-password-form.js.map +1 -1
  44. package/dist/auth/register-form-labels.js +1 -1
  45. package/dist/auth/register-form-labels.js.map +1 -1
  46. package/dist/auth/reset-password-form-labels.js +1 -1
  47. package/dist/auth/reset-password-form-labels.js.map +1 -1
  48. package/dist/auth/reset-password-form.js +5 -1
  49. package/dist/auth/reset-password-form.js.map +1 -1
  50. package/dist/auth/sign-in-form-labels.js +1 -1
  51. package/dist/auth/sign-in-form-labels.js.map +1 -1
  52. package/dist/auth/status-parts.d.ts +0 -3
  53. package/dist/auth/status-parts.js +0 -4
  54. package/dist/auth/status-parts.js.map +1 -1
  55. package/dist/auth/verify-email-labels.js +1 -1
  56. package/dist/auth/verify-email-labels.js.map +1 -1
  57. package/dist/auth/verify-email.js +7 -2
  58. package/dist/auth/verify-email.js.map +1 -1
  59. package/dist/billing/cancel-subscription.d.ts +20 -8
  60. package/dist/billing/cancel-subscription.js +7 -5
  61. package/dist/billing/cancel-subscription.js.map +1 -1
  62. package/dist/billing/dialog-close.d.ts +29 -0
  63. package/dist/billing/dialog-close.js +8 -0
  64. package/dist/billing/dialog-close.js.map +1 -0
  65. package/dist/billing/plan-card.d.ts +14 -4
  66. package/dist/billing/plan-card.js +15 -8
  67. package/dist/billing/plan-card.js.map +1 -1
  68. package/dist/billing/plan-change-labels.d.ts +42 -0
  69. package/dist/billing/plan-change-labels.js +22 -0
  70. package/dist/billing/plan-change-labels.js.map +1 -0
  71. package/dist/billing/plan-switch.d.ts +19 -7
  72. package/dist/billing/plan-switch.js +8 -4
  73. package/dist/billing/plan-switch.js.map +1 -1
  74. package/dist/chart.d.ts +12 -0
  75. package/dist/chart.js +16 -1
  76. package/dist/chart.js.map +1 -1
  77. package/dist/components/account-chips.d.ts +5 -5
  78. package/dist/components/account-chips.js +11 -13
  79. package/dist/components/account-chips.js.map +1 -1
  80. package/dist/components/alert-banner.d.ts +16 -5
  81. package/dist/components/alert-banner.js +46 -1
  82. package/dist/components/alert-banner.js.map +1 -1
  83. package/dist/components/amount-input.d.ts +13 -0
  84. package/dist/components/amount-input.js +1 -0
  85. package/dist/components/amount-input.js.map +1 -1
  86. package/dist/components/autocomplete.js +57 -65
  87. package/dist/components/autocomplete.js.map +1 -1
  88. package/dist/components/bulk-action-bar.d.ts +12 -2
  89. package/dist/components/bulk-action-bar.js +65 -21
  90. package/dist/components/bulk-action-bar.js.map +1 -1
  91. package/dist/components/button-look.d.ts +5 -5
  92. package/dist/components/button-look.js.map +1 -1
  93. package/dist/components/button.d.ts +2 -2
  94. package/dist/components/button.js.map +1 -1
  95. package/dist/components/chart-edge.d.ts +16 -0
  96. package/dist/components/chart-edge.js +14 -1
  97. package/dist/components/chart-edge.js.map +1 -1
  98. package/dist/components/chart-figure.d.ts +13 -0
  99. package/dist/components/chart-figure.js +15 -0
  100. package/dist/components/chart-figure.js.map +1 -0
  101. package/dist/components/chart-zoom-core.js +1 -4
  102. package/dist/components/chart-zoom-core.js.map +1 -1
  103. package/dist/components/chart.d.ts +8 -1
  104. package/dist/components/chart.js +19 -10
  105. package/dist/components/chart.js.map +1 -1
  106. package/dist/components/combobox-core.d.ts +111 -2
  107. package/dist/components/combobox-core.js +148 -42
  108. package/dist/components/combobox-core.js.map +1 -1
  109. package/dist/components/combobox.d.ts +1 -1
  110. package/dist/components/combobox.js +141 -130
  111. package/dist/components/combobox.js.map +1 -1
  112. package/dist/components/copy-button.d.ts +6 -5
  113. package/dist/components/copy-button.js +5 -3
  114. package/dist/components/copy-button.js.map +1 -1
  115. package/dist/components/country-select.js +2 -2
  116. package/dist/components/country-select.js.map +1 -1
  117. package/dist/components/data-table-column-settings.d.ts +1 -1
  118. package/dist/components/data-table-column-settings.js +33 -7
  119. package/dist/components/data-table-column-settings.js.map +1 -1
  120. package/dist/components/data-table-filters.d.ts +4 -2
  121. package/dist/components/data-table-filters.js +2 -2
  122. package/dist/components/data-table-filters.js.map +1 -1
  123. package/dist/components/data-table-fit.d.ts +6 -4
  124. package/dist/components/data-table-fit.js +4 -1
  125. package/dist/components/data-table-fit.js.map +1 -1
  126. package/dist/components/data-table-groups.d.ts +1 -1
  127. package/dist/components/data-table-groups.js.map +1 -1
  128. package/dist/components/data-table-head.d.ts +1 -1
  129. package/dist/components/data-table-head.js +15 -12
  130. package/dist/components/data-table-head.js.map +1 -1
  131. package/dist/components/data-table-mobile.d.ts +12 -7
  132. package/dist/components/data-table-mobile.js +43 -30
  133. package/dist/components/data-table-mobile.js.map +1 -1
  134. package/dist/components/data-table-pagination.js +11 -6
  135. package/dist/components/data-table-pagination.js.map +1 -1
  136. package/dist/components/data-table-resize.d.ts +1 -1
  137. package/dist/components/data-table-resize.js.map +1 -1
  138. package/dist/components/data-table-row-actions.d.ts +2 -2
  139. package/dist/components/data-table-row-actions.js.map +1 -1
  140. package/dist/components/data-table-row.d.ts +6 -2
  141. package/dist/components/data-table-row.js +17 -10
  142. package/dist/components/data-table-row.js.map +1 -1
  143. package/dist/components/data-table-selection.d.ts +1 -1
  144. package/dist/components/data-table-selection.js.map +1 -1
  145. package/dist/components/data-table-shared.d.ts +10 -2
  146. package/dist/components/data-table-shared.js +18 -0
  147. package/dist/components/data-table-shared.js.map +1 -1
  148. package/dist/components/data-table-sort.d.ts +21 -2
  149. package/dist/components/data-table-sort.js +24 -0
  150. package/dist/components/data-table-sort.js.map +1 -1
  151. package/dist/components/data-table-virtual.d.ts +1 -1
  152. package/dist/components/data-table-virtual.js +18 -6
  153. package/dist/components/data-table-virtual.js.map +1 -1
  154. package/dist/components/data-table.d.ts +13 -4
  155. package/dist/components/data-table.js +139 -36
  156. package/dist/components/data-table.js.map +1 -1
  157. package/dist/components/date-field.d.ts +24 -2
  158. package/dist/components/date-field.js +33 -8
  159. package/dist/components/date-field.js.map +1 -1
  160. package/dist/components/date-picker.d.ts +15 -2
  161. package/dist/components/date-picker.js +55 -26
  162. package/dist/components/date-picker.js.map +1 -1
  163. package/dist/components/date-range-picker.d.ts +47 -20
  164. package/dist/components/date-range-picker.js +23 -7
  165. package/dist/components/date-range-picker.js.map +1 -1
  166. package/dist/components/error-boundary.js +37 -30
  167. package/dist/components/error-boundary.js.map +1 -1
  168. package/dist/components/{ui-labels.d.ts → field-base-labels.d.ts} +0 -2
  169. package/dist/components/{ui-labels.js → field-base-labels.js} +2 -9
  170. package/dist/components/field-base-labels.js.map +1 -0
  171. package/dist/components/field-base.d.ts +4 -3
  172. package/dist/components/field-base.js +3 -3
  173. package/dist/components/field-base.js.map +1 -1
  174. package/dist/components/field-sync-labels.d.ts +1 -0
  175. package/dist/components/field-sync-labels.js.map +1 -1
  176. package/dist/components/field-sync.d.ts +23 -1
  177. package/dist/components/field-sync.js.map +1 -1
  178. package/dist/components/form-actions.js +11 -5
  179. package/dist/components/form-actions.js.map +1 -1
  180. package/dist/components/hover-menu.d.ts +3 -0
  181. package/dist/components/hover-menu.js +5 -3
  182. package/dist/components/hover-menu.js.map +1 -1
  183. package/dist/components/icon-button.js +14 -9
  184. package/dist/components/icon-button.js.map +1 -1
  185. package/dist/components/inline-confirm.d.ts +43 -0
  186. package/dist/components/inline-confirm.js +67 -0
  187. package/dist/components/inline-confirm.js.map +1 -0
  188. package/dist/components/inline-entity-combobox.d.ts +4 -3
  189. package/dist/components/inline-entity-combobox.js +148 -140
  190. package/dist/components/inline-entity-combobox.js.map +1 -1
  191. package/dist/components/input.js +1 -1
  192. package/dist/components/input.js.map +1 -1
  193. package/dist/components/jump-calendar.js +28 -112
  194. package/dist/components/jump-calendar.js.map +1 -1
  195. package/dist/components/legal-page.d.ts +3 -1
  196. package/dist/components/legal-page.js +3 -2
  197. package/dist/components/legal-page.js.map +1 -1
  198. package/dist/components/legal.d.ts +4 -2
  199. package/dist/components/legal.js.map +1 -1
  200. package/dist/components/lightbox.d.ts +2 -2
  201. package/dist/components/lightbox.js +17 -6
  202. package/dist/components/lightbox.js.map +1 -1
  203. package/dist/components/list.d.ts +6 -2
  204. package/dist/components/list.js +3 -2
  205. package/dist/components/list.js.map +1 -1
  206. package/dist/components/measured-grid.d.ts +1 -1
  207. package/dist/components/measured-grid.js +2 -1
  208. package/dist/components/measured-grid.js.map +1 -1
  209. package/dist/components/mini-calendar.js +3 -5
  210. package/dist/components/mini-calendar.js.map +1 -1
  211. package/dist/components/modal.js +3 -1
  212. package/dist/components/modal.js.map +1 -1
  213. package/dist/components/month-grid.d.ts +55 -0
  214. package/dist/components/month-grid.js +148 -0
  215. package/dist/components/month-grid.js.map +1 -0
  216. package/dist/components/month-picker.d.ts +36 -4
  217. package/dist/components/month-picker.js +123 -146
  218. package/dist/components/month-picker.js.map +1 -1
  219. package/dist/components/multi-select.d.ts +2 -2
  220. package/dist/components/multi-select.js +11 -6
  221. package/dist/components/multi-select.js.map +1 -1
  222. package/dist/components/picker-sheet.js +2 -1
  223. package/dist/components/picker-sheet.js.map +1 -1
  224. package/dist/components/pie-chart-labels.d.ts +7 -0
  225. package/dist/components/pie-chart-labels.js +1 -0
  226. package/dist/components/pie-chart-labels.js.map +1 -1
  227. package/dist/components/pie-chart.d.ts +5 -3
  228. package/dist/components/pie-chart.js +7 -22
  229. package/dist/components/pie-chart.js.map +1 -1
  230. package/dist/components/popover.d.ts +3 -0
  231. package/dist/components/popover.js +7 -1
  232. package/dist/components/popover.js.map +1 -1
  233. package/dist/components/row-actions.js +2 -1
  234. package/dist/components/row-actions.js.map +1 -1
  235. package/dist/components/section-nav.d.ts +7 -0
  236. package/dist/components/section-nav.js.map +1 -1
  237. package/dist/components/series-chart-axes.d.ts +3 -3
  238. package/dist/components/series-chart-axes.js.map +1 -1
  239. package/dist/components/series-chart-labels.d.ts +7 -4
  240. package/dist/components/series-chart-labels.js.map +1 -1
  241. package/dist/components/series-chart.d.ts +31 -7
  242. package/dist/components/series-chart.js +72 -29
  243. package/dist/components/series-chart.js.map +1 -1
  244. package/dist/components/share-card.js +3 -3
  245. package/dist/components/share-card.js.map +1 -1
  246. package/dist/components/sign-chip-labels.d.ts +1 -0
  247. package/dist/components/sign-chip-labels.js.map +1 -1
  248. package/dist/components/sign-chip.d.ts +10 -1
  249. package/dist/components/sign-chip.js.map +1 -1
  250. package/dist/components/tabs-labels.d.ts +2 -0
  251. package/dist/components/tabs-labels.js +11 -0
  252. package/dist/components/tabs-labels.js.map +1 -0
  253. package/dist/components/tabs.d.ts +1 -1
  254. package/dist/components/tabs.js +1 -1
  255. package/dist/components/tabs.js.map +1 -1
  256. package/dist/components/toggle-legend.d.ts +2 -2
  257. package/dist/components/toggle-legend.js +6 -6
  258. package/dist/components/toggle-legend.js.map +1 -1
  259. package/dist/components/translation-review-editor.d.ts +4 -2
  260. package/dist/components/translation-review-editor.js +2 -3
  261. package/dist/components/translation-review-editor.js.map +1 -1
  262. package/dist/components/translation-review.d.ts +2 -1
  263. package/dist/components/translation-review.js +2 -2
  264. package/dist/components/translation-review.js.map +1 -1
  265. package/dist/components/treemap-labels.d.ts +26 -0
  266. package/dist/components/treemap-labels.js +10 -0
  267. package/dist/components/treemap-labels.js.map +1 -0
  268. package/dist/components/treemap-layout.d.ts +62 -0
  269. package/dist/components/treemap-layout.js +126 -0
  270. package/dist/components/treemap-layout.js.map +1 -0
  271. package/dist/components/treemap.d.ts +140 -17
  272. package/dist/components/treemap.js +324 -97
  273. package/dist/components/treemap.js.map +1 -1
  274. package/dist/components/use-table-state.d.ts +5 -5
  275. package/dist/components/use-table-state.js +4 -1
  276. package/dist/components/use-table-state.js.map +1 -1
  277. package/dist/data-table.d.ts +6 -2
  278. package/dist/data-table.js +7 -2
  279. package/dist/data-table.js.map +1 -1
  280. package/dist/eslint/index.js +1 -1
  281. package/dist/eslint/role-utilities.d.ts +36 -7
  282. package/dist/eslint/role-utilities.js +42 -9
  283. package/dist/eslint/role-utilities.js.map +1 -1
  284. package/dist/feedback/feedback-dialog.d.ts +25 -16
  285. package/dist/feedback/feedback-dialog.js +6 -2
  286. package/dist/feedback/feedback-dialog.js.map +1 -1
  287. package/dist/feedback/feedback-mutation.d.ts +30 -0
  288. package/dist/feedback/feedback-mutation.js +16 -0
  289. package/dist/feedback/feedback-mutation.js.map +1 -0
  290. package/dist/feedback/feedback-page-link.d.ts +53 -0
  291. package/dist/feedback/feedback-page-link.js +37 -0
  292. package/dist/feedback/feedback-page-link.js.map +1 -0
  293. package/dist/feedback/feedback-public-list.d.ts +12 -3
  294. package/dist/feedback/feedback-public-list.js +2 -1
  295. package/dist/feedback/feedback-public-list.js.map +1 -1
  296. package/dist/feedback/feedback-replies.d.ts +3 -1
  297. package/dist/feedback/feedback-replies.js +5 -4
  298. package/dist/feedback/feedback-replies.js.map +1 -1
  299. package/dist/feedback/feedback-row-detail-labels.js +5 -0
  300. package/dist/feedback/feedback-row-detail-labels.js.map +1 -1
  301. package/dist/feedback/feedback-row-detail.d.ts +64 -28
  302. package/dist/feedback/feedback-row-detail.js +30 -28
  303. package/dist/feedback/feedback-row-detail.js.map +1 -1
  304. package/dist/feedback/feedback-status-undo.d.ts +30 -11
  305. package/dist/feedback/feedback-status-undo.js +10 -15
  306. package/dist/feedback/feedback-status-undo.js.map +1 -1
  307. package/dist/feedback/feedback-submit-dialog.d.ts +1 -1
  308. package/dist/feedback/feedback-submit-dialog.js +1 -1
  309. package/dist/feedback/feedback-submit-dialog.js.map +1 -1
  310. package/dist/feedback/feedback-submit.js +9 -3
  311. package/dist/feedback/feedback-submit.js.map +1 -1
  312. package/dist/feedback/feedback-swipe.d.ts +1 -1
  313. package/dist/feedback/feedback-swipe.js.map +1 -1
  314. package/dist/feedback/feedback-table.d.ts +21 -9
  315. package/dist/feedback/feedback-table.js +45 -25
  316. package/dist/feedback/feedback-table.js.map +1 -1
  317. package/dist/feedback/feedback-thread-labels.d.ts +2 -2
  318. package/dist/feedback/feedback-thread-labels.js.map +1 -1
  319. package/dist/feedback/feedback-thread.d.ts +24 -23
  320. package/dist/feedback/feedback-thread.js +2 -1
  321. package/dist/feedback/feedback-thread.js.map +1 -1
  322. package/dist/feedback.d.ts +34 -0
  323. package/dist/feedback.js +42 -0
  324. package/dist/feedback.js.map +1 -1
  325. package/dist/hooks/use-focus-trap.d.ts +35 -0
  326. package/dist/hooks/use-focus-trap.js +6 -4
  327. package/dist/hooks/use-focus-trap.js.map +1 -1
  328. package/dist/hooks/use-hotkey.d.ts +5 -2
  329. package/dist/hooks/use-hotkey.js +5 -4
  330. package/dist/hooks/use-hotkey.js.map +1 -1
  331. package/dist/hooks/use-long-press.d.ts +7 -4
  332. package/dist/hooks/use-long-press.js +3 -0
  333. package/dist/hooks/use-long-press.js.map +1 -1
  334. package/dist/hooks/use-media-query.js +12 -4
  335. package/dist/hooks/use-media-query.js.map +1 -1
  336. package/dist/hooks/use-mounted.d.ts +22 -0
  337. package/dist/hooks/use-mounted.js +24 -0
  338. package/dist/hooks/use-mounted.js.map +1 -0
  339. package/dist/hooks/use-overlay-history.js +19 -3
  340. package/dist/hooks/use-overlay-history.js.map +1 -1
  341. package/dist/i18n/defaults.js +4 -5
  342. package/dist/i18n/defaults.js.map +1 -1
  343. package/dist/i18n/german.js +7 -1
  344. package/dist/i18n/german.js.map +1 -1
  345. package/dist/i18n/kit-labels.d.ts +9 -2
  346. package/dist/i18n/kit-labels.js.map +1 -1
  347. package/dist/i18n/locales/en.js +20 -41
  348. package/dist/i18n/locales/en.js.map +3 -3
  349. package/dist/i18n/locales/es.js +7 -1
  350. package/dist/i18n/locales/es.js.map +1 -1
  351. package/dist/i18n/locales/fr.js +20 -10
  352. package/dist/i18n/locales/fr.js.map +1 -1
  353. package/dist/i18n/locales/hu.js +9 -1
  354. package/dist/i18n/locales/hu.js.map +1 -1
  355. package/dist/i18n/locales/it.js +7 -1
  356. package/dist/i18n/locales/it.js.map +1 -1
  357. package/dist/i18n/locales/zh.js +8 -1
  358. package/dist/i18n/locales/zh.js.map +1 -1
  359. package/dist/i18n/review.js +5 -0
  360. package/dist/i18n/review.js.map +1 -1
  361. package/dist/index.d.ts +64 -17
  362. package/dist/index.js +77 -12
  363. package/dist/index.js.map +1 -1
  364. package/dist/landing/routing.d.ts +3 -1
  365. package/dist/landing/routing.js +4 -7
  366. package/dist/landing/routing.js.map +1 -1
  367. package/dist/lib/broadcast.d.ts +17 -0
  368. package/dist/lib/broadcast.js +30 -0
  369. package/dist/lib/broadcast.js.map +1 -0
  370. package/dist/lib/date-format.d.ts +37 -2
  371. package/dist/lib/date-format.js +34 -8
  372. package/dist/lib/date-format.js.map +1 -1
  373. package/dist/lib/day-format.d.ts +46 -0
  374. package/dist/lib/day-format.js +46 -0
  375. package/dist/lib/day-format.js.map +1 -0
  376. package/dist/lib/describe-failure.d.ts +14 -0
  377. package/dist/lib/describe-failure.js +9 -0
  378. package/dist/lib/describe-failure.js.map +1 -0
  379. package/dist/lib/direction.d.ts +16 -0
  380. package/dist/lib/direction.js +9 -1
  381. package/dist/lib/direction.js.map +1 -1
  382. package/dist/lib/focus-left.d.ts +63 -0
  383. package/dist/lib/focus-left.js +67 -0
  384. package/dist/lib/focus-left.js.map +1 -0
  385. package/dist/lib/format.js +5 -14
  386. package/dist/lib/format.js.map +1 -1
  387. package/dist/lib/http-status.d.ts +24 -0
  388. package/dist/lib/http-status.js +14 -0
  389. package/dist/lib/http-status.js.map +1 -0
  390. package/dist/lib/intl-cache.d.ts +14 -0
  391. package/dist/lib/intl-cache.js +15 -0
  392. package/dist/lib/intl-cache.js.map +1 -0
  393. package/dist/lib/isin.d.ts +10 -0
  394. package/dist/lib/isin.js.map +1 -1
  395. package/dist/lib/iso-day.d.ts +11 -0
  396. package/dist/lib/iso-day.js +8 -0
  397. package/dist/lib/iso-day.js.map +1 -0
  398. package/dist/lib/lazy-with-fallback.d.ts +31 -0
  399. package/dist/lib/lazy-with-fallback.js +40 -0
  400. package/dist/lib/lazy-with-fallback.js.map +1 -0
  401. package/dist/lib/path-pattern.d.ts +34 -0
  402. package/dist/lib/path-pattern.js +24 -0
  403. package/dist/lib/path-pattern.js.map +1 -0
  404. package/dist/lib/reduced-motion.d.ts +2 -1
  405. package/dist/lib/reduced-motion.js.map +1 -1
  406. package/dist/lib/round.d.ts +2 -1
  407. package/dist/lib/round.js +2 -1
  408. package/dist/lib/round.js.map +1 -1
  409. package/dist/lib/safe-storage.d.ts +24 -5
  410. package/dist/lib/safe-storage.js +27 -2
  411. package/dist/lib/safe-storage.js.map +1 -1
  412. package/dist/lib/session-refresh.js +1 -1
  413. package/dist/lib/session-refresh.js.map +1 -1
  414. package/dist/lib/share-format.d.ts +13 -0
  415. package/dist/lib/share-format.js +10 -0
  416. package/dist/lib/share-format.js.map +1 -0
  417. package/dist/lib/text-case.d.ts +17 -0
  418. package/dist/lib/text-case.js +8 -0
  419. package/dist/lib/text-case.js.map +1 -0
  420. package/dist/lib/text-match.d.ts +29 -0
  421. package/dist/lib/text-match.js +20 -2
  422. package/dist/lib/text-match.js.map +1 -1
  423. package/dist/lib/thenable.d.ts +13 -0
  424. package/dist/lib/thenable.js +8 -0
  425. package/dist/lib/thenable.js.map +1 -0
  426. package/dist/lib/translation-review.d.ts +7 -2
  427. package/dist/lib/translation-review.js +4 -4
  428. package/dist/lib/translation-review.js.map +1 -1
  429. package/dist/notifications/device-zone-report.js +1 -1
  430. package/dist/notifications/device-zone-report.js.map +1 -1
  431. package/dist/notifications/notification-bell.d.ts +2 -1
  432. package/dist/notifications/notification-bell.js +3 -3
  433. package/dist/notifications/notification-bell.js.map +1 -1
  434. package/dist/notifications/notification-inbox.d.ts +10 -6
  435. package/dist/notifications/notification-inbox.js +26 -48
  436. package/dist/notifications/notification-inbox.js.map +1 -1
  437. package/dist/notifications/notification-parts.d.ts +3 -2
  438. package/dist/notifications/notification-parts.js +1 -3
  439. package/dist/notifications/notification-parts.js.map +1 -1
  440. package/dist/notifications/notification-types-setting.js +1 -1
  441. package/dist/notifications/notification-types-setting.js.map +1 -1
  442. package/dist/notifications/notifications-labels.d.ts +6 -0
  443. package/dist/notifications/notifications-labels.js +1 -0
  444. package/dist/notifications/notifications-labels.js.map +1 -1
  445. package/dist/notifications/push-device-setting.js +1 -1
  446. package/dist/notifications/push-device-setting.js.map +1 -1
  447. package/dist/notifications/push-devices-setting.d.ts +2 -1
  448. package/dist/notifications/push-devices-setting.js +27 -49
  449. package/dist/notifications/push-devices-setting.js.map +1 -1
  450. package/dist/notifications/quiet-hours-setting.d.ts +10 -3
  451. package/dist/notifications/quiet-hours-setting.js +65 -26
  452. package/dist/notifications/quiet-hours-setting.js.map +1 -1
  453. package/dist/notifications/unsubscribe-panel.js +4 -7
  454. package/dist/notifications/unsubscribe-panel.js.map +1 -1
  455. package/dist/notifications/use-push-device.d.ts +39 -1
  456. package/dist/notifications/use-push-device.js +28 -7
  457. package/dist/notifications/use-push-device.js.map +1 -1
  458. package/dist/offline/query-persistence.d.ts +3 -1
  459. package/dist/offline/query-persistence.js +19 -2
  460. package/dist/offline/query-persistence.js.map +1 -1
  461. package/dist/rhf/fields.d.ts +11 -4
  462. package/dist/rhf/fields.js.map +1 -1
  463. package/dist/search/command-palette.js +1 -1
  464. package/dist/search/command-palette.js.map +1 -1
  465. package/dist/search/global-search.js +19 -9
  466. package/dist/search/global-search.js.map +1 -1
  467. package/dist/search/use-command-key.d.ts +12 -1
  468. package/dist/search/use-command-key.js +3 -12
  469. package/dist/search/use-command-key.js.map +1 -1
  470. package/dist/search.d.ts +7 -0
  471. package/dist/search.js +10 -0
  472. package/dist/search.js.map +1 -1
  473. package/dist/settings/use-settings-focus.js +1 -0
  474. package/dist/settings/use-settings-focus.js.map +1 -1
  475. package/dist/shell/account-menu-language.d.ts +21 -5
  476. package/dist/shell/account-menu-language.js +3 -1
  477. package/dist/shell/account-menu-language.js.map +1 -1
  478. package/dist/shell/account-menu-text-size.d.ts +26 -9
  479. package/dist/shell/account-menu-text-size.js +3 -2
  480. package/dist/shell/account-menu-text-size.js.map +1 -1
  481. package/dist/shell/dev-menu.js +5 -18
  482. package/dist/shell/dev-menu.js.map +1 -1
  483. package/dist/shell/role-switcher.d.ts +7 -0
  484. package/dist/shell/role-switcher.js.map +1 -1
  485. package/dist/shell/top-bar-actions.d.ts +1 -1
  486. package/dist/shell/top-bar-actions.js.map +1 -1
  487. package/dist/shell/top-bar-parts-labels.d.ts +0 -2
  488. package/dist/shell/top-bar-parts-labels.js +0 -1
  489. package/dist/shell/top-bar-parts-labels.js.map +1 -1
  490. package/dist/shell.d.ts +27 -8
  491. package/dist/shell.js +28 -7
  492. package/dist/shell.js.map +1 -1
  493. package/dist/sync/engine.d.ts +30 -4
  494. package/dist/sync/engine.js +172 -52
  495. package/dist/sync/engine.js.map +1 -1
  496. package/dist/sync.d.ts +6 -2
  497. package/dist/sync.js +6 -1
  498. package/dist/sync.js.map +1 -1
  499. package/dist/testing/css-variables.js +3 -8
  500. package/dist/testing/css-variables.js.map +1 -1
  501. package/dist/theme/contrast.d.ts +2 -1
  502. package/dist/theme/contrast.js +3 -8
  503. package/dist/theme/contrast.js.map +1 -1
  504. package/dist/theme/palette-store.js +6 -6
  505. package/dist/theme/palette-store.js.map +1 -1
  506. package/dist/theme/text-size.js +3 -8
  507. package/dist/theme/text-size.js.map +1 -1
  508. package/dist/theme/theme-store.js +3 -6
  509. package/dist/theme/theme-store.js.map +1 -1
  510. package/dist/tour/tour-catalogue.d.ts +7 -4
  511. package/dist/tour/tour-catalogue.js +12 -10
  512. package/dist/tour/tour-catalogue.js.map +1 -1
  513. package/dist/tour/tour-dock.d.ts +0 -2
  514. package/dist/tour/tour-dock.js +0 -2
  515. package/dist/tour/tour-dock.js.map +1 -1
  516. package/dist/tour/tour-gates.d.ts +4 -0
  517. package/dist/tour/tour-gates.js +14 -0
  518. package/dist/tour/tour-gates.js.map +1 -1
  519. package/dist/tour/tour-overlay.d.ts +27 -0
  520. package/dist/tour/tour-overlay.js +741 -0
  521. package/dist/tour/tour-overlay.js.map +1 -0
  522. package/dist/tour/tour.d.ts +13 -3
  523. package/dist/tour/tour.js +59 -781
  524. package/dist/tour/tour.js.map +1 -1
  525. package/dist/tour/use-tour-progress.js +1 -1
  526. package/dist/tour/use-tour-progress.js.map +1 -1
  527. package/dist/wizard/stepper-nav.d.ts +5 -1
  528. package/dist/wizard/stepper-nav.js.map +1 -1
  529. package/dist/wizard/types.d.ts +6 -2
  530. package/dist/wizard/types.js.map +1 -1
  531. package/dist/wizard/use-wizard.d.ts +3 -1
  532. package/dist/wizard/use-wizard.js.map +1 -1
  533. package/dist/wizard/validation.d.ts +3 -1
  534. package/dist/wizard/validation.js.map +1 -1
  535. package/package.json +10 -6
  536. package/src/account/account-parts.ts +5 -25
  537. package/src/account/data-export-setting-labels.ts +1 -1
  538. package/src/account/delete-account-setting-labels.ts +1 -1
  539. package/src/account/email-change-setting-labels.ts +1 -1
  540. package/src/account/passkeys-setting.tsx +60 -38
  541. package/src/account/sessions-setting.tsx +18 -12
  542. package/src/admin/admin-action-confirm.tsx +1 -5
  543. package/src/admin/admin-parts.ts +11 -25
  544. package/src/admin/mail-test-card-labels.ts +1 -1
  545. package/src/admin/plan-change-labels.ts +5 -23
  546. package/src/admin/plan-change.tsx +7 -44
  547. package/src/admin/reviewer-scope-editor.tsx +3 -3
  548. package/src/auth/auth-errors-labels.ts +23 -0
  549. package/src/auth/auth-errors.ts +11 -38
  550. package/src/auth/complete-name-dialog.tsx +4 -1
  551. package/src/auth/forgot-password-form-labels.ts +1 -1
  552. package/src/auth/forgot-password-form.tsx +11 -6
  553. package/src/auth/register-form-labels.ts +1 -1
  554. package/src/auth/reset-password-form-labels.ts +1 -1
  555. package/src/auth/reset-password-form.tsx +8 -4
  556. package/src/auth/sign-in-form-labels.ts +1 -1
  557. package/src/auth/status-parts.tsx +0 -6
  558. package/src/auth/verify-email-labels.ts +1 -1
  559. package/src/auth/verify-email.tsx +11 -6
  560. package/src/billing/cancel-subscription.tsx +26 -12
  561. package/src/billing/dialog-close.ts +34 -0
  562. package/src/billing/plan-card.tsx +36 -11
  563. package/src/billing/plan-change-labels.ts +66 -0
  564. package/src/billing/plan-switch.tsx +26 -12
  565. package/src/chart.ts +21 -3
  566. package/src/components/account-chips.tsx +20 -23
  567. package/src/components/alert-banner.tsx +82 -5
  568. package/src/components/amount-input.tsx +18 -7
  569. package/src/components/autocomplete.tsx +64 -70
  570. package/src/components/bulk-action-bar.tsx +70 -2
  571. package/src/components/button-look.ts +5 -5
  572. package/src/components/button.tsx +2 -2
  573. package/src/components/chart-edge.ts +34 -1
  574. package/src/components/chart-figure.tsx +35 -0
  575. package/src/components/chart-zoom-core.tsx +6 -5
  576. package/src/components/chart.tsx +26 -5
  577. package/src/components/combobox-core.tsx +293 -62
  578. package/src/components/combobox.tsx +183 -153
  579. package/src/components/copy-button.tsx +34 -23
  580. package/src/components/country-select.tsx +16 -11
  581. package/src/components/data-table-column-settings.tsx +52 -10
  582. package/src/components/data-table-filters.ts +6 -4
  583. package/src/components/data-table-fit.ts +19 -5
  584. package/src/components/data-table-groups.tsx +1 -1
  585. package/src/components/data-table-head.tsx +32 -22
  586. package/src/components/data-table-mobile.tsx +61 -26
  587. package/src/components/data-table-pagination.tsx +21 -6
  588. package/src/components/data-table-resize.ts +1 -1
  589. package/src/components/data-table-row-actions.tsx +3 -3
  590. package/src/components/data-table-row.tsx +25 -14
  591. package/src/components/data-table-selection.ts +1 -1
  592. package/src/components/data-table-shared.tsx +33 -1
  593. package/src/components/data-table-sort.ts +57 -2
  594. package/src/components/data-table-virtual.ts +32 -8
  595. package/src/components/data-table.tsx +239 -68
  596. package/src/components/date-field.tsx +65 -8
  597. package/src/components/date-picker.tsx +125 -48
  598. package/src/components/date-range-picker.tsx +82 -31
  599. package/src/components/error-boundary.tsx +53 -39
  600. package/src/components/field-base-labels.ts +20 -0
  601. package/src/components/field-base.tsx +13 -7
  602. package/src/components/field-sync-labels.ts +1 -0
  603. package/src/components/field-sync.tsx +23 -1
  604. package/src/components/form-actions.tsx +20 -5
  605. package/src/components/hover-menu.tsx +18 -8
  606. package/src/components/icon-button.tsx +14 -9
  607. package/src/components/inline-confirm.tsx +116 -0
  608. package/src/components/inline-entity-combobox.tsx +197 -153
  609. package/src/components/input.tsx +1 -1
  610. package/src/components/jump-calendar.tsx +30 -146
  611. package/src/components/legal-page.tsx +10 -7
  612. package/src/components/legal.tsx +4 -2
  613. package/src/components/lightbox.tsx +21 -8
  614. package/src/components/list.tsx +10 -4
  615. package/src/components/measured-grid.tsx +8 -4
  616. package/src/components/mini-calendar.tsx +6 -5
  617. package/src/components/modal.tsx +6 -1
  618. package/src/components/month-grid.tsx +229 -0
  619. package/src/components/month-picker.tsx +192 -200
  620. package/src/components/multi-select.tsx +21 -9
  621. package/src/components/picker-sheet.tsx +12 -9
  622. package/src/components/pie-chart-labels.ts +8 -0
  623. package/src/components/pie-chart.tsx +22 -28
  624. package/src/components/popover.tsx +18 -1
  625. package/src/components/row-actions.tsx +2 -1
  626. package/src/components/section-nav.tsx +7 -0
  627. package/src/components/series-chart-axes.ts +3 -3
  628. package/src/components/series-chart-labels.ts +7 -4
  629. package/src/components/series-chart.tsx +178 -69
  630. package/src/components/share-card.tsx +6 -6
  631. package/src/components/sign-chip-labels.ts +1 -0
  632. package/src/components/sign-chip.tsx +10 -1
  633. package/src/components/tabs-labels.ts +13 -0
  634. package/src/components/tabs.tsx +2 -2
  635. package/src/components/toggle-legend.tsx +16 -11
  636. package/src/components/translation-review-editor.tsx +6 -5
  637. package/src/components/translation-review.tsx +4 -3
  638. package/src/components/treemap-labels.ts +39 -0
  639. package/src/components/treemap-layout.ts +210 -0
  640. package/src/components/treemap.tsx +722 -133
  641. package/src/components/use-table-state.ts +12 -9
  642. package/src/data-table.ts +9 -2
  643. package/src/eslint/role-utilities.ts +97 -19
  644. package/src/feedback/feedback-dialog.tsx +30 -19
  645. package/src/feedback/feedback-mutation.ts +41 -0
  646. package/src/feedback/feedback-page-link.ts +90 -0
  647. package/src/feedback/feedback-public-list.tsx +17 -5
  648. package/src/feedback/feedback-replies.tsx +13 -6
  649. package/src/feedback/feedback-row-detail-labels.ts +5 -0
  650. package/src/feedback/feedback-row-detail.tsx +103 -67
  651. package/src/feedback/feedback-status-undo.ts +41 -27
  652. package/src/feedback/feedback-submit-dialog.tsx +2 -2
  653. package/src/feedback/feedback-submit.tsx +21 -7
  654. package/src/feedback/feedback-swipe.tsx +1 -1
  655. package/src/feedback/feedback-table.tsx +86 -38
  656. package/src/feedback/feedback-thread-labels.ts +2 -2
  657. package/src/feedback/feedback-thread.tsx +27 -25
  658. package/src/feedback.ts +42 -0
  659. package/src/hooks/use-focus-trap.ts +8 -24
  660. package/src/hooks/use-hotkey.ts +24 -7
  661. package/src/hooks/use-long-press.ts +16 -2
  662. package/src/hooks/use-media-query.ts +20 -4
  663. package/src/hooks/use-mounted.ts +39 -0
  664. package/src/hooks/use-overlay-history.ts +32 -3
  665. package/src/i18n/defaults.ts +4 -5
  666. package/src/i18n/german.ts +8 -2
  667. package/src/i18n/kit-labels.tsx +9 -2
  668. package/src/i18n/locales/es.ts +8 -2
  669. package/src/i18n/locales/fr.ts +21 -13
  670. package/src/i18n/locales/hu.ts +10 -2
  671. package/src/i18n/locales/it.ts +8 -2
  672. package/src/i18n/locales/zh.ts +9 -2
  673. package/src/i18n/review.ts +5 -0
  674. package/src/index.ts +103 -13
  675. package/src/landing/routing.tsx +14 -10
  676. package/src/lib/broadcast.ts +52 -0
  677. package/src/lib/date-format.ts +89 -13
  678. package/src/lib/day-format.ts +96 -0
  679. package/src/lib/describe-failure.ts +18 -0
  680. package/src/lib/direction.ts +39 -0
  681. package/src/lib/focus-left.ts +156 -0
  682. package/src/lib/format.ts +5 -17
  683. package/src/lib/http-status.ts +38 -0
  684. package/src/lib/intl-cache.ts +25 -0
  685. package/src/lib/isin.ts +10 -0
  686. package/src/lib/iso-day.ts +13 -0
  687. package/src/lib/lazy-with-fallback.ts +82 -0
  688. package/src/lib/path-pattern.ts +55 -0
  689. package/src/lib/reduced-motion.ts +2 -1
  690. package/src/lib/round.ts +7 -2
  691. package/src/lib/safe-storage.ts +48 -6
  692. package/src/lib/session-refresh.ts +1 -1
  693. package/src/lib/share-format.ts +18 -0
  694. package/src/lib/text-case.ts +19 -0
  695. package/src/lib/text-match.ts +57 -2
  696. package/src/lib/thenable.ts +19 -0
  697. package/src/lib/translation-review.ts +13 -5
  698. package/src/notifications/device-zone-report.ts +1 -1
  699. package/src/notifications/notification-bell.tsx +7 -4
  700. package/src/notifications/notification-inbox.tsx +40 -58
  701. package/src/notifications/notification-parts.tsx +3 -4
  702. package/src/notifications/notification-types-setting.tsx +1 -1
  703. package/src/notifications/notifications-labels.ts +7 -0
  704. package/src/notifications/push-device-setting.tsx +1 -1
  705. package/src/notifications/push-devices-setting.tsx +30 -55
  706. package/src/notifications/quiet-hours-setting.tsx +93 -31
  707. package/src/notifications/unsubscribe-panel.tsx +4 -7
  708. package/src/notifications/use-push-device.ts +76 -10
  709. package/src/offline/query-persistence.ts +35 -3
  710. package/src/rhf/fields.tsx +11 -4
  711. package/src/search/command-palette.tsx +2 -4
  712. package/src/search/global-search.tsx +36 -15
  713. package/src/search/use-command-key.ts +13 -12
  714. package/src/search.ts +9 -0
  715. package/src/settings/use-settings-focus.ts +3 -0
  716. package/src/shell/account-menu-language.tsx +27 -5
  717. package/src/shell/account-menu-text-size.tsx +32 -10
  718. package/src/shell/dev-menu.tsx +7 -21
  719. package/src/shell/role-switcher.tsx +7 -0
  720. package/src/shell/top-bar-actions.tsx +1 -1
  721. package/src/shell/top-bar-parts-labels.ts +0 -3
  722. package/src/shell.ts +40 -9
  723. package/src/sync/engine.ts +339 -61
  724. package/src/sync.ts +8 -1
  725. package/src/testing/css-variables.ts +8 -9
  726. package/src/theme/contrast.ts +8 -9
  727. package/src/theme/palette-store.ts +9 -9
  728. package/src/theme/text-size.ts +6 -8
  729. package/src/theme/theme-store.ts +7 -9
  730. package/src/tour/tour-catalogue.tsx +26 -15
  731. package/src/tour/tour-dock.ts +0 -2
  732. package/src/tour/tour-gates.ts +23 -0
  733. package/src/tour/tour-overlay.tsx +1170 -0
  734. package/src/tour/tour.tsx +108 -1202
  735. package/src/tour/use-tour-progress.ts +1 -1
  736. package/src/wizard/stepper-nav.tsx +3 -1
  737. package/src/wizard/types.ts +11 -2
  738. package/src/wizard/use-wizard.ts +2 -1
  739. package/src/wizard/validation.ts +8 -2
  740. package/tokens.css +2 -1
  741. package/dist/components/ui-labels.js.map +0 -1
  742. package/dist/shell/dev-switchers.d.ts +0 -6
  743. package/dist/shell/dev-switchers.js +0 -12
  744. package/dist/shell/dev-switchers.js.map +0 -1
  745. package/src/components/ui-labels.ts +0 -24
  746. package/src/shell/dev-switchers.tsx +0 -18
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/month-picker.tsx"],"sourcesContent":["import { useEffect, useId, useMemo, useRef, useState } from \"react\";\nimport type { ComponentPropsWithoutRef, KeyboardEvent, ReactNode, Ref, RefObject } from \"react\";\nimport { Calendar, ChevronDown, ChevronLeft, ChevronRight } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { dirOf, horizontalStep, type Direction } from \"../lib/direction\";\nimport { monthKey, pad } from \"../lib/dates\";\nimport { Button } from \"./button\";\nimport { FIELD_FLOATING_PAD, FIELD_INVALID, FIELD_TRIGGER } from \"./field-base\";\nimport { IconButton } from \"./icon-button\";\nimport { FOCUS_RING, FOCUS_RING_WIDTH } from \"./focus-ring\";\nimport { Popover } from \"./popover\";\nimport { splitTriggerAria } from \"./trigger-aria\";\nimport type { TriggerAria } from \"./trigger-aria\";\nimport { useKitDateFormatter, useKitLabels, useKitLocale, type LabelOverride } from \"../i18n/kit-labels\";\nimport { hasContent } from \"../lib/presence\";\nimport {\n FieldBox,\n FieldLabelLine,\n LABEL_ABOVE_BOX,\n mergeRefs,\n useFieldMessages,\n type FieldHelpProps,\n} from \"./field-parts\";\nimport { DEFAULT_MONTH_PICKER_LABELS } from \"./month-picker-labels\";\n\n/**\n * Every string the month picker can speak. The month and year names are NOT here —\n * they come from `Intl` in the `locale` prop, so they are already in the user's\n * language. What is left is the two icon-only year arrows, whose `aria-label` is the\n * only name they have, the panel's name, and a month cell's name.\n */\nexport interface MonthPickerLabels {\n previousYear: string;\n nextYear: string;\n /** The popover's accessible name (`role=\"dialog\"`). */\n panel: string;\n /**\n * Accessible name of one month cell, given that month and its year formatted in\n * `locale` (\"August 2026\"). The cell's visible text is the short month alone, and\n * \"Aug\" read out on its own does not say which year the grid is on.\n */\n month: (monthYear: string) => string;\n /** `variant=\"stepper\"`: the icon-only arrows that step the value by one month. */\n previousMonth: string;\n nextMonth: string;\n /** `variant=\"stepper\"`: the button that jumps to the current month. Visible text. */\n today: string;\n /** `mode=\"year\"`: the popover's accessible name. */\n yearPanel: string;\n /** `mode=\"year\"`: the icon-only arrows that page the year grid back and forward by a\n * page of twelve years. */\n earlierYears: string;\n laterYears: string;\n /** `mode=\"year\"`, `variant=\"stepper\"`: the button that jumps to the current year.\n * Visible text. */\n thisYear: string;\n}\n\n// Defined in ./month-picker-labels, which holds no component code (0.34, KS U8).\nexport { DEFAULT_MONTH_PICKER_LABELS };\n\n/** Merge caller overrides onto the English defaults. */\nfunction useMonthPickerLabels(partial?: Partial<MonthPickerLabels>): MonthPickerLabels {\n // Prop > <UiKitProvider> > English, like every labelled component in the kit.\n return useKitLabels(\"monthPicker\", DEFAULT_MONTH_PICKER_LABELS, partial);\n}\n\n/**\n * `onChange` is omitted from the `<div>` attributes: the DOM event of that name carries\n * a `FormEvent`, and this one answers with a month key. Everything else reaches the\n * field's root, so a test id or a `data-tour` anchor can find it.\n */\nexport interface MonthPickerProps extends Omit<ComponentPropsWithoutRef<\"div\">, \"onChange\">, FieldHelpProps {\n /**\n * The selected month as a `\"YYYY-MM\"` key, or `\"\"` for none.\n *\n * A string rather than `{ year, month }` or a `Date`, for the reason every date in\n * this kit is one: it is what `monthKey()` in `lib/dates` produces, it sorts\n * chronologically so the bounds are plain `<` / `>`, and it crosses a URL or an API\n * without a serialiser. A `{ year, month }` caller converts with\n * `` `${year}-${pad(month)}` `` (month 1–12) and back with `key.split(\"-\").map(Number)`.\n */\n value: string;\n /** Called with the picked month as `\"YYYY-MM\"`. */\n onChange: (month: string) => void;\n /** Drives the month and year names (`Intl.DateTimeFormat`). Defaults to the\n * `<UiKitProvider>` locale, then the runtime's. */\n locale?: string;\n /**\n * Inclusive bounds, as `\"YYYY-MM\"`. A full ISO date (`\"YYYY-MM-DD\"`) is accepted and\n * read as its month, so a host holding \"the first day we have data for\" can pass it\n * straight in. Months outside are shown but refuse selection; a year arrow is\n * disabled once the whole year beyond it is out of bounds.\n */\n min?: string;\n max?: string;\n /** Embedded top-boundary label, matching the other field-shaped controls. */\n label?: ReactNode;\n /** Trigger text when `value` is `\"\"`. */\n placeholder?: string;\n disabled?: boolean;\n /** Required and unanswered — {@link FIELD_INVALID}. See {@link Input}'s `invalid`. */\n invalid?: boolean;\n /**\n * What is wrong with the month, in the caller's words — {@link Input}'s `error`\n * (keksdose K4): under the field, attached to the trigger with `aria-describedby`\n * (merged, after any caption), and implies `invalid`. Passing the key keeps the\n * field's box while there is no message, so the trigger is never remounted.\n */\n error?: ReactNode;\n /** Standing advice, as on a {@link Select}: text is a caption under the field; a\n * {@link FieldHint} rides the label line. */\n hint?: ReactNode;\n /**\n * `\"md\"` (default): the form field. `\"sm\"`: the toolbar trigger — 36px tall (an\n * `IconButton`'s height) and as wide as the month it shows, for a picker that sits\n * between two icon buttons in a page header (keksdose K13: the budget header wrote\n * `triggerClassName=\"h-9 w-auto … py-0\"` by hand, live #260 — 34px between two 36px\n * buttons, *\"the kind of 2px that reads as sloppy\"*). Unlabelled pickers only, as on\n * {@link Select}: a floating label needs the tall box to float in. The width follows\n * the month's name; add a `min-w-*` in `triggerClassName` if the buttons beside it\n * must not move as it changes.\n */\n size?: \"sm\" | \"md\";\n /**\n * `\"month\"` (default): the value is a month, `\"YYYY-MM\"`.\n *\n * `\"year\"`: the value is a YEAR, `\"YYYY\"`, and the panel is a grid of years — kastlan's\n * budget fiscal year (a `Select` of 2020 to three years ahead) and keksdose's tax tab\n * (\"this year and four before\", tax-tab.tsx:33). `min` / `max` are read as their year\n * (`\"2022\"`, or any longer key). A bounded span of twelve years or fewer is shown\n * whole, with no paging; otherwise the grid pages twelve years at a time (PageUp /\n * PageDown, or the arrows), starting from `min` when there is one. The stepper\n * variant steps a year, and its Today button reads \"This year\".\n *\n * A string rather than a number, for the reason the month key is one: it is the\n * same type across both modes, it crosses a URL as is, and it sorts. A numeric\n * caller converts with `String(year)` and `Number(key)`.\n */\n mode?: \"month\" | \"year\";\n /** Intl options for the trigger's text (default: long month + numeric year, or the\n * numeric year in `mode=\"year\"`). Win over the provider's `formatDate`. */\n formatOptions?: Intl.DateTimeFormatOptions;\n /**\n * The month to ring as \"now\", as `\"YYYY-MM\"`. Defaults to the local calendar's\n * current month; pass one to pin it (a test, a host with its own notion of today).\n */\n currentMonth?: string;\n /** User-facing strings; see {@link MonthPickerLabels}. */\n labels?: LabelOverride<MonthPickerLabels>;\n /** Extra classes for the field's root. */\n className?: string;\n /** Extra classes for the trigger button — e.g. a compact `h-9 w-auto` trigger\n * sitting between two icon buttons in a toolbar. */\n triggerClassName?: string;\n /**\n * `\"field\"` (default): the field-shaped trigger, for a form.\n *\n * `\"stepper\"`: the header of a month PAGE — the month as a heading that opens the\n * grid, then Today and ‹ › stepping the value by one month. What a calendar page\n * assembled by hand around the field (overriding its trigger to look like a\n * heading). `label`, `placeholder`, `invalid`, `hint`, `error`, `size` and\n * `triggerClassName` are the field's and are not drawn here; with no `value` the\n * arrows step from the current month (or year).\n */\n variant?: \"field\" | \"stepper\";\n /** `variant=\"stepper\"`: the heading level the month is set in (`<h2>` by default), or\n * `false` for no heading element — the page already has one for the month. */\n headingLevel?: 1 | 2 | 3 | 4 | 5 | 6 | false;\n /** `variant=\"stepper\"`: draw the Today button. Default `true`. */\n showToday?: boolean;\n /**\n * The TRIGGER — the field's button, or the stepper's heading button — as a React 19\n * ref prop (0.34, API-3, refactor audit 2026-10). The picker took none, so\n * react-hook-form's focus-on-error reached it only through /rhf's id lookup. Not the\n * wrapper, which takes the rest of the props.\n */\n ref?: Ref<HTMLButtonElement>;\n}\n\n// ── Month keys ────────────────────────────────────────────────────────────\n\ninterface YearMonth {\n year: number;\n /** 1–12, as in the key. */\n month: number;\n}\n\nconst KEY = /^(\\d{4})-(\\d{2})/;\n\nfunction parseMonth(key: string | undefined): YearMonth | null {\n const m = key ? KEY.exec(key) : null;\n if (!m) return null;\n const month = Number(m[2]);\n return month >= 1 && month <= 12 ? { year: Number(m[1]), month } : null;\n}\n\nfunction toKey({ year, month }: YearMonth): string {\n return `${year}-${pad(month)}`;\n}\n\n/** Move a month key by whole months, carrying into the year. */\nfunction shiftMonth(ym: YearMonth, delta: number): YearMonth {\n const index = ym.year * 12 + (ym.month - 1) + delta;\n return { year: Math.floor(index / 12), month: (index % 12) + 1 };\n}\n\n/** A bound, normalised to its month key — so `\"2024-03-15\"` compares as `\"2024-03\"`. */\nfunction boundKey(bound: string | undefined): string | null {\n const ym = parseMonth(bound);\n return ym ? toKey(ym) : null;\n}\n\n/** Local-calendar Date for the 1st of a month, for `Intl` to format. */\nfunction firstOf({ year, month }: YearMonth): Date {\n return new Date(year, month - 1, 1);\n}\n\nconst DEFAULT_TRIGGER_FORMAT: Intl.DateTimeFormatOptions = { month: \"long\", year: \"numeric\" };\nconst MONTH_NAME_FORMAT: Intl.DateTimeFormatOptions = { month: \"long\", year: \"numeric\" };\nconst YEAR_FORMAT: Intl.DateTimeFormatOptions = { year: \"numeric\" };\n\n// ── Year keys ─────────────────────────────────────────────────────────────\n\nconst YEAR = /^(\\d{4})/;\n\n/** The year of `\"YYYY\"`, `\"YYYY-MM\"` or `\"YYYY-MM-DD\"`, or null. */\nfunction parseYear(key: string | undefined): number | null {\n const m = key ? YEAR.exec(key) : null;\n return m ? Number(m[1]) : null;\n}\n\n// ── Trigger ───────────────────────────────────────────────────────────────\n\n/** Hand focus back to the trigger when the panel closes with focus still inside it. */\nfunction useFocusBackOnClose(open: boolean, triggerRef: RefObject<HTMLButtonElement | null>) {\n const wasOpen = useRef(false);\n useEffect(() => {\n const justClosed = wasOpen.current && !open;\n wasOpen.current = open;\n if (!justClosed) return;\n // Only when focus went down with the panel. An outside click has already put it\n // somewhere real, and dragging it back here would be worse than leaving it.\n const active = document.activeElement;\n if (!active || active === document.body) triggerRef.current?.focus();\n }, [open, triggerRef]);\n}\n\n/** The Popover's handle on the trigger and the picker's own `ref` prop (API-3), as one. */\nfunction useTriggerRef(triggerRef: RefObject<HTMLButtonElement | null>, fieldRef: Ref<HTMLButtonElement> | undefined) {\n return useMemo(() => mergeRefs(triggerRef, fieldRef), [triggerRef, fieldRef]);\n}\n\n/**\n * The stepper's trigger: the month as a heading-sized button that opens the grid.\n *\n * Its own text is its name (\"September 2026\"), so it needs no label of its own, and\n * it is `aria-live` for the reason `MiniCalendar`'s caption is: pressing ‹ or › leaves\n * focus on the arrow, and the heading is the only thing that says where the arrow\n * went. A plain button with `aria-haspopup`, not the field's `combobox` — it does not\n * look or behave like a field, and a heading reading \"combobox\" is a surprise.\n */\nfunction MonthHeadingTrigger({\n open,\n toggle,\n triggerRef,\n fieldRef,\n text,\n panelId,\n disabled,\n aria,\n}: {\n open: boolean;\n toggle: () => void;\n triggerRef: RefObject<HTMLButtonElement | null>;\n fieldRef?: Ref<HTMLButtonElement>;\n text: string;\n panelId: string;\n disabled?: boolean;\n aria: TriggerAria;\n}) {\n useFocusBackOnClose(open, triggerRef);\n const setTrigger = useTriggerRef(triggerRef, fieldRef);\n return (\n <button\n ref={setTrigger}\n type=\"button\"\n disabled={disabled}\n onClick={toggle}\n aria-haspopup=\"dialog\"\n aria-controls={panelId}\n aria-expanded={open}\n id={aria.id}\n aria-label={aria[\"aria-label\"]}\n aria-labelledby={aria[\"aria-labelledby\"]}\n aria-describedby={aria[\"aria-describedby\"]}\n className={cn(\n \"-ms-1.5 inline-flex h-9 min-w-0 items-center gap-1 rounded-md px-1.5 text-lg font-semibold text-primary transition-colors\",\n \"hover:bg-hover\",\n FOCUS_RING,\n disabled && \"cursor-not-allowed opacity-50\",\n )}\n >\n <span aria-live=\"polite\" className=\"truncate\">\n {text || \"\\u00a0\"}\n </span>\n <ChevronDown aria-hidden className=\"size-4 shrink-0 text-muted\" />\n </button>\n );\n}\n\n/**\n * The field-shaped button that opens the grid. The same shape as `DatePicker`'s\n * trigger, for the same reasons written down there: `role=\"combobox\"` so it may carry\n * `aria-expanded` / `aria-invalid`, a name made of the label AND the value, and focus\n * handed back here when the panel closes with focus still inside it.\n */\nfunction MonthFieldTrigger({\n open,\n toggle,\n triggerRef,\n fieldRef,\n triggerText,\n hasValue,\n labelledBy,\n valueId,\n panelId,\n padded,\n small,\n disabled,\n invalid,\n className,\n aria,\n}: {\n open: boolean;\n toggle: () => void;\n triggerRef: RefObject<HTMLButtonElement | null>;\n fieldRef?: Ref<HTMLButtonElement>;\n triggerText: string;\n hasValue: boolean;\n labelledBy: string;\n /** The caller's naming attributes — see `trigger-aria.ts`. */\n aria: TriggerAria;\n valueId: string;\n panelId: string;\n padded: boolean;\n /** `size=\"sm\"`: the 36px, content-wide toolbar trigger. */\n small: boolean;\n disabled?: boolean;\n invalid?: boolean;\n className?: string;\n}) {\n useFocusBackOnClose(open, triggerRef);\n const setTrigger = useTriggerRef(triggerRef, fieldRef);\n\n return (\n <button\n ref={setTrigger}\n type=\"button\"\n disabled={disabled}\n onClick={toggle}\n role=\"combobox\"\n aria-haspopup=\"dialog\"\n aria-controls={panelId}\n aria-expanded={open}\n id={aria.id}\n aria-invalid={invalid || aria[\"aria-invalid\"] === true || aria[\"aria-invalid\"] === \"true\" || undefined}\n aria-required={aria[\"aria-required\"]}\n aria-labelledby={aria[\"aria-label\"] && !aria[\"aria-labelledby\"] ? undefined : labelledBy}\n aria-label={aria[\"aria-label\"]}\n aria-describedby={aria[\"aria-describedby\"]}\n className={cn(\n FIELD_TRIGGER,\n \"pe-9\",\n padded && FIELD_FLOATING_PAD,\n // keksdose K13 (live #260): an IconButton's 36px, and only as wide as the\n // month — `py-0` with a fixed height so the box is 36px whatever the type's\n // line height. `pe-9` stays: the calendar glyph still sits at the end.\n small && \"h-9 w-auto py-0\",\n disabled && \"cursor-not-allowed opacity-50\",\n invalid && FIELD_INVALID,\n className,\n )}\n >\n {/* The U+00A0 fallback keeps the line box when there is no text — see\n DatePicker. The value arrives capitalised (see `upperFirst`); this span does\n NOT use `capitalize`, which upper-cases every WORD — \"Choose A Month\",\n \"Septiembre De 2026\". It wraps from Large up, as DatePicker's value does\n (`truncate-until-large`, text size §10.17; round 0.34 T1: the sweep read\n \"Choose a mo…\" at 360 px / Extra large). */}\n <span id={valueId} className={cn(\"truncate-until-large\", !hasValue && \"text-placeholder\")}>\n {triggerText || \" \"}\n </span>\n </button>\n );\n}\n\n// ── Grid ──────────────────────────────────────────────────────────────────\n\nconst COLUMNS = 3;\n\n/**\n * The panel: a year caption between ‹ › arrows, over a 4×3 grid of months.\n *\n * Mounted only while the popover is open, so every opening starts from the current\n * value's year — a value moved from outside (a prev/next-month button beside the\n * field) is where the grid lands, with no effect needed to snap it back.\n *\n * A grid in the APG sense, like `MiniCalendar`: one tab stop (the roving tabindex),\n * the arrows walk the months — left/right by one, up/down by a row of three — and\n * walking off either end of the year MOVES the year rather than stopping. PageUp /\n * PageDown step a whole year. Out-of-bounds months are `aria-disabled`, not\n * `disabled`, so the roving tab stop never falls into a hole.\n */\nfunction MonthGrid({\n value,\n onPick,\n locale,\n minKey,\n maxKey,\n currentKey,\n labels,\n}: {\n value: YearMonth | null;\n onPick: (key: string) => void;\n locale: string | undefined;\n minKey: string | null;\n maxKey: string | null;\n currentKey: string;\n labels: MonthPickerLabels;\n}) {\n const outOfBounds = (key: string) => Boolean((minKey && key < minKey) || (maxKey && key > maxKey));\n\n // The month holding the tab order, and with it the year on screen — one piece of\n // state, as in MiniCalendar, so the two cannot disagree. Opens on the value, or on\n // the current month, pulled inside the bounds.\n const [active, setActive] = useState<YearMonth>(() => {\n const start = value ? toKey(value) : currentKey;\n if (minKey && start < minKey) return parseMonth(minKey)!;\n if (maxKey && start > maxKey) return parseMonth(maxKey)!;\n return parseMonth(start)!;\n });\n const year = active.year;\n\n const gridRef = useRef<HTMLDivElement>(null);\n const captionId = `${useId()}-year`;\n\n // Short names for the cells, long \"Month YYYY\" for their accessible names.\n const shortNames = useMemo(() => {\n const fmt = new Intl.DateTimeFormat(locale, { month: \"short\" });\n return Array.from({ length: 12 }, (_, i) => fmt.format(new Date(2000, i, 1)));\n }, [locale]);\n const longName = useMemo(() => new Intl.DateTimeFormat(locale, MONTH_NAME_FORMAT), [locale]);\n const yearCaption = new Intl.DateTimeFormat(locale, { year: \"numeric\" }).format(firstOf({ year, month: 1 }));\n\n // A month whose cell must take focus after the next commit. Arrowing across a year\n // boundary re-labels the same twelve buttons, so the one to focus is a DIFFERENT\n // button from the one the key landed on.\n const pendingFocus = useRef<string | null>(null);\n useEffect(() => {\n const key = pendingFocus.current;\n if (!key) return;\n pendingFocus.current = null;\n gridRef.current?.querySelector<HTMLElement>(`[data-month=\"${key}\"]`)?.focus();\n });\n\n // Opened because the user asked to pick a month: land them on it. Runs before the\n // popover's focus trap (a child's effect fires first), which then keeps this focus.\n useEffect(() => {\n gridRef.current?.querySelector<HTMLElement>('[tabindex=\"0\"]')?.focus();\n }, []);\n\n const moveTo = (next: YearMonth, focus: boolean) => {\n if (focus) pendingFocus.current = toKey(next);\n setActive(next);\n };\n\n const onCellKeyDown = (e: KeyboardEvent<HTMLButtonElement>) => {\n // Left and right are VISUAL directions: in a right-to-left page the grid runs the\n // other way, so \"right\" is the previous month there. The panel is portalled, but\n // it carries the field's `dir` (see MonthPicker), so the nearest `dir` is right.\n const step = horizontalStep(e.key, e.currentTarget);\n const col = (active.month - 1) % COLUMNS;\n let delta: number;\n switch (step ? \"horizontal\" : e.key) {\n case \"horizontal\":\n delta = step;\n break;\n case \"ArrowUp\":\n delta = -COLUMNS;\n break;\n case \"ArrowDown\":\n delta = COLUMNS;\n break;\n case \"Home\":\n delta = -col;\n break;\n case \"End\":\n delta = COLUMNS - 1 - col;\n break;\n case \"PageUp\":\n delta = -12;\n break;\n case \"PageDown\":\n delta = 12;\n break;\n default:\n return;\n }\n // Before any no-op check: these keys also scroll the panel, and a month that\n // cannot move still must not move the page.\n e.preventDefault();\n if (delta !== 0) moveTo(shiftMonth(active, delta), true);\n };\n\n // An arrow is dead once EVERY month of the year it leads to is out of bounds.\n const prevBlocked = Boolean(minKey && toKey({ year: year - 1, month: 12 }) < minKey);\n const nextBlocked = Boolean(maxKey && toKey({ year: year + 1, month: 1 }) > maxKey);\n const selectedKey = value ? toKey(value) : null;\n\n const arrow =\n \"flex size-7 items-center justify-center rounded text-muted hover:bg-hover disabled:cursor-not-allowed disabled:opacity-30 disabled:hover:bg-transparent\";\n\n return (\n <div className=\"select-none\">\n <div className=\"mb-1 flex items-center justify-between\">\n <button\n type=\"button\"\n aria-label={labels.previousYear}\n disabled={prevBlocked}\n // Focus stays on the arrow being pressed — only the grid moves.\n onClick={() => moveTo(shiftMonth(active, -12), false)}\n className={arrow}\n >\n {/* Mirrored in RTL, where \"previous\" points the other way. */}\n <ChevronLeft className=\"size-4 rtl:-scale-x-100\" />\n </button>\n {/* Names the grid, and `aria-live` so stepping a year says which one you are\n on — the user pressing the arrow is not inside the grid to hear it change. */}\n <div id={captionId} aria-live=\"polite\" className=\"text-sm font-semibold tabular-nums text-primary\">\n {yearCaption}\n </div>\n <button\n type=\"button\"\n aria-label={labels.nextYear}\n disabled={nextBlocked}\n onClick={() => moveTo(shiftMonth(active, 12), false)}\n className={arrow}\n >\n <ChevronRight className=\"size-4 rtl:-scale-x-100\" />\n </button>\n </div>\n <div ref={gridRef} role=\"grid\" aria-labelledby={captionId} className=\"grid gap-1\">\n {Array.from({ length: 12 / COLUMNS }, (_, row) => (\n <div key={row} role=\"row\" className=\"grid grid-cols-3 gap-1\">\n {Array.from({ length: COLUMNS }, (_, c) => {\n const ym = { year, month: row * COLUMNS + c + 1 };\n const key = toKey(ym);\n const disabled = outOfBounds(key);\n const selected = key === selectedKey;\n const current = key === currentKey;\n return (\n <button\n key={c}\n type=\"button\"\n role=\"gridcell\"\n data-month={key}\n tabIndex={ym.month === active.month ? 0 : -1}\n aria-label={labels.month(longName.format(firstOf(ym)))}\n aria-selected={selected}\n aria-disabled={disabled || undefined}\n aria-current={current ? \"date\" : undefined}\n onClick={() => {\n // The guard `disabled` would have been; see the component note.\n if (disabled) return;\n setActive(ym);\n onPick(key);\n }}\n onKeyDown={onCellKeyDown}\n className={cn(\n \"rounded px-2 py-1.5 text-sm capitalize transition-colors\",\n FOCUS_RING_WIDTH,\n \"focus-visible:outline-none focus-visible:ring-inset\",\n // A --brand ring on a --brand fill exists only in the DOM.\n selected && !disabled ? \"focus-visible:ring-brand-contrast\" : \"focus-visible:ring-brand\",\n disabled\n ? \"cursor-not-allowed text-placeholder\"\n : selected\n ? \"bg-brand text-brand-contrast hover:bg-brand-hover\"\n : \"text-primary hover:bg-hover\",\n current && !selected && !disabled && \"ring-1 ring-inset ring-strong\",\n )}\n >\n {shortNames[ym.month - 1]}\n </button>\n );\n })}\n </div>\n ))}\n </div>\n </div>\n );\n}\n\n/** Years on one page of the year grid: the month grid's 4×3. */\nconst YEAR_PAGE = 12;\n\n/**\n * `mode=\"year\"`'s panel: a page of years between ‹ › arrows — {@link MonthGrid}'s shape\n * and keyboard, one level up (kastlan 7, keksdose tax-tab.tsx:33).\n *\n * Which years a page holds:\n * - Bounded on both sides by twelve years or fewer: exactly those years, and no\n * arrows — keksdose's \"this year and four before\" is five cells, not a page of\n * twelve with seven greyed out.\n * - Otherwise twelve at a time, starting from `min` when there is one (so the first\n * page opens on it), else ending at `max`, else on multiples of twelve.\n *\n * The arrow keys walk the years (up/down by a row of three), PageUp / PageDown a page,\n * Home / End the row; walking off a page turns it. Focus stops at the bounds rather\n * than walking into years that refuse selection: a page has no \"next year\" past the\n * last selectable one worth landing on.\n */\nfunction YearGrid({\n value,\n onPick,\n locale,\n minYear,\n maxYear,\n currentYear,\n labels,\n}: {\n value: number | null;\n onPick: (key: string) => void;\n locale: string | undefined;\n minYear: number | null;\n maxYear: number | null;\n currentYear: number;\n labels: MonthPickerLabels;\n}) {\n const clamp = (y: number) => Math.min(maxYear ?? y, Math.max(minYear ?? y, y));\n // A bounded span that fits one page is shown whole.\n const whole =\n minYear !== null && maxYear !== null && maxYear >= minYear && maxYear - minYear < YEAR_PAGE\n ? { first: minYear, count: maxYear - minYear + 1 }\n : null;\n // The year holding the tab order, and with it the page on screen — one piece of\n // state, as in MonthGrid. Opens on the value, or on this year, inside the bounds.\n const [active, setActive] = useState(() => clamp(value ?? currentYear));\n const anchor = minYear ?? (maxYear !== null ? maxYear - (YEAR_PAGE - 1) : 0);\n const first = whole ? whole.first : anchor + Math.floor((active - anchor) / YEAR_PAGE) * YEAR_PAGE;\n const count = whole ? whole.count : YEAR_PAGE;\n const years = Array.from({ length: count }, (_, i) => first + i);\n const outOfBounds = (y: number) => (minYear !== null && y < minYear) || (maxYear !== null && y > maxYear);\n\n const gridRef = useRef<HTMLDivElement>(null);\n const captionId = `${useId()}-years`;\n const yearFormat = useMemo(() => new Intl.DateTimeFormat(locale, YEAR_FORMAT), [locale]);\n // \"2016–2027\" in the locale's own range punctuation and digits.\n const caption = yearFormat.formatRange(new Date(first, 0, 1), new Date(first + count - 1, 0, 1));\n\n const pendingFocus = useRef<number | null>(null);\n useEffect(() => {\n const year = pendingFocus.current;\n if (year === null) return;\n pendingFocus.current = null;\n gridRef.current?.querySelector<HTMLElement>(`[data-year=\"${year}\"]`)?.focus();\n });\n // Opened to pick a year: land on it (before the popover's trap, which keeps it).\n useEffect(() => {\n gridRef.current?.querySelector<HTMLElement>('[tabindex=\"0\"]')?.focus();\n }, []);\n\n const moveTo = (next: number, focus: boolean) => {\n const year = clamp(next);\n if (focus) pendingFocus.current = year;\n setActive(year);\n };\n\n const onCellKeyDown = (e: KeyboardEvent<HTMLButtonElement>) => {\n const step = horizontalStep(e.key, e.currentTarget);\n const col = (active - first) % COLUMNS;\n let delta: number;\n switch (step ? \"horizontal\" : e.key) {\n case \"horizontal\":\n delta = step;\n break;\n case \"ArrowUp\":\n delta = -COLUMNS;\n break;\n case \"ArrowDown\":\n delta = COLUMNS;\n break;\n case \"Home\":\n delta = -col;\n break;\n case \"End\":\n delta = Math.min(COLUMNS - 1 - col, first + count - 1 - active);\n break;\n case \"PageUp\":\n delta = -YEAR_PAGE;\n break;\n case \"PageDown\":\n delta = YEAR_PAGE;\n break;\n default:\n return;\n }\n e.preventDefault();\n if (delta !== 0) moveTo(active + delta, true);\n };\n\n const prevBlocked = minYear !== null && first - 1 < minYear;\n const nextBlocked = maxYear !== null && first + count > maxYear;\n const arrow =\n \"flex size-7 items-center justify-center rounded text-muted hover:bg-hover disabled:cursor-not-allowed disabled:opacity-30 disabled:hover:bg-transparent\";\n\n return (\n <div className=\"select-none\">\n <div className={cn(\"mb-1 flex items-center\", whole ? \"h-7 justify-center\" : \"justify-between\")}>\n {!whole && (\n <button\n type=\"button\"\n aria-label={labels.earlierYears}\n disabled={prevBlocked}\n onClick={() => moveTo(active - YEAR_PAGE, false)}\n className={arrow}\n >\n <ChevronLeft className=\"size-4 rtl:-scale-x-100\" />\n </button>\n )}\n <div id={captionId} aria-live=\"polite\" className=\"text-sm font-semibold tabular-nums text-primary\">\n {caption}\n </div>\n {!whole && (\n <button\n type=\"button\"\n aria-label={labels.laterYears}\n disabled={nextBlocked}\n onClick={() => moveTo(active + YEAR_PAGE, false)}\n className={arrow}\n >\n <ChevronRight className=\"size-4 rtl:-scale-x-100\" />\n </button>\n )}\n </div>\n <div ref={gridRef} role=\"grid\" aria-labelledby={captionId} className=\"grid gap-1\">\n {Array.from({ length: Math.ceil(count / COLUMNS) }, (_, row) => (\n <div key={row} role=\"row\" className=\"grid grid-cols-3 gap-1\">\n {years.slice(row * COLUMNS, row * COLUMNS + COLUMNS).map((year) => {\n const disabled = outOfBounds(year);\n const selected = year === value;\n const current = year === currentYear;\n return (\n <button\n key={year}\n type=\"button\"\n role=\"gridcell\"\n data-year={year}\n tabIndex={year === active ? 0 : -1}\n aria-selected={selected}\n aria-disabled={disabled || undefined}\n aria-current={current ? \"date\" : undefined}\n onClick={() => {\n if (disabled) return;\n setActive(year);\n onPick(String(year));\n }}\n onKeyDown={onCellKeyDown}\n className={cn(\n \"rounded px-2 py-1.5 text-sm tabular-nums transition-colors\",\n FOCUS_RING_WIDTH,\n \"focus-visible:outline-none focus-visible:ring-inset\",\n selected && !disabled ? \"focus-visible:ring-brand-contrast\" : \"focus-visible:ring-brand\",\n disabled\n ? \"cursor-not-allowed text-placeholder\"\n : selected\n ? \"bg-brand text-brand-contrast hover:bg-brand-hover\"\n : \"text-primary hover:bg-hover\",\n current && !selected && !disabled && \"ring-1 ring-inset ring-strong\",\n )}\n >\n {yearFormat.format(new Date(year, 0, 1))}\n </button>\n );\n })}\n </div>\n ))}\n </div>\n </div>\n );\n}\n\n// ── Field ─────────────────────────────────────────────────────────────────\n\n/**\n * Month picker: a field showing the selected month (\"August 2026\"), opening a 12-month\n * grid with year arrows. For a value that IS a month — a budget period, a calendar\n * view, a statement — where a day picker would ask for a day nobody means.\n *\n * One component for the two app-local pickers it replaces: keksdose's budget-header\n * popover (grid + year steppers + a floor) and kastlan's calendar header (a month list\n * and a year list in two popovers). The grid does both jobs — the year arrows and\n * PageUp/PageDown cover what the year list did, without a fixed ±5-year window.\n */\n/** Upper-case the first letter only, in `locale`. Several locales (fr, es, it, pt, …)\n * write month names in lower case, which at the start of a field reads as a typo —\n * but only the FIRST letter is the field's business: \"septiembre de 2026\" becomes\n * \"Septiembre de 2026\", not \"Septiembre De 2026\". */\nfunction upperFirst(text: string, locale: string | undefined): string {\n return text.charAt(0).toLocaleUpperCase(locale) + text.slice(1);\n}\n\nexport function MonthPicker(props: MonthPickerProps) {\n const {\n value,\n onChange,\n locale: localeProp,\n min,\n max,\n label,\n placeholder,\n disabled,\n invalid,\n error,\n hint,\n help,\n size = \"md\",\n mode = \"month\",\n formatOptions,\n currentMonth,\n labels: labelsProp,\n className,\n triggerClassName,\n variant = \"field\",\n headingLevel = 2,\n showToday = true,\n // The trigger's (API-3); named apart from the Popover trigger's own `ref` below.\n ref: fieldRef,\n ...rest\n } = props;\n const labels = useMonthPickerLabels(labelsProp);\n const locale = useKitLocale(localeProp);\n const fromProvider = useKitDateFormatter();\n const years = mode === \"year\";\n const selected = years ? null : parseMonth(value);\n const selectedYear = years ? parseYear(value) : null;\n const minKey = boundKey(min);\n const maxKey = boundKey(max);\n const minYear = parseYear(min);\n const maxYear = parseYear(max);\n const currentKey = boundKey(currentMonth) ?? monthKey(new Date());\n const currentYear = parseYear(currentKey)!;\n // The key the trigger shows, in the mode's own shape: \"YYYY-MM\" or \"YYYY\".\n const shownKey = years ? (selectedYear !== null ? String(selectedYear) : null) : selected ? toKey(selected) : null;\n\n // The kit's order: `formatOptions` (this field's own Intl look), then the provider's\n // `formatDate` (keksdose K12 — the app's one way of writing a month), then the default.\n const triggerText = shownKey\n ? (formatOptions === undefined &&\n fromProvider?.(shownKey, { unit: years ? \"year\" : \"month\", source: \"monthPicker\", locale, weekday: false })) ||\n upperFirst(\n new Intl.DateTimeFormat(locale, formatOptions ?? (years ? YEAR_FORMAT : DEFAULT_TRIGGER_FORMAT)).format(\n years ? new Date(selectedYear!, 0, 1) : firstOf(selected!),\n ),\n locale,\n )\n : (placeholder ?? \"\");\n\n const id = useId();\n // keksdose K4: the caption and the error, on the trigger after the caller's own\n // description. Painted for `invalid` and a message — not for a bare `aria-invalid`,\n // which this field has never painted for.\n const messages = useFieldMessages({\n hint,\n help,\n name: label ?? rest[\"aria-label\"],\n error,\n describedBy: rest[\"aria-describedby\"],\n });\n const [ownAria, wrapperRest] = splitTriggerAria(rest);\n // The field's trigger is described by the caption and the error too; the stepper\n // draws neither, so its heading keeps the caller's own description.\n const aria: TriggerAria = { ...ownAria, \"aria-describedby\": messages.describedBy };\n const labelId = `${id}-label`;\n const valueId = `${id}-value`;\n const panelId = `${id}-panel`;\n // Only a STRING label can double as a name; a ReactNode may hold its own controls.\n const named = typeof label === \"string\";\n // `sm` is the toolbar trigger, for an unlabelled picker only (see `size`).\n const small = size === \"sm\" && label === undefined;\n // The panel is portalled to <body>, out of the subtree whose `dir` it inherited — an\n // RTL form got an LTR grid. Read when opening (`toggle` is the popover's only way\n // open) and put back on the panel. Also what the arrow keys above read.\n const rootRef = useRef<HTMLDivElement>(null);\n const [dir, setDir] = useState<Direction>(\"ltr\");\n const panelLabel = years ? labels.yearPanel : labels.panel;\n\n const grid = (close: () => void) =>\n years ? (\n <YearGrid\n value={selectedYear}\n locale={locale}\n minYear={minYear}\n maxYear={maxYear}\n currentYear={currentYear}\n labels={labels}\n onPick={(key) => {\n onChange(key);\n close();\n }}\n />\n ) : (\n <MonthGrid\n value={selected}\n locale={locale}\n minKey={minKey}\n maxKey={maxKey}\n currentKey={currentKey}\n labels={labels}\n onPick={(key) => {\n onChange(key);\n close();\n }}\n />\n );\n\n if (variant === \"stepper\") {\n // The arrows step from the value, or from \"now\" when there is none; each is dead\n // once the month (or year) it leads to is out of bounds, and Today once \"now\" is.\n let prevKey: string;\n let nextKey: string;\n let nowKey: string;\n let outside: (key: string) => boolean;\n if (years) {\n const base = selectedYear ?? currentYear;\n prevKey = String(base - 1);\n nextKey = String(base + 1);\n nowKey = String(currentYear);\n outside = (key) => {\n const y = Number(key);\n return (minYear !== null && y < minYear) || (maxYear !== null && y > maxYear);\n };\n } else {\n const base = selected ?? parseMonth(currentKey)!;\n prevKey = toKey(shiftMonth(base, -1));\n nextKey = toKey(shiftMonth(base, 1));\n nowKey = currentKey;\n outside = (key) => Boolean((minKey && key < minKey) || (maxKey && key > maxKey));\n }\n const Heading = headingLevel ? (`h${headingLevel}` as const) : null;\n const trigger = (\n <Popover\n width=\"16rem\"\n panelId={panelId}\n labels={{ panel: panelLabel }}\n dir={dir}\n trigger={({ open, toggle, ref }) => (\n <MonthHeadingTrigger\n open={open}\n toggle={() => {\n setDir(dirOf(rootRef.current));\n toggle();\n }}\n triggerRef={ref}\n fieldRef={fieldRef}\n text={triggerText}\n panelId={panelId}\n disabled={disabled}\n aria={ownAria}\n />\n )}\n >\n {grid}\n </Popover>\n );\n return (\n // `flex-wrap`: where the heading and the buttons do not fit one row, the buttons\n // take the next line, at the end (0.32.1, the 360 px Extra-large sweep;\n // docs/text-size-harmonization.md §4). In one row at 150 % \"Today ‹ ›\" left the\n // heading 30 px on a 360 px phone, and \"October 2026\" was drawn under \"Today\".\n // Where they fit, nothing changes.\n <div {...wrapperRest} ref={rootRef} className={cn(\"flex min-w-0 flex-wrap items-center gap-2\", className)}>\n {Heading ? <Heading className=\"m-0 min-w-0 text-lg font-semibold\">{trigger}</Heading> : trigger}\n <div className=\"ms-auto flex shrink-0 items-center gap-1\">\n {showToday && (\n <Button\n variant=\"secondary\"\n size=\"sm\"\n disabled={disabled || outside(nowKey)}\n onClick={() => {\n if (nowKey !== shownKey) onChange(nowKey);\n }}\n >\n {years ? labels.thisYear : labels.today}\n </Button>\n )}\n <IconButton\n aria-label={years ? labels.previousYear : labels.previousMonth}\n disabled={disabled || outside(prevKey)}\n onClick={() => onChange(prevKey)}\n >\n {/* Mirrored in RTL, where \"previous\" points the other way. */}\n <ChevronLeft className=\"rtl:-scale-x-100\" />\n </IconButton>\n <IconButton\n aria-label={years ? labels.nextYear : labels.nextMonth}\n disabled={disabled || outside(nextKey)}\n onClick={() => onChange(nextKey)}\n >\n <ChevronRight className=\"rtl:-scale-x-100\" />\n </IconButton>\n </div>\n </div>\n );\n }\n\n const field = (\n // `w-fit` for the toolbar size: the calendar glyph is placed against this box, and a\n // full-width box would park it at the far end of the row, away from its trigger.\n // At Large a labelled box is a grid: the label above, the calendar glyph measured\n // against the trigger's row (LABEL_ABOVE_BOX, 0.33 §10.17).\n <div\n {...wrapperRest}\n ref={rootRef}\n className={cn(\"relative\", small && \"w-fit max-w-full\", label !== undefined && LABEL_ABOVE_BOX, className)}\n >\n {label !== undefined && <FieldLabelLine label={label} hint={messages.labelHint} />}\n {/* The hidden twin the trigger is named by — see DateField. `sr-only-fixed`,\n and inside this `relative` root either way. */}\n {named && (\n <span id={labelId} aria-hidden className=\"sr-only-fixed\">\n {label}\n </span>\n )}\n <Popover\n width=\"16rem\"\n panelId={panelId}\n labels={{ panel: panelLabel }}\n dir={dir}\n trigger={({ open, toggle, ref }) => (\n <MonthFieldTrigger\n open={open}\n toggle={() => {\n setDir(dirOf(rootRef.current));\n toggle();\n }}\n triggerRef={ref}\n fieldRef={fieldRef}\n triggerText={triggerText}\n hasValue={shownKey !== null}\n // As DatePicker: a caller's reference first; an `id` without a label of our\n // own references the trigger itself, so an external <label htmlFor> names it.\n labelledBy={[\n aria[\"aria-labelledby\"],\n named ? labelId : !aria[\"aria-labelledby\"] && aria.id ? aria.id : undefined,\n valueId,\n ]\n .filter(Boolean)\n .join(\" \")}\n aria={aria}\n valueId={valueId}\n panelId={panelId}\n padded={label !== undefined}\n small={small}\n disabled={disabled}\n invalid={invalid || hasContent(error)}\n className={triggerClassName}\n />\n )}\n >\n {grid}\n </Popover>\n <Calendar\n aria-hidden\n className=\"pointer-events-none absolute end-2.5 top-1/2 size-4 -translate-y-1/2 text-placeholder\"\n />\n </div>\n );\n // An unlabelled picker has no label line: a FieldHint goes at its end, outside the\n // box, as on an unlabelled Select.\n const endHint = label === undefined ? messages.labelHint : undefined;\n return (\n <FieldBox below={messages.below} reserve={\"error\" in props || \"hint\" in props}>\n {endHint === undefined ? (\n field\n ) : (\n <div className=\"flex items-center gap-1.5\">\n <div className={cn(\"min-w-0\", !small && \"flex-1\")}>{field}</div>\n <span className=\"flex shrink-0 items-center\">{endHint}</span>\n </div>\n )}\n </FieldBox>\n );\n}\n"],"mappings":"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","names":["_"]}
1
+ {"version":3,"sources":["../../src/components/month-picker.tsx"],"sourcesContent":["import { useEffect, useId, useMemo, useRef, useState } from \"react\";\nimport type { ComponentPropsWithoutRef, KeyboardEvent, ReactNode, Ref, RefObject } from \"react\";\nimport { Calendar, ChevronDown, ChevronLeft, ChevronRight } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { dirOf, horizontalStep, type Direction } from \"../lib/direction\";\nimport { monthKey, pad } from \"../lib/dates\";\nimport { upperFirst } from \"../lib/text-case\";\nimport { Button } from \"./button\";\nimport { FIELD_FLOATING_PAD, FIELD_INVALID, FIELD_TRIGGER } from \"./field-base\";\nimport { IconButton } from \"./icon-button\";\nimport { FOCUS_RING, FOCUS_RING_WIDTH } from \"./focus-ring\";\nimport { Popover } from \"./popover\";\nimport { splitTriggerAria } from \"./trigger-aria\";\nimport type { TriggerAria } from \"./trigger-aria\";\nimport { useKitDateFormatter, useKitLabels, useKitLocale, type LabelOverride } from \"../i18n/kit-labels\";\nimport { hasContent } from \"../lib/presence\";\nimport {\n FieldBox,\n FieldLabelLine,\n LABEL_ABOVE_BOX,\n LockedReason,\n mergeDescribedBy,\n mergeRefs,\n useFieldMessages,\n useLockReason,\n type DisabledReasonDisplay,\n type FieldHelpProps,\n} from \"./field-parts\";\nimport { MonthGrid, clampKey, shiftKey, yearArrowsBlocked } from \"./month-grid\";\nimport { DEFAULT_MONTH_PICKER_LABELS } from \"./month-picker-labels\";\nimport type { CommitScope } from \"./write-lock\";\n\n/**\n * Every string the month picker can speak. The month and year names are NOT here —\n * they come from `Intl` in the `locale` prop, so they are already in the user's\n * language. What is left is the two icon-only year arrows, whose `aria-label` is the\n * only name they have, the panel's name, and a month cell's name.\n */\nexport interface MonthPickerLabels {\n previousYear: string;\n nextYear: string;\n /** The popover's accessible name (`role=\"dialog\"`). */\n panel: string;\n /**\n * Accessible name of one month cell, given that month and its year formatted in\n * `locale` (\"August 2026\"). The cell's visible text is the short month alone, and\n * \"Aug\" read out on its own does not say which year the grid is on.\n */\n month: (monthYear: string) => string;\n /** `variant=\"stepper\"`: the icon-only arrows that step the value by one month. */\n previousMonth: string;\n nextMonth: string;\n /** `variant=\"stepper\"`: the button that jumps to the current month. Visible text. */\n today: string;\n /** `mode=\"year\"`: the popover's accessible name. */\n yearPanel: string;\n /** `mode=\"year\"`: the icon-only arrows that page the year grid back and forward by a\n * page of twelve years. */\n earlierYears: string;\n laterYears: string;\n /** `mode=\"year\"`, `variant=\"stepper\"`: the button that jumps to the current year.\n * Visible text. */\n thisYear: string;\n}\n\n// Defined in ./month-picker-labels, which holds no component code (0.34, KS U8).\nexport { DEFAULT_MONTH_PICKER_LABELS };\n\n/** Merge caller overrides onto the English defaults. */\nfunction useMonthPickerLabels(partial?: Partial<MonthPickerLabels>): MonthPickerLabels {\n // Prop > <UiKitProvider> > English, like every labelled component in the kit.\n return useKitLabels(\"monthPicker\", DEFAULT_MONTH_PICKER_LABELS, partial);\n}\n\n/**\n * `onChange` is omitted from the `<div>` attributes: the DOM event of that name carries\n * a `FormEvent`, and this one answers with a month key. Everything else reaches the\n * field's root, so a test id or a `data-tour` anchor can find it.\n */\nexport interface MonthPickerProps extends Omit<ComponentPropsWithoutRef<\"div\">, \"onChange\">, FieldHelpProps {\n /**\n * The selected month as a `\"YYYY-MM\"` key, or `\"\"` for none.\n *\n * A string rather than `{ year, month }` or a `Date`, for the reason every date in\n * this kit is one: it is what `monthKey()` in `lib/dates` produces, it sorts\n * chronologically so the bounds are plain `<` / `>`, and it crosses a URL or an API\n * without a serialiser. A `{ year, month }` caller converts with\n * `` `${year}-${pad(month)}` `` (month 1–12) and back with `key.split(\"-\").map(Number)`.\n */\n value: string;\n /** Called with the picked month as `\"YYYY-MM\"`. */\n onChange: (month: string) => void;\n /** Drives the month and year names (`Intl.DateTimeFormat`). Defaults to the\n * `<UiKitProvider>` locale, then the runtime's. */\n locale?: string;\n /**\n * Inclusive bounds, as `\"YYYY-MM\"`. A full ISO date (`\"YYYY-MM-DD\"`) is accepted and\n * read as its month, so a host holding \"the first day we have data for\" can pass it\n * straight in. Months outside are shown but refuse selection; a year arrow refuses\n * once the whole year beyond it is out of bounds (`aria-disabled`, so the focus stays\n * on the arrow just pressed — 0.52).\n */\n min?: string;\n max?: string;\n /** Embedded top-boundary label, matching the other field-shaped controls. */\n label?: ReactNode;\n /** Trigger text when `value` is `\"\"`. */\n placeholder?: string;\n disabled?: boolean;\n /**\n * Why the month cannot be changed — {@link Select}'s `disabledReason`, for a picker\n * that SAVES on change (a budget page's month, a calendar view kept per user). The\n * trigger stays focusable and `aria-disabled`, the grid does not open, the stepper's\n * Today and ‹ › refuse, and `onChange` never fires; the reason is a line under the\n * field (or the stepper) at Large and on a touch screen, else in the kit\n * {@link Tooltip} (`disabledReasonDisplay`), and on the trigger's `aria-describedby`.\n * Wins over `disabled`. 0.52 (refactor audit 2026-10-30, API-3).\n */\n disabledReason?: ReactNode;\n /** Where the reason shows — {@link Button}'s `disabledReasonDisplay`: a line under the\n * field at Large and on a touch screen by default, the tooltip otherwise. */\n disabledReasonDisplay?: DisabledReasonDisplay;\n /**\n * This picker COMMITS — picking saves. Under a locked {@link WriteLockProvider} it is\n * locked the `disabledReason` way, with the lock's reason; a {@link CommitScope}, as on\n * every kit control. A month field inside a form with its own Save stays editable\n * under the lock — leave this off there, and put `commit` on the Save. 0.52 (API-3).\n */\n commit?: CommitScope;\n /** Required and unanswered — {@link FIELD_INVALID}. See {@link Input}'s `invalid`. */\n invalid?: boolean;\n /**\n * What is wrong with the month, in the caller's words — {@link Input}'s `error`\n * (keksdose K4): under the field, attached to the trigger with `aria-describedby`\n * (merged, after any caption), and implies `invalid`. Passing the key keeps the\n * field's box while there is no message, so the trigger is never remounted.\n */\n error?: ReactNode;\n /** Standing advice, as on a {@link Select}: text is a caption under the field; a\n * {@link FieldHint} rides the label line. */\n hint?: ReactNode;\n /**\n * `\"md\"` (default): the form field. `\"sm\"`: the toolbar trigger — 36px tall (an\n * `IconButton`'s height) and as wide as the month it shows, for a picker that sits\n * between two icon buttons in a page header (keksdose K13: the budget header wrote\n * `triggerClassName=\"h-9 w-auto … py-0\"` by hand, live #260 — 34px between two 36px\n * buttons, *\"the kind of 2px that reads as sloppy\"*). Unlabelled pickers only, as on\n * {@link Select}: a floating label needs the tall box to float in. The width follows\n * the month's name; add a `min-w-*` in `triggerClassName` if the buttons beside it\n * must not move as it changes.\n */\n size?: \"sm\" | \"md\";\n /**\n * `\"month\"` (default): the value is a month, `\"YYYY-MM\"`.\n *\n * `\"year\"`: the value is a YEAR, `\"YYYY\"`, and the panel is a grid of years — kastlan's\n * budget fiscal year (a `Select` of 2020 to three years ahead) and keksdose's tax tab\n * (\"this year and four before\", tax-tab.tsx:33). `min` / `max` are read as their year\n * (`\"2022\"`, or any longer key). A bounded span of twelve years or fewer is shown\n * whole, with no paging; otherwise the grid pages twelve years at a time (PageUp /\n * PageDown, or the arrows), starting from `min` when there is one. The stepper\n * variant steps a year, and its Today button reads \"This year\".\n *\n * A string rather than a number, for the reason the month key is one: it is the\n * same type across both modes, it crosses a URL as is, and it sorts. A numeric\n * caller converts with `String(year)` and `Number(key)`.\n */\n mode?: \"month\" | \"year\";\n /** Intl options for the trigger's text (default: long month + numeric year, or the\n * numeric year in `mode=\"year\"`). Win over the provider's `formatDate`. */\n formatOptions?: Intl.DateTimeFormatOptions;\n /**\n * The month to ring as \"now\", as `\"YYYY-MM\"`. Defaults to the local calendar's\n * current month; pass one to pin it (a test, a host with its own notion of today).\n */\n currentMonth?: string;\n /** User-facing strings; see {@link MonthPickerLabels}. */\n labels?: LabelOverride<MonthPickerLabels>;\n /** Extra classes for the field's root. */\n className?: string;\n /** Extra classes for the trigger button — e.g. a compact `h-9 w-auto` trigger\n * sitting between two icon buttons in a toolbar. */\n triggerClassName?: string;\n /**\n * `\"field\"` (default): the field-shaped trigger, for a form.\n *\n * `\"stepper\"`: the header of a month PAGE — the month as a heading that opens the\n * grid, then Today and ‹ › stepping the value by one month. What a calendar page\n * assembled by hand around the field (overriding its trigger to look like a\n * heading). `label`, `placeholder`, `invalid`, `hint`, `error`, `size` and\n * `triggerClassName` are the field's and are not drawn here; with no `value` the\n * arrows step from the current month (or year).\n */\n variant?: \"field\" | \"stepper\";\n /** `variant=\"stepper\"`: the heading level the month is set in (`<h2>` by default), or\n * `false` for no heading element — the page already has one for the month. */\n headingLevel?: 1 | 2 | 3 | 4 | 5 | 6 | false;\n /** `variant=\"stepper\"`: draw the Today button. Default `true`. */\n showToday?: boolean;\n /**\n * The TRIGGER — the field's button, or the stepper's heading button — as a React 19\n * ref prop (0.34, API-3, refactor audit 2026-10). The picker took none, so\n * react-hook-form's focus-on-error reached it only through /rhf's id lookup. Not the\n * wrapper, which takes the rest of the props.\n */\n ref?: Ref<HTMLButtonElement>;\n}\n\n// ── Month keys ────────────────────────────────────────────────────────────\n\ninterface YearMonth {\n year: number;\n /** 1–12, as in the key. */\n month: number;\n}\n\nconst KEY = /^(\\d{4})-(\\d{2})/;\n\nfunction parseMonth(key: string | undefined): YearMonth | null {\n const m = key ? KEY.exec(key) : null;\n if (!m) return null;\n const month = Number(m[2]);\n return month >= 1 && month <= 12 ? { year: Number(m[1]), month } : null;\n}\n\nfunction toKey({ year, month }: YearMonth): string {\n return `${year}-${pad(month)}`;\n}\n\n/** Move a month key by whole months, carrying into the year. */\nfunction shiftMonth(ym: YearMonth, delta: number): YearMonth {\n const index = ym.year * 12 + (ym.month - 1) + delta;\n return { year: Math.floor(index / 12), month: (index % 12) + 1 };\n}\n\n/** A bound, normalised to its month key — so `\"2024-03-15\"` compares as `\"2024-03\"`. */\nfunction boundKey(bound: string | undefined): string | null {\n const ym = parseMonth(bound);\n return ym ? toKey(ym) : null;\n}\n\n/** Local-calendar Date for the 1st of a month, for `Intl` to format. */\nfunction firstOf({ year, month }: YearMonth): Date {\n return new Date(year, month - 1, 1);\n}\n\nconst DEFAULT_TRIGGER_FORMAT: Intl.DateTimeFormatOptions = { month: \"long\", year: \"numeric\" };\nconst YEAR_FORMAT: Intl.DateTimeFormatOptions = { year: \"numeric\" };\n\n// ── Year keys ─────────────────────────────────────────────────────────────\n\nconst YEAR = /^(\\d{4})/;\n\n/** The year of `\"YYYY\"`, `\"YYYY-MM\"` or `\"YYYY-MM-DD\"`, or null. */\nfunction parseYear(key: string | undefined): number | null {\n const m = key ? YEAR.exec(key) : null;\n return m ? Number(m[1]) : null;\n}\n\n// ── Trigger ───────────────────────────────────────────────────────────────\n\n/** Hand focus back to the trigger when the panel closes with focus still inside it. */\nfunction useFocusBackOnClose(open: boolean, triggerRef: RefObject<HTMLButtonElement | null>) {\n const wasOpen = useRef(false);\n useEffect(() => {\n const justClosed = wasOpen.current && !open;\n wasOpen.current = open;\n if (!justClosed) return;\n // Only when focus went down with the panel. An outside click has already put it\n // somewhere real, and dragging it back here would be worse than leaving it.\n const active = document.activeElement;\n if (!active || active === document.body) triggerRef.current?.focus();\n }, [open, triggerRef]);\n}\n\n/** The Popover's handle on the trigger and the picker's own `ref` prop (API-3), as one. */\nfunction useTriggerRef(triggerRef: RefObject<HTMLButtonElement | null>, fieldRef: Ref<HTMLButtonElement> | undefined) {\n return useMemo(() => mergeRefs(triggerRef, fieldRef), [triggerRef, fieldRef]);\n}\n\n/**\n * The stepper's trigger: the month as a heading-sized button that opens the grid.\n *\n * Its own text is its name (\"September 2026\"), so it needs no label of its own, and\n * it is `aria-live` for the reason `MiniCalendar`'s caption is: pressing ‹ or › leaves\n * focus on the arrow, and the heading is the only thing that says where the arrow\n * went. A plain button with `aria-haspopup`, not the field's `combobox` — it does not\n * look or behave like a field, and a heading reading \"combobox\" is a surprise.\n */\nfunction MonthHeadingTrigger({\n open,\n toggle,\n triggerRef,\n fieldRef,\n text,\n panelId,\n disabled,\n locked,\n aria,\n}: {\n open: boolean;\n toggle: () => void;\n triggerRef: RefObject<HTMLButtonElement | null>;\n fieldRef?: Ref<HTMLButtonElement>;\n text: string;\n panelId: string;\n disabled?: boolean;\n /** The write lock: the focusable kind of disabled. */\n locked: boolean;\n aria: TriggerAria;\n}) {\n useFocusBackOnClose(open, triggerRef);\n const setTrigger = useTriggerRef(triggerRef, fieldRef);\n return (\n <button\n ref={setTrigger}\n type=\"button\"\n disabled={locked ? undefined : disabled}\n aria-disabled={locked || undefined}\n onClick={() => {\n if (!locked) toggle();\n }}\n aria-haspopup=\"dialog\"\n aria-controls={panelId}\n aria-expanded={open}\n id={aria.id}\n aria-label={aria[\"aria-label\"]}\n aria-labelledby={aria[\"aria-labelledby\"]}\n aria-describedby={aria[\"aria-describedby\"]}\n className={cn(\n \"-ms-1.5 inline-flex h-9 min-w-0 items-center gap-1 rounded-md px-1.5 text-lg font-semibold text-primary transition-colors\",\n \"hover:bg-hover\",\n FOCUS_RING,\n (disabled || locked) && \"cursor-not-allowed opacity-50\",\n )}\n >\n <span aria-live=\"polite\" className=\"truncate\">\n {text || \"\\u00a0\"}\n </span>\n <ChevronDown aria-hidden className=\"size-4 shrink-0 text-muted\" />\n </button>\n );\n}\n\n/**\n * The field-shaped button that opens the grid. The same shape as `DatePicker`'s\n * trigger, for the same reasons written down there: `role=\"combobox\"` so it may carry\n * `aria-expanded` / `aria-invalid`, a name made of the label AND the value, and focus\n * handed back here when the panel closes with focus still inside it.\n */\nfunction MonthFieldTrigger({\n open,\n toggle,\n triggerRef,\n fieldRef,\n triggerText,\n hasValue,\n labelledBy,\n valueId,\n panelId,\n padded,\n small,\n disabled,\n locked,\n invalid,\n className,\n aria,\n}: {\n open: boolean;\n toggle: () => void;\n triggerRef: RefObject<HTMLButtonElement | null>;\n fieldRef?: Ref<HTMLButtonElement>;\n triggerText: string;\n hasValue: boolean;\n labelledBy: string;\n /** The caller's naming attributes — see `trigger-aria.ts`. */\n aria: TriggerAria;\n valueId: string;\n panelId: string;\n padded: boolean;\n /** `size=\"sm\"`: the 36px, content-wide toolbar trigger. */\n small: boolean;\n disabled?: boolean;\n /** The write lock: the focusable kind of disabled (Button's rule — native `disabled`\n * would take the field out of the tab order, and the reason with it). */\n locked: boolean;\n invalid?: boolean;\n className?: string;\n}) {\n useFocusBackOnClose(open, triggerRef);\n const setTrigger = useTriggerRef(triggerRef, fieldRef);\n\n return (\n <button\n ref={setTrigger}\n type=\"button\"\n disabled={locked ? undefined : disabled}\n aria-disabled={locked || undefined}\n onClick={() => {\n // Locked, the grid does not open. One open when the lock arrives closes: the\n // field moves into the reason's frame, which remounts it, as Select's does.\n if (!locked) toggle();\n }}\n role=\"combobox\"\n aria-haspopup=\"dialog\"\n aria-controls={panelId}\n aria-expanded={open}\n id={aria.id}\n aria-invalid={invalid || aria[\"aria-invalid\"] === true || aria[\"aria-invalid\"] === \"true\" || undefined}\n aria-required={aria[\"aria-required\"]}\n aria-labelledby={aria[\"aria-label\"] && !aria[\"aria-labelledby\"] ? undefined : labelledBy}\n aria-label={aria[\"aria-label\"]}\n aria-describedby={aria[\"aria-describedby\"]}\n className={cn(\n FIELD_TRIGGER,\n \"pe-9\",\n padded && FIELD_FLOATING_PAD,\n // keksdose K13 (live #260): an IconButton's 36px, and only as wide as the\n // month — `py-0` with a fixed height so the box is 36px whatever the type's\n // line height. `pe-9` stays: the calendar glyph still sits at the end.\n small && \"h-9 w-auto py-0\",\n (disabled || locked) && \"cursor-not-allowed opacity-50\",\n invalid && FIELD_INVALID,\n className,\n )}\n >\n {/* The U+00A0 fallback keeps the line box when there is no text — see\n DatePicker. The value arrives capitalised (see `upperFirst`); this span does\n NOT use `capitalize`, which upper-cases every WORD — \"Choose A Month\",\n \"Septiembre De 2026\". It wraps from Large up, as DatePicker's value does\n (`truncate-until-large`, text size §10.17; round 0.34 T1: the sweep read\n \"Choose a mo…\" at 360 px / Extra large). */}\n <span id={valueId} className={cn(\"truncate-until-large\", !hasValue && \"text-placeholder\")}>\n {triggerText || \" \"}\n </span>\n </button>\n );\n}\n\n// ── Grid ──────────────────────────────────────────────────────────────────\n\nconst COLUMNS = 3;\n\n/**\n * A year arrow over a grid (the month grid's ‹ › years, the year grid's pages). At a\n * bound it refuses rather than turning `disabled`, JumpCalendar's rule (refactor audit\n * 2026-10-30, INP-4): the press that reaches the bound is made with the focus ON the\n * arrow, and a focused button that turns `disabled` drops the focus to <body>, mid-panel\n * (keksdose's budget MonthPicker passes `min`). `aria-disabled`, a click that does\n * nothing, and the look through `aria-disabled:`.\n */\nconst PANEL_ARROW =\n \"flex size-7 items-center justify-center rounded text-muted hover:bg-hover aria-disabled:cursor-not-allowed aria-disabled:opacity-30 aria-disabled:hover:bg-transparent\";\n\n/** The stepper's Today and ‹ › while refused: Button's look for its focusable kind of\n * disabled — the kit's `disabled:` classes cannot match an `aria-disabled` button. The\n * arrows, ghost IconButtons, also pin their resting fill under hover, as\n * `disabled:hover:` does for them. */\nconst STEPPER_REFUSED = \"aria-disabled:cursor-not-allowed aria-disabled:opacity-50\";\nconst STEPPER_ARROW_REFUSED = `${STEPPER_REFUSED} aria-disabled:hover:bg-transparent`;\n\n/**\n * The panel: a year caption between ‹ › arrows, over the year's {@link MonthGrid} (4×3,\n * its keys and its bounds — one with JumpCalendar's since 0.52, DUP-11).\n *\n * Mounted only while the popover is open, so every opening starts from the current\n * value's year — a value moved from outside (a prev/next-month button beside the\n * field) is where the grid lands, with no effect needed to snap it back.\n */\nfunction MonthPanel({\n value,\n onPick,\n locale,\n minKey,\n maxKey,\n currentKey,\n labels,\n}: {\n value: YearMonth | null;\n onPick: (key: string) => void;\n locale: string | undefined;\n minKey: string | null;\n maxKey: string | null;\n currentKey: string;\n labels: MonthPickerLabels;\n}) {\n const selectedKey = value ? toKey(value) : null;\n // The month holding the tab order, and with it the year on screen — one piece of\n // state, as in MiniCalendar, so the two cannot disagree. Opens on the value, or on\n // the current month, pulled inside the bounds.\n const [active, setActive] = useState(() => clampKey(selectedKey ?? currentKey, minKey, maxKey));\n const year = Number(active.slice(0, 4));\n const captionId = `${useId()}-year`;\n const yearCaption = new Intl.DateTimeFormat(locale, YEAR_FORMAT).format(new Date(year, 0, 1));\n const blocked = yearArrowsBlocked(active, minKey, maxKey);\n\n return (\n <div className=\"select-none\">\n <div className=\"mb-1 flex items-center justify-between\">\n <button\n type=\"button\"\n aria-label={labels.previousYear}\n aria-disabled={blocked.previous || undefined}\n // Focus stays on the arrow being pressed — only the grid moves.\n onClick={() => {\n if (!blocked.previous) setActive(shiftKey(active, -12));\n }}\n className={PANEL_ARROW}\n >\n {/* Mirrored in RTL, where \"previous\" points the other way. */}\n <ChevronLeft className=\"size-4 rtl:-scale-x-100\" />\n </button>\n {/* Names the grid, and `aria-live` so stepping a year says which one you are\n on — the user pressing the arrow is not inside the grid to hear it change. */}\n <div id={captionId} aria-live=\"polite\" className=\"text-sm font-semibold tabular-nums text-primary\">\n {yearCaption}\n </div>\n <button\n type=\"button\"\n aria-label={labels.nextYear}\n aria-disabled={blocked.next || undefined}\n onClick={() => {\n if (!blocked.next) setActive(shiftKey(active, 12));\n }}\n className={PANEL_ARROW}\n >\n <ChevronRight className=\"size-4 rtl:-scale-x-100\" />\n </button>\n </div>\n <MonthGrid\n active={active}\n onActiveChange={setActive}\n onPick={onPick}\n from={selectedKey}\n to={selectedKey}\n minKey={minKey}\n maxKey={maxKey}\n currentKey={currentKey}\n locale={locale}\n cellName={labels.month}\n labelledBy={captionId}\n />\n </div>\n );\n}\n\n/** Years on one page of the year grid: the month grid's 4×3. */\nconst YEAR_PAGE = 12;\n\n/**\n * `mode=\"year\"`'s panel: a page of years between ‹ › arrows — {@link MonthGrid}'s shape\n * and keyboard, one level up (kastlan 7, keksdose tax-tab.tsx:33).\n *\n * Which years a page holds:\n * - Bounded on both sides by twelve years or fewer: exactly those years, and no\n * arrows — keksdose's \"this year and four before\" is five cells, not a page of\n * twelve with seven greyed out.\n * - Otherwise twelve at a time, starting from `min` when there is one (so the first\n * page opens on it), else ending at `max`, else on multiples of twelve.\n *\n * The arrow keys walk the years (up/down by a row of three), PageUp / PageDown a page,\n * Home / End the row; walking off a page turns it. Focus stops at the bounds rather\n * than walking into years that refuse selection: a page has no \"next year\" past the\n * last selectable one worth landing on.\n */\nfunction YearGrid({\n value,\n onPick,\n locale,\n minYear,\n maxYear,\n currentYear,\n labels,\n}: {\n value: number | null;\n onPick: (key: string) => void;\n locale: string | undefined;\n minYear: number | null;\n maxYear: number | null;\n currentYear: number;\n labels: MonthPickerLabels;\n}) {\n const clamp = (y: number) => Math.min(maxYear ?? y, Math.max(minYear ?? y, y));\n // A bounded span that fits one page is shown whole.\n const whole =\n minYear !== null && maxYear !== null && maxYear >= minYear && maxYear - minYear < YEAR_PAGE\n ? { first: minYear, count: maxYear - minYear + 1 }\n : null;\n // The year holding the tab order, and with it the page on screen — one piece of\n // state, as in MonthPanel. Opens on the value, or on this year, inside the bounds.\n const [active, setActive] = useState(() => clamp(value ?? currentYear));\n const anchor = minYear ?? (maxYear !== null ? maxYear - (YEAR_PAGE - 1) : 0);\n const first = whole ? whole.first : anchor + Math.floor((active - anchor) / YEAR_PAGE) * YEAR_PAGE;\n const count = whole ? whole.count : YEAR_PAGE;\n const years = Array.from({ length: count }, (_, i) => first + i);\n const outOfBounds = (y: number) => (minYear !== null && y < minYear) || (maxYear !== null && y > maxYear);\n\n const gridRef = useRef<HTMLDivElement>(null);\n const captionId = `${useId()}-years`;\n const yearFormat = useMemo(() => new Intl.DateTimeFormat(locale, YEAR_FORMAT), [locale]);\n // \"2016–2027\" in the locale's own range punctuation and digits.\n const caption = yearFormat.formatRange(new Date(first, 0, 1), new Date(first + count - 1, 0, 1));\n\n const pendingFocus = useRef<number | null>(null);\n useEffect(() => {\n const year = pendingFocus.current;\n if (year === null) return;\n pendingFocus.current = null;\n gridRef.current?.querySelector<HTMLElement>(`[data-year=\"${year}\"]`)?.focus();\n });\n // Opened to pick a year: land on it (before the popover's trap, which keeps it).\n useEffect(() => {\n gridRef.current?.querySelector<HTMLElement>('[tabindex=\"0\"]')?.focus();\n }, []);\n\n const moveTo = (next: number, focus: boolean) => {\n const year = clamp(next);\n if (focus) pendingFocus.current = year;\n setActive(year);\n };\n\n const onCellKeyDown = (e: KeyboardEvent<HTMLButtonElement>) => {\n const step = horizontalStep(e.key, e.currentTarget);\n const col = (active - first) % COLUMNS;\n let delta: number;\n switch (step ? \"horizontal\" : e.key) {\n case \"horizontal\":\n delta = step;\n break;\n case \"ArrowUp\":\n delta = -COLUMNS;\n break;\n case \"ArrowDown\":\n delta = COLUMNS;\n break;\n case \"Home\":\n delta = -col;\n break;\n case \"End\":\n delta = Math.min(COLUMNS - 1 - col, first + count - 1 - active);\n break;\n case \"PageUp\":\n delta = -YEAR_PAGE;\n break;\n case \"PageDown\":\n delta = YEAR_PAGE;\n break;\n default:\n return;\n }\n e.preventDefault();\n if (delta !== 0) moveTo(active + delta, true);\n };\n\n const prevBlocked = minYear !== null && first - 1 < minYear;\n const nextBlocked = maxYear !== null && first + count > maxYear;\n\n return (\n <div className=\"select-none\">\n <div className={cn(\"mb-1 flex items-center\", whole ? \"h-7 justify-center\" : \"justify-between\")}>\n {!whole && (\n <button\n type=\"button\"\n aria-label={labels.earlierYears}\n aria-disabled={prevBlocked || undefined}\n onClick={() => {\n if (!prevBlocked) moveTo(active - YEAR_PAGE, false);\n }}\n className={PANEL_ARROW}\n >\n <ChevronLeft className=\"size-4 rtl:-scale-x-100\" />\n </button>\n )}\n <div id={captionId} aria-live=\"polite\" className=\"text-sm font-semibold tabular-nums text-primary\">\n {caption}\n </div>\n {!whole && (\n <button\n type=\"button\"\n aria-label={labels.laterYears}\n aria-disabled={nextBlocked || undefined}\n onClick={() => {\n if (!nextBlocked) moveTo(active + YEAR_PAGE, false);\n }}\n className={PANEL_ARROW}\n >\n <ChevronRight className=\"size-4 rtl:-scale-x-100\" />\n </button>\n )}\n </div>\n <div ref={gridRef} role=\"grid\" aria-labelledby={captionId} className=\"grid gap-1\">\n {Array.from({ length: Math.ceil(count / COLUMNS) }, (_, row) => (\n <div key={row} role=\"row\" className=\"grid grid-cols-3 gap-1\">\n {years.slice(row * COLUMNS, row * COLUMNS + COLUMNS).map((year) => {\n const disabled = outOfBounds(year);\n const selected = year === value;\n const current = year === currentYear;\n return (\n <button\n key={year}\n type=\"button\"\n role=\"gridcell\"\n data-year={year}\n tabIndex={year === active ? 0 : -1}\n aria-selected={selected}\n aria-disabled={disabled || undefined}\n aria-current={current ? \"date\" : undefined}\n onClick={() => {\n if (disabled) return;\n setActive(year);\n onPick(String(year));\n }}\n onKeyDown={onCellKeyDown}\n className={cn(\n // The month grid's 44px rows (DUP-11): one picker, one row height.\n \"h-11 rounded px-1 text-sm tabular-nums transition-colors\",\n FOCUS_RING_WIDTH,\n \"focus-visible:outline-none focus-visible:ring-inset\",\n selected && !disabled ? \"focus-visible:ring-brand-contrast\" : \"focus-visible:ring-brand\",\n disabled\n ? \"cursor-not-allowed text-placeholder\"\n : selected\n ? \"bg-brand text-brand-contrast hover:bg-brand-hover\"\n : \"text-primary hover:bg-hover\",\n current && !selected && !disabled && \"ring-1 ring-inset ring-strong\",\n )}\n >\n {yearFormat.format(new Date(year, 0, 1))}\n </button>\n );\n })}\n </div>\n ))}\n </div>\n </div>\n );\n}\n\n// ── Field ─────────────────────────────────────────────────────────────────\n\n/**\n * Month picker: a field showing the selected month (\"August 2026\"), opening a 12-month\n * grid with year arrows. For a value that IS a month — a budget period, a calendar\n * view, a statement — where a day picker would ask for a day nobody means.\n *\n * One component for the two app-local pickers it replaces: keksdose's budget-header\n * popover (grid + year steppers + a floor) and kastlan's calendar header (a month list\n * and a year list in two popovers). The grid does both jobs — the year arrows and\n * PageUp/PageDown cover what the year list did, without a fixed ±5-year window.\n */\nexport function MonthPicker(props: MonthPickerProps) {\n const {\n value,\n onChange,\n locale: localeProp,\n min,\n max,\n label,\n placeholder,\n disabled,\n disabledReason,\n disabledReasonDisplay,\n commit,\n invalid,\n error,\n hint,\n help,\n size = \"md\",\n mode = \"month\",\n formatOptions,\n currentMonth,\n labels: labelsProp,\n className,\n triggerClassName,\n variant = \"field\",\n headingLevel = 2,\n showToday = true,\n // The trigger's (API-3); named apart from the Popover trigger's own `ref` below.\n ref: fieldRef,\n ...rest\n } = props;\n const labels = useMonthPickerLabels(labelsProp);\n const locale = useKitLocale(localeProp);\n const fromProvider = useKitDateFormatter();\n const years = mode === \"year\";\n const selected = years ? null : parseMonth(value);\n const selectedYear = years ? parseYear(value) : null;\n const minKey = boundKey(min);\n const maxKey = boundKey(max);\n const minYear = parseYear(min);\n const maxYear = parseYear(max);\n const currentKey = boundKey(currentMonth) ?? monthKey(new Date());\n const currentYear = parseYear(currentKey)!;\n // The key the trigger shows, in the mode's own shape: \"YYYY-MM\" or \"YYYY\".\n const shownKey = years ? (selectedYear !== null ? String(selectedYear) : null) : selected ? toKey(selected) : null;\n\n // The kit's order: `formatOptions` (this field's own Intl look), then the provider's\n // `formatDate` (keksdose K12 — the app's one way of writing a month), then the default.\n const triggerText = shownKey\n ? (formatOptions === undefined &&\n fromProvider?.(shownKey, { unit: years ? \"year\" : \"month\", source: \"monthPicker\", locale, weekday: false })) ||\n upperFirst(\n new Intl.DateTimeFormat(locale, formatOptions ?? (years ? YEAR_FORMAT : DEFAULT_TRIGGER_FORMAT)).format(\n years ? new Date(selectedYear!, 0, 1) : firstOf(selected!),\n ),\n locale,\n )\n : (placeholder ?? \"\");\n\n const id = useId();\n // The write lock (API-3): Select's — the trigger stays reachable and says why.\n const lock = useLockReason(commit, disabledReason);\n const locked = lock.locked;\n const reasonId = locked ? lock.reasonId : undefined;\n const change = (key: string) => {\n if (!locked) onChange(key);\n };\n // keksdose K4: the caption and the error, on the trigger after the caller's own\n // description; a lock's reason between the two, as on Select. Painted for `invalid`\n // and a message — not for a bare `aria-invalid`, which this field has never painted for.\n const messages = useFieldMessages({\n hint,\n help,\n name: label ?? rest[\"aria-label\"],\n error,\n describedBy: rest[\"aria-describedby\"],\n reasonId,\n });\n const [ownAria, wrapperRest] = splitTriggerAria(rest);\n // The field's trigger is described by the caption and the error too; the stepper\n // draws neither, so its heading keeps the caller's own description — and a lock's.\n const aria: TriggerAria = { ...ownAria, \"aria-describedby\": messages.describedBy };\n const headingAria: TriggerAria = {\n ...ownAria,\n \"aria-describedby\": mergeDescribedBy(ownAria[\"aria-describedby\"], reasonId),\n };\n // Locked, the field (or the stepper) sits in the reason's frame — the line, or the\n // Tooltip — which becomes the outermost box and takes the caller's classes, as on\n // Select, so a lock does not reflow the page.\n const rootClass = locked ? undefined : className;\n const withReason = (box: ReactNode) =>\n locked ? (\n <LockedReason lock={lock} display={disabledReasonDisplay} className={cn(\"block\", className)}>\n {box}\n </LockedReason>\n ) : (\n box\n );\n const labelId = `${id}-label`;\n const valueId = `${id}-value`;\n const panelId = `${id}-panel`;\n // Only a STRING label can double as a name; a ReactNode may hold its own controls.\n const named = typeof label === \"string\";\n // `sm` is the toolbar trigger, for an unlabelled picker only (see `size`).\n const small = size === \"sm\" && label === undefined;\n // The panel is portalled to <body>, out of the subtree whose `dir` it inherited — an\n // RTL form got an LTR grid. Read when opening (`toggle` is the popover's only way\n // open) and put back on the panel. Also what the arrow keys above read.\n const rootRef = useRef<HTMLDivElement>(null);\n const [dir, setDir] = useState<Direction>(\"ltr\");\n const panelLabel = years ? labels.yearPanel : labels.panel;\n\n const grid = (close: () => void) =>\n years ? (\n <YearGrid\n value={selectedYear}\n locale={locale}\n minYear={minYear}\n maxYear={maxYear}\n currentYear={currentYear}\n labels={labels}\n onPick={(key) => {\n change(key);\n close();\n }}\n />\n ) : (\n <MonthPanel\n value={selected}\n locale={locale}\n minKey={minKey}\n maxKey={maxKey}\n currentKey={currentKey}\n labels={labels}\n onPick={(key) => {\n change(key);\n close();\n }}\n />\n );\n\n if (variant === \"stepper\") {\n // The arrows step from the value, or from \"now\" when there is none; each is dead\n // once the month (or year) it leads to is out of bounds, and Today once \"now\" is.\n let prevKey: string;\n let nextKey: string;\n let nowKey: string;\n let outside: (key: string) => boolean;\n if (years) {\n const base = selectedYear ?? currentYear;\n prevKey = String(base - 1);\n nextKey = String(base + 1);\n nowKey = String(currentYear);\n outside = (key) => {\n const y = Number(key);\n return (minYear !== null && y < minYear) || (maxYear !== null && y > maxYear);\n };\n } else {\n const base = selected ?? parseMonth(currentKey)!;\n prevKey = toKey(shiftMonth(base, -1));\n nextKey = toKey(shiftMonth(base, 1));\n nowKey = currentKey;\n outside = (key) => Boolean((minKey && key < minKey) || (maxKey && key > maxKey));\n }\n const Heading = headingLevel ? (`h${headingLevel}` as const) : null;\n const trigger = (\n <Popover\n width=\"16rem\"\n panelId={panelId}\n labels={{ panel: panelLabel }}\n dir={dir}\n trigger={({ open, toggle, ref }) => (\n <MonthHeadingTrigger\n open={open}\n toggle={() => {\n setDir(dirOf(rootRef.current));\n toggle();\n }}\n triggerRef={ref}\n fieldRef={fieldRef}\n text={triggerText}\n panelId={panelId}\n disabled={disabled}\n locked={locked}\n aria={headingAria}\n />\n )}\n >\n {grid}\n </Popover>\n );\n // ‹ › at a bound refuse rather than turn `disabled` (refactor audit 2026-10-30,\n // INP-4): the press that reaches the bound is made with the focus on the arrow, and\n // a focused button that turns `disabled` drops the focus to <body>. A lock refuses\n // them, and Today, the same focusable way — its reason is the frame's, so no name\n // bubble of their own beside it. `disabled` (a lock's reason wins over it) and\n // Today's \"now is out of bounds\", which no press here changes, stay native.\n const off = Boolean(disabled) && !locked;\n const prevRefused = locked || outside(prevKey);\n const nextRefused = locked || outside(nextKey);\n return withReason(\n // `flex-wrap`: where the heading and the buttons do not fit one row, the buttons\n // take the next line, at the end (0.32.1, the 360 px Extra-large sweep;\n // docs/text-size-harmonization.md §4). In one row at 150 % \"Today ‹ ›\" left the\n // heading 30 px on a 360 px phone, and \"October 2026\" was drawn under \"Today\".\n // Where they fit, nothing changes.\n <div {...wrapperRest} ref={rootRef} className={cn(\"flex min-w-0 flex-wrap items-center gap-2\", rootClass)}>\n {Heading ? <Heading className=\"m-0 min-w-0 text-lg font-semibold\">{trigger}</Heading> : trigger}\n <div className=\"ms-auto flex shrink-0 items-center gap-1\">\n {showToday && (\n <Button\n variant=\"secondary\"\n size=\"sm\"\n disabled={!locked && (disabled || outside(nowKey))}\n aria-disabled={locked || undefined}\n aria-describedby={reasonId}\n onClick={() => {\n if (nowKey !== shownKey) change(nowKey);\n }}\n className={STEPPER_REFUSED}\n >\n {years ? labels.thisYear : labels.today}\n </Button>\n )}\n <IconButton\n aria-label={years ? labels.previousYear : labels.previousMonth}\n disabled={off}\n aria-disabled={(!off && prevRefused) || undefined}\n aria-describedby={reasonId}\n tooltip={!locked}\n onClick={() => {\n if (!prevRefused) change(prevKey);\n }}\n className={STEPPER_ARROW_REFUSED}\n >\n {/* Mirrored in RTL, where \"previous\" points the other way. */}\n <ChevronLeft className=\"rtl:-scale-x-100\" />\n </IconButton>\n <IconButton\n aria-label={years ? labels.nextYear : labels.nextMonth}\n disabled={off}\n aria-disabled={(!off && nextRefused) || undefined}\n aria-describedby={reasonId}\n tooltip={!locked}\n onClick={() => {\n if (!nextRefused) change(nextKey);\n }}\n className={STEPPER_ARROW_REFUSED}\n >\n <ChevronRight className=\"rtl:-scale-x-100\" />\n </IconButton>\n </div>\n </div>,\n );\n }\n\n const field = (\n // `w-fit` for the toolbar size: the calendar glyph is placed against this box, and a\n // full-width box would park it at the far end of the row, away from its trigger.\n // At Large a labelled box is a grid: the label above, the calendar glyph measured\n // against the trigger's row (LABEL_ABOVE_BOX, 0.33 §10.17).\n <div\n {...wrapperRest}\n ref={rootRef}\n className={cn(\"relative\", small && \"w-fit max-w-full\", label !== undefined && LABEL_ABOVE_BOX, rootClass)}\n >\n {label !== undefined && <FieldLabelLine label={label} hint={messages.labelHint} />}\n {/* The hidden twin the trigger is named by — see DateField. `sr-only-fixed`,\n and inside this `relative` root either way. */}\n {named && (\n <span id={labelId} aria-hidden className=\"sr-only-fixed\">\n {label}\n </span>\n )}\n <Popover\n width=\"16rem\"\n panelId={panelId}\n labels={{ panel: panelLabel }}\n dir={dir}\n trigger={({ open, toggle, ref }) => (\n <MonthFieldTrigger\n open={open}\n toggle={() => {\n setDir(dirOf(rootRef.current));\n toggle();\n }}\n triggerRef={ref}\n fieldRef={fieldRef}\n triggerText={triggerText}\n hasValue={shownKey !== null}\n // As DatePicker: a caller's reference first; an `id` without a label of our\n // own references the trigger itself, so an external <label htmlFor> names it.\n labelledBy={[\n aria[\"aria-labelledby\"],\n named ? labelId : !aria[\"aria-labelledby\"] && aria.id ? aria.id : undefined,\n valueId,\n ]\n .filter(Boolean)\n .join(\" \")}\n aria={aria}\n valueId={valueId}\n panelId={panelId}\n padded={label !== undefined}\n small={small}\n disabled={disabled}\n locked={locked}\n invalid={invalid || hasContent(error)}\n className={triggerClassName}\n />\n )}\n >\n {grid}\n </Popover>\n <Calendar\n aria-hidden\n className=\"pointer-events-none absolute end-2.5 top-1/2 size-4 -translate-y-1/2 text-placeholder\"\n />\n </div>\n );\n // An unlabelled picker has no label line: a FieldHint goes at its end, outside the\n // box, as on an unlabelled Select.\n const endHint = label === undefined ? messages.labelHint : undefined;\n return (\n <FieldBox below={messages.below} reserve={\"error\" in props || \"hint\" in props}>\n {withReason(\n endHint === undefined ? (\n field\n ) : (\n <div className=\"flex items-center gap-1.5\">\n <div className={cn(\"min-w-0\", !small && \"flex-1\")}>{field}</div>\n <span className=\"flex shrink-0 items-center\">{endHint}</span>\n </div>\n ),\n )}\n </FieldBox>\n );\n}\n"],"mappings":"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","names":[]}
@@ -24,9 +24,9 @@ export interface MultiSelectOption<V extends string | number = string | number>
24
24
  * inferred from `options` and `value`, so `onChange` hands back `number[]` for number
25
25
  * ids and `string[]` for string ones, and no call site casts. */
26
26
  export interface MultiSelectProps<V extends string | number = string | number> extends Omit<ComponentPropsWithoutRef<"div">, "onChange">, FieldHelpProps {
27
- options: MultiSelectOption<V>[];
27
+ options: readonly MultiSelectOption<V>[];
28
28
  /** The chosen values — the select family's `value` (API-4). */
29
- value?: V[];
29
+ value?: readonly V[];
30
30
  onChange: (next: V[]) => void;
31
31
  /** Singular/plural countable label, used when 1+ items are selected. Default:
32
32
  * `labels.selectedCount`, else `multiSelect.selectedCount` from the provider — the
@@ -19,6 +19,7 @@ import {
19
19
  DEFAULT_MULTI_SELECT_LABELS,
20
20
  useKitLabels
21
21
  } from "../i18n/kit-labels.js";
22
+ const TOOLBAR_BUTTON = "rounded px-2 py-0.5 text-secondary hover:bg-hover aria-disabled:cursor-not-allowed aria-disabled:opacity-40 aria-disabled:hover:bg-transparent";
22
23
  function MultiSelect(props) {
23
24
  const {
24
25
  options,
@@ -175,9 +176,11 @@ function MultiSelect(props) {
175
176
  "button",
176
177
  {
177
178
  type: "button",
178
- onClick: selectAll,
179
- disabled: !canSelectAll,
180
- className: "rounded px-2 py-0.5 text-secondary hover:bg-hover disabled:opacity-40",
179
+ onClick: () => {
180
+ if (canSelectAll) selectAll();
181
+ },
182
+ "aria-disabled": !canSelectAll || void 0,
183
+ className: TOOLBAR_BUTTON,
181
184
  children: text.selectAll
182
185
  }
183
186
  ),
@@ -185,9 +188,11 @@ function MultiSelect(props) {
185
188
  "button",
186
189
  {
187
190
  type: "button",
188
- onClick: clearAll,
189
- disabled: !canClear,
190
- className: "rounded px-2 py-0.5 text-secondary hover:bg-hover disabled:opacity-40",
191
+ onClick: () => {
192
+ if (canClear) clearAll();
193
+ },
194
+ "aria-disabled": !canClear || void 0,
195
+ className: TOOLBAR_BUTTON,
191
196
  children: text.clear
192
197
  }
193
198
  )
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/multi-select.tsx"],"sourcesContent":["import { useCallback, useId, useMemo } from \"react\";\nimport type { ComponentPropsWithoutRef, ReactNode, Ref } from \"react\";\nimport { FieldChevron, FIELD_TRIGGER, FIELD_INVALID, FIELD_FLOATING_PAD } from \"./field-base\";\nimport { FieldLabel } from \"./field-parts\";\nimport { cn } from \"../lib/cn\";\nimport { ComboboxPanel, useComboboxCore, type ComboOption } from \"./combobox-core\";\nimport {\n assignRef,\n EndHintRow,\n LABEL_IN_ROW,\n LockedReason,\n StaticLabelRow,\n useFieldMessages,\n useLockReason,\n type DisabledReasonDisplay,\n type FieldHelpProps,\n} from \"./field-parts\";\nimport type { CommitScope } from \"./write-lock\";\nimport {\n DEFAULT_COMMON_LABELS,\n DEFAULT_MULTI_SELECT_LABELS,\n useKitLabels,\n type MultiSelectLabels,\n type LabelOverride,\n} from \"../i18n/kit-labels\";\n\n/**\n * One row of a {@link MultiSelect}. Generic over its value since 0.34 (API-4), so a list of\n * number ids hands `onChange` numbers — `MultiSelectOption` alone is the old\n * `string | number` row.\n */\nexport interface MultiSelectOption<V extends string | number = string | number> {\n value: V;\n label: string;\n /** A second line under the label — a postcode, a count (keksdose #147). Searchable. */\n hint?: string;\n /**\n * Shown but not takeable — {@link ComboOption.disabled}: dimmed, `aria-disabled`, passed\n * over by the arrow keys, and a press does nothing. A chosen disabled row stays chosen:\n * \"Select all\" and \"Clear\" leave it as it is. Say why in `hint`.\n */\n disabled?: boolean;\n}\n\n/** `onChange` is the kit's — \"the selection changed\", carrying the new values —\n * rather than the div's form event. Generic over the option value (API-4): `V` is\n * inferred from `options` and `value`, so `onChange` hands back `number[]` for number\n * ids and `string[]` for string ones, and no call site casts. */\nexport interface MultiSelectProps<V extends string | number = string | number>\n extends Omit<ComponentPropsWithoutRef<\"div\">, \"onChange\">, FieldHelpProps {\n options: MultiSelectOption<V>[];\n /** The chosen values — the select family's `value` (API-4). */\n value?: V[];\n onChange: (next: V[]) => void;\n /** Singular/plural countable label, used when 1+ items are selected. Default:\n * `labels.selectedCount`, else `multiSelect.selectedCount` from the provider — the\n * bare count in English. */\n itemLabel?: (count: number) => string;\n /** Shown when nothing is picked, unless `labels.all` says otherwise — it has always\n * doubled as the \"nothing picked\" text. */\n placeholder?: string;\n /** Embedded top-boundary label, matching the native Input/Select fields. */\n label?: ReactNode;\n /**\n * Per-instance overrides of the provider's `multiSelect` namespace, over English — the\n * kit's `labels` shape, where this took four string props until 0.35 (API-4: `allLabel`,\n * `searchLabel`, `selectAllLabel`, `clearLabel`). `all` is the trigger when nothing is\n * picked: `labels.all`, then `placeholder`, then the provider's.\n */\n labels?: LabelOverride<MultiSelectLabels>;\n /**\n * Extra classes for the open PANEL. The panel is portalled now (dev#548) and sizes\n * itself: as wide as the trigger and at least 16rem, growing with its rows up to 32rem\n * (or the trigger) — what this prop was asked for (keksdose feedback #147: the market\n * picker's postcode, distance and receipt-count hints were cut at 16rem). A `w-*` class\n * here no longer sets the width.\n */\n panelClassName?: string;\n /** Required and unanswered — {@link FIELD_INVALID}, the same rose border the\n * native `Select` has worn since feedback #235. Every field-styled control in\n * this package carries it now, so a form can mark any of its fields rather than\n * only the one that happened to have it first. */\n invalid?: boolean;\n /** What is wrong with the selection, in the caller's words. Rendered under the\n * field, pointed at by the trigger's `aria-describedby` (merged with any the\n * caller passed) and implies `invalid` — {@link Input}'s `error`, on the same\n * rules. */\n error?: ReactNode;\n /**\n * Standing advice for the field — {@link Combobox}'s `hint` (keksdose K4): plain TEXT\n * is a caption under the field on the trigger's `aria-describedby`, before any error;\n * a {@link FieldHint} \"?\" rides the label line, or with no label sits at the trigger's\n * end edge, outside the box. Not an option's `hint`, which stays on its row.\n */\n hint?: ReactNode;\n /** The trigger cannot be opened: FIELD_BASE's settled grey, no chevron (as a\n * disabled {@link Select} drops its own), and a dimmed label. */\n disabled?: boolean;\n /**\n * Why the selection cannot be changed — {@link EntityCombobox}'s `disabledReason`: the\n * trigger stays focusable and `aria-disabled`, the list does not open and `onChange`\n * never fires; the reason is a line under the field at Large and on a touch screen,\n * else in the kit {@link Tooltip} (`disabledReasonDisplay`), and on the trigger's\n * `aria-describedby`. Wins over `disabled`.\n */\n disabledReason?: ReactNode;\n /** Where the reason shows — {@link Button}'s `disabledReasonDisplay`: a line under the\n * field at Large and on a touch screen by default, the tooltip otherwise. */\n disabledReasonDisplay?: DisabledReasonDisplay;\n /** This picker COMMITS — toggling saves (a filter persisted per user, a settings row).\n * Locked the `disabledReason` way under a locked {@link WriteLockProvider}, with the\n * lock's reason. Leave it off inside a form that has its own Save. */\n commit?: CommitScope;\n /** The TRIGGER — the focusable `<button>` — as a React 19 ref prop, as on\n * {@link MultiEntityCombobox} (react-hook-form's focus-on-error). Not the wrapper. */\n ref?: Ref<HTMLButtonElement>;\n}\n\n/**\n * Pick several of a fixed list — a filter's accounts, a comparison's data sets — with a\n * search box, \"Select all\" and \"Clear\"; nothing picked reads as \"all\".\n *\n * Built on the picker family's foundation since round 0.34 (Wave 2, DUP-4):\n * `useComboboxCore` + `ComboboxPanel`, as {@link MultiEntityCombobox} is. It was an\n * absolute `DropdownPanel`, which an `overflow` ancestor clipped (dev#548) and which on a\n * phone was a 16rem panel between the field and the keyboard; it now has the full-screen\n * sheet (live #200), the portal, the live result count, disabled rows and the search\n * that folds accents (\"zurich\" finds Zürich, DUP-5).\n */\nexport function MultiSelect<V extends string | number = string | number>(props: MultiSelectProps<V>) {\n const {\n options,\n value: valueProp,\n onChange,\n itemLabel,\n placeholder,\n label,\n labels: labelsProp,\n className,\n panelClassName,\n invalid,\n error,\n hint,\n help,\n disabled,\n disabledReason,\n disabledReasonDisplay,\n commit,\n ref,\n id,\n \"aria-label\": ariaLabel,\n // Off `rest` and onto the trigger, which is the combobox a reader meets: `Field`'s\n // render-prop spreads `{ id, aria-describedby, aria-invalid, aria-required }`, and on\n // the wrapper div the label named nothing and the required state described nothing\n // (DUP-8, refactor audit 2026-10 — Combobox's rule).\n \"aria-describedby\": ariaDescribedBy,\n \"aria-invalid\": ariaInvalid,\n \"aria-required\": ariaRequired,\n ...rest\n } = props;\n const value = useMemo<V[]>(() => valueProp ?? [], [valueProp]);\n // The rows as the family's options — the hint is the sublabel, searched like the label.\n const comboOptions = useMemo(\n () =>\n options.map((o): ComboOption<V> => ({ value: o.value, label: o.label, sublabel: o.hint, disabled: o.disabled })),\n [options],\n );\n // Back closes the open list, as it did on `useDropdown` (keksdose live #309).\n const core = useComboboxCore<V>({ options: comboOptions, backCloses: true });\n const { open, setOpen, triggerRef } = core;\n // The core anchors the panel to the trigger and hands focus back to it; the caller's\n // `ref` wants the same element. One callback feeds both.\n const setTrigger = useCallback(\n (el: HTMLButtonElement | null) => {\n triggerRef.current = el;\n assignRef(ref, el);\n },\n [ref, triggerRef],\n );\n // `labels` > the provider's `multiSelect` > English. `all` is resolved apart below:\n // between the instance's and the provider's sits `placeholder`, which has always\n // doubled as the \"nothing picked\" text, and a caller who set it must keep seeing it.\n const text = useKitLabels(\"multiSelect\", DEFAULT_MULTI_SELECT_LABELS, {\n search: labelsProp?.search,\n selectAll: labelsProp?.selectAll,\n clear: labelsProp?.clear,\n selectedCount: labelsProp?.selectedCount ?? itemLabel,\n });\n const common = useKitLabels(\"common\", DEFAULT_COMMON_LABELS);\n\n // The lock — EntityCombobox's: the trigger stays reachable and says why.\n const lock = useLockReason(commit, disabledReason);\n const inert = lock.locked || Boolean(disabled);\n // A lock (or `disabled`) arriving while the list is up closes it — adjusted while\n // rendering, as CountrySelect does, so no frame shows a list that can no longer be used.\n if (inert && open) setOpen(false);\n\n // The caller's ids, the caption, a lock's reason, the error — Select's order.\n const field = useFieldMessages({\n hint,\n help,\n name: label ?? ariaLabel,\n error,\n invalid,\n describedBy: ariaDescribedBy,\n ariaInvalid,\n reasonId: lock.locked && lock.reasonId,\n });\n // One id per instance: `aria-controls` on the trigger names the list while the\n // list is still closed.\n const listboxId = `${useId()}-listbox`;\n\n const valueSet = useMemo(() => new Set<V>(value), [value]);\n const allChecked = options.length > 0 && options.every((o) => valueSet.has(o.value));\n const selectedSummary =\n value.length === 0 || allChecked ? (labelsProp?.all ?? placeholder ?? text.all) : text.selectedCount(value.length);\n\n const toggle = (o: ComboOption<V>) => {\n if (lock.locked) return;\n if (valueSet.has(o.value)) onChange(value.filter((v) => v !== o.value));\n else onChange([...value, o.value]);\n // Stays open, as a pressed row always has: this control answers \"which of these\",\n // which is rarely one of them.\n };\n // A chosen disabled row stays chosen, an unchosen one stays out — the row refuses to be\n // toggled, by a press or by the two actions.\n const selectAll = () => onChange(options.filter((o) => !o.disabled || valueSet.has(o.value)).map((o) => o.value));\n const clearAll = () => onChange(options.filter((o) => o.disabled && valueSet.has(o.value)).map((o) => o.value));\n const canSelectAll = options.some((o) => !o.disabled && !valueSet.has(o.value));\n const canClear = options.some((o) => !o.disabled && valueSet.has(o.value));\n\n const trigger = (\n <button\n ref={setTrigger}\n id={id}\n type=\"button\"\n // The focusable kind of disabled while locked (Button's rule): native `disabled`\n // would take the field out of the tab order, and the reason with it.\n disabled={lock.locked ? undefined : disabled}\n aria-disabled={lock.locked || undefined}\n // A combobox, not a bare button: this trigger carried `aria-invalid`, which\n // `button` does not support, so a required-and-empty filter painted its rose\n // border and announced nothing (ESLint's `role-supports-aria-props`, the\n // audit's §a11y). The role that describes it is the one that also supports\n // the attribute.\n role=\"combobox\"\n aria-expanded={open}\n aria-controls={listboxId}\n aria-haspopup=\"listbox\"\n // The label is a floating <span>, not a <label for>, so without this the\n // trigger announces only its summary — \"3 selected\", with nothing saying\n // three of WHAT. The label AND the summary, because `aria-label` replaces\n // the content rather than adding to it; the same composition its two\n // siblings use, so a form row of pickers reads the same way throughout.\n // A caller's own name wins: two filters labelled \"Accounts\" on one screen have\n // to be told apart, and only the caller knows by what.\n aria-label={ariaLabel ?? (typeof label === \"string\" ? common.fieldValue(label, selectedSummary) : undefined)}\n onClick={() => {\n if (!inert) setOpen((v) => !v);\n }}\n // Down/Up opens the list from the closed trigger, per the APG; Enter and\n // Space already do through the button's own click.\n onKeyDown={(e) => {\n if (inert) return;\n if (e.key === \"ArrowDown\" || e.key === \"ArrowUp\") {\n e.preventDefault();\n setOpen(true);\n }\n }}\n aria-invalid={field.isInvalid || undefined}\n aria-required={ariaRequired}\n // On the TRIGGER, where focus lands — the `aria-describedby` a caller put on\n // the component is taken off the wrapper for the same reason.\n aria-describedby={field.describedBy}\n className={cn(\n FIELD_TRIGGER,\n \"pe-9\",\n label !== undefined && FIELD_FLOATING_PAD,\n // FIELD_TRIGGER's hover would still light a trigger nothing can open.\n \"disabled:hover:bg-surface-2\",\n // Dimmed like EntityCombobox's and MultiEntityCombobox's disabled triggers;\n // only the label was, so a disabled MultiSelect read as live (showcase audit).\n \"disabled:cursor-not-allowed disabled:opacity-50\",\n lock.locked && \"cursor-not-allowed opacity-50\",\n field.isInvalid && FIELD_INVALID,\n )}\n >\n {/* The summary IS the field's value, so it inherits FIELD_BASE's ink rather\n than restating a lighter one (Keksdose dev#477). An ellipsis at Normal; from\n Large up it wraps and the trigger grows, as DatePicker's value does (text\n size §10.17, round 0.34 T1). */}\n <span className=\"truncate-until-large\">{selectedSummary}</span>\n {!inert && <FieldChevron />}\n </button>\n );\n\n return (\n // `rest` dresses the wrapper; the name belongs to the trigger below it, which is\n // the thing with a role. Spread FIRST so the ARIA the trigger composes, and the\n // handlers that open the list, cannot be replaced by accident from outside.\n <div {...rest} className={cn(\"relative\", className)}>\n {label !== undefined && field.labelHint === undefined && (\n <FieldLabel className={inert ? \"opacity-50\" : undefined}>{label}</FieldLabel>\n )}\n <EndHintRow hint={label === undefined ? field.labelHint : undefined}>\n {lock.locked ? (\n // Button's frame: the line under the trigger, or the Tooltip with a hidden copy\n // for `aria-describedby`.\n <LockedReason lock={lock} display={disabledReasonDisplay} className=\"block\">\n {trigger}\n </LockedReason>\n ) : (\n trigger\n )}\n </EndHintRow>\n {/* With a \"?\" the label shares the top strip with it — after the trigger, so the\n \"?\" follows the control in the tab order, as on Input and Select. */}\n {label !== undefined && field.labelHint !== undefined && (\n <StaticLabelRow hint={field.labelHint}>\n <FieldLabel className={cn(LABEL_IN_ROW, inert && \"opacity-50\")}>{label}</FieldLabel>\n </StaticLabelRow>\n )}\n {field.below}\n <ComboboxPanel\n core={core}\n listboxId={listboxId}\n multi\n // On a phone the list is a full-screen sheet, which needs the field's own name\n // to say what it asks for (live #200).\n sheetTitle={label ?? placeholder}\n searchPlaceholder={text.search}\n isSelected={(v) => valueSet.has(v)}\n onChoose={toggle}\n showCreate={false}\n onCreate={() => {}}\n createContent={null}\n // At least the trigger's width and 16rem, growing with the rows to 32rem: the\n // hints are why it was widened at all (keksdose #147).\n width={{ minRem: 16, fit: true, maxRem: 32 }}\n className={panelClassName}\n toolbar={\n <div className=\"flex items-center justify-between gap-2 px-2 py-1.5 text-xs\">\n <button\n type=\"button\"\n onClick={selectAll}\n disabled={!canSelectAll}\n className=\"rounded px-2 py-0.5 text-secondary hover:bg-hover disabled:opacity-40\"\n >\n {text.selectAll}\n </button>\n <button\n type=\"button\"\n onClick={clearAll}\n disabled={!canClear}\n className=\"rounded px-2 py-0.5 text-secondary hover:bg-hover disabled:opacity-40\"\n >\n {text.clear}\n </button>\n </div>\n }\n />\n </div>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/components/multi-select.tsx"],"sourcesContent":["import { useCallback, useId, useMemo } from \"react\";\nimport type { ComponentPropsWithoutRef, ReactNode, Ref } from \"react\";\nimport { FieldChevron, FIELD_TRIGGER, FIELD_INVALID, FIELD_FLOATING_PAD } from \"./field-base\";\nimport { FieldLabel } from \"./field-parts\";\nimport { cn } from \"../lib/cn\";\nimport { ComboboxPanel, useComboboxCore, type ComboOption } from \"./combobox-core\";\nimport {\n assignRef,\n EndHintRow,\n LABEL_IN_ROW,\n LockedReason,\n StaticLabelRow,\n useFieldMessages,\n useLockReason,\n type DisabledReasonDisplay,\n type FieldHelpProps,\n} from \"./field-parts\";\nimport type { CommitScope } from \"./write-lock\";\nimport {\n DEFAULT_COMMON_LABELS,\n DEFAULT_MULTI_SELECT_LABELS,\n useKitLabels,\n type MultiSelectLabels,\n type LabelOverride,\n} from \"../i18n/kit-labels\";\n\n/**\n * One row of a {@link MultiSelect}. Generic over its value since 0.34 (API-4), so a list of\n * number ids hands `onChange` numbers — `MultiSelectOption` alone is the old\n * `string | number` row.\n */\nexport interface MultiSelectOption<V extends string | number = string | number> {\n value: V;\n label: string;\n /** A second line under the label — a postcode, a count (keksdose #147). Searchable. */\n hint?: string;\n /**\n * Shown but not takeable — {@link ComboOption.disabled}: dimmed, `aria-disabled`, passed\n * over by the arrow keys, and a press does nothing. A chosen disabled row stays chosen:\n * \"Select all\" and \"Clear\" leave it as it is. Say why in `hint`.\n */\n disabled?: boolean;\n}\n\n/** `onChange` is the kit's — \"the selection changed\", carrying the new values —\n * rather than the div's form event. Generic over the option value (API-4): `V` is\n * inferred from `options` and `value`, so `onChange` hands back `number[]` for number\n * ids and `string[]` for string ones, and no call site casts. */\nexport interface MultiSelectProps<V extends string | number = string | number>\n extends Omit<ComponentPropsWithoutRef<\"div\">, \"onChange\">, FieldHelpProps {\n options: readonly MultiSelectOption<V>[];\n /** The chosen values — the select family's `value` (API-4). */\n value?: readonly V[];\n onChange: (next: V[]) => void;\n /** Singular/plural countable label, used when 1+ items are selected. Default:\n * `labels.selectedCount`, else `multiSelect.selectedCount` from the provider — the\n * bare count in English. */\n itemLabel?: (count: number) => string;\n /** Shown when nothing is picked, unless `labels.all` says otherwise — it has always\n * doubled as the \"nothing picked\" text. */\n placeholder?: string;\n /** Embedded top-boundary label, matching the native Input/Select fields. */\n label?: ReactNode;\n /**\n * Per-instance overrides of the provider's `multiSelect` namespace, over English — the\n * kit's `labels` shape, where this took four string props until 0.35 (API-4: `allLabel`,\n * `searchLabel`, `selectAllLabel`, `clearLabel`). `all` is the trigger when nothing is\n * picked: `labels.all`, then `placeholder`, then the provider's.\n */\n labels?: LabelOverride<MultiSelectLabels>;\n /**\n * Extra classes for the open PANEL. The panel is portalled now (dev#548) and sizes\n * itself: as wide as the trigger and at least 16rem, growing with its rows up to 32rem\n * (or the trigger) — what this prop was asked for (keksdose feedback #147: the market\n * picker's postcode, distance and receipt-count hints were cut at 16rem). A `w-*` class\n * here no longer sets the width.\n */\n panelClassName?: string;\n /** Required and unanswered — {@link FIELD_INVALID}, the same rose border the\n * native `Select` has worn since feedback #235. Every field-styled control in\n * this package carries it now, so a form can mark any of its fields rather than\n * only the one that happened to have it first. */\n invalid?: boolean;\n /** What is wrong with the selection, in the caller's words. Rendered under the\n * field, pointed at by the trigger's `aria-describedby` (merged with any the\n * caller passed) and implies `invalid` — {@link Input}'s `error`, on the same\n * rules. */\n error?: ReactNode;\n /**\n * Standing advice for the field — {@link Combobox}'s `hint` (keksdose K4): plain TEXT\n * is a caption under the field on the trigger's `aria-describedby`, before any error;\n * a {@link FieldHint} \"?\" rides the label line, or with no label sits at the trigger's\n * end edge, outside the box. Not an option's `hint`, which stays on its row.\n */\n hint?: ReactNode;\n /** The trigger cannot be opened: FIELD_BASE's settled grey, no chevron (as a\n * disabled {@link Select} drops its own), and a dimmed label. */\n disabled?: boolean;\n /**\n * Why the selection cannot be changed — {@link EntityCombobox}'s `disabledReason`: the\n * trigger stays focusable and `aria-disabled`, the list does not open and `onChange`\n * never fires; the reason is a line under the field at Large and on a touch screen,\n * else in the kit {@link Tooltip} (`disabledReasonDisplay`), and on the trigger's\n * `aria-describedby`. Wins over `disabled`.\n */\n disabledReason?: ReactNode;\n /** Where the reason shows — {@link Button}'s `disabledReasonDisplay`: a line under the\n * field at Large and on a touch screen by default, the tooltip otherwise. */\n disabledReasonDisplay?: DisabledReasonDisplay;\n /** This picker COMMITS — toggling saves (a filter persisted per user, a settings row).\n * Locked the `disabledReason` way under a locked {@link WriteLockProvider}, with the\n * lock's reason. Leave it off inside a form that has its own Save. */\n commit?: CommitScope;\n /** The TRIGGER — the focusable `<button>` — as a React 19 ref prop, as on\n * {@link MultiEntityCombobox} (react-hook-form's focus-on-error). Not the wrapper. */\n ref?: Ref<HTMLButtonElement>;\n}\n\n/** \"Select all\" and \"Clear\": dimmed, with no hover, while there is nothing for them to do. */\nconst TOOLBAR_BUTTON =\n \"rounded px-2 py-0.5 text-secondary hover:bg-hover aria-disabled:cursor-not-allowed aria-disabled:opacity-40 aria-disabled:hover:bg-transparent\";\n\n/**\n * Pick several of a fixed list — a filter's accounts, a comparison's data sets — with a\n * search box, \"Select all\" and \"Clear\"; nothing picked reads as \"all\".\n *\n * Built on the picker family's foundation since round 0.34 (Wave 2, DUP-4):\n * `useComboboxCore` + `ComboboxPanel`, as {@link MultiEntityCombobox} is. It was an\n * absolute `DropdownPanel`, which an `overflow` ancestor clipped (dev#548) and which on a\n * phone was a 16rem panel between the field and the keyboard; it now has the full-screen\n * sheet (live #200), the portal, the live result count, disabled rows and the search\n * that folds accents (\"zurich\" finds Zürich, DUP-5).\n */\nexport function MultiSelect<V extends string | number = string | number>(props: MultiSelectProps<V>) {\n const {\n options,\n value: valueProp,\n onChange,\n itemLabel,\n placeholder,\n label,\n labels: labelsProp,\n className,\n panelClassName,\n invalid,\n error,\n hint,\n help,\n disabled,\n disabledReason,\n disabledReasonDisplay,\n commit,\n ref,\n id,\n \"aria-label\": ariaLabel,\n // Off `rest` and onto the trigger, which is the combobox a reader meets: `Field`'s\n // render-prop spreads `{ id, aria-describedby, aria-invalid, aria-required }`, and on\n // the wrapper div the label named nothing and the required state described nothing\n // (DUP-8, refactor audit 2026-10 — Combobox's rule).\n \"aria-describedby\": ariaDescribedBy,\n \"aria-invalid\": ariaInvalid,\n \"aria-required\": ariaRequired,\n ...rest\n } = props;\n const value = useMemo<readonly V[]>(() => valueProp ?? [], [valueProp]);\n // The rows as the family's options — the hint is the sublabel, searched like the label.\n const comboOptions = useMemo(\n () =>\n options.map((o): ComboOption<V> => ({ value: o.value, label: o.label, sublabel: o.hint, disabled: o.disabled })),\n [options],\n );\n // Back closes the open list, as it did on `useDropdown` (keksdose live #309).\n const core = useComboboxCore<V>({ options: comboOptions, backCloses: true });\n const { open, setOpen, triggerRef } = core;\n // The core anchors the panel to the trigger and hands focus back to it; the caller's\n // `ref` wants the same element. One callback feeds both.\n const setTrigger = useCallback(\n (el: HTMLButtonElement | null) => {\n triggerRef.current = el;\n assignRef(ref, el);\n },\n [ref, triggerRef],\n );\n // `labels` > the provider's `multiSelect` > English. `all` is resolved apart below:\n // between the instance's and the provider's sits `placeholder`, which has always\n // doubled as the \"nothing picked\" text, and a caller who set it must keep seeing it.\n const text = useKitLabels(\"multiSelect\", DEFAULT_MULTI_SELECT_LABELS, {\n search: labelsProp?.search,\n selectAll: labelsProp?.selectAll,\n clear: labelsProp?.clear,\n selectedCount: labelsProp?.selectedCount ?? itemLabel,\n });\n const common = useKitLabels(\"common\", DEFAULT_COMMON_LABELS);\n\n // The lock — EntityCombobox's: the trigger stays reachable and says why.\n const lock = useLockReason(commit, disabledReason);\n const inert = lock.locked || Boolean(disabled);\n // A lock (or `disabled`) arriving while the list is up closes it — adjusted while\n // rendering, as CountrySelect does, so no frame shows a list that can no longer be used.\n if (inert && open) setOpen(false);\n\n // The caller's ids, the caption, a lock's reason, the error — Select's order.\n const field = useFieldMessages({\n hint,\n help,\n name: label ?? ariaLabel,\n error,\n invalid,\n describedBy: ariaDescribedBy,\n ariaInvalid,\n reasonId: lock.locked && lock.reasonId,\n });\n // One id per instance: `aria-controls` on the trigger names the list while the\n // list is still closed.\n const listboxId = `${useId()}-listbox`;\n\n const valueSet = useMemo(() => new Set<V>(value), [value]);\n const allChecked = options.length > 0 && options.every((o) => valueSet.has(o.value));\n const selectedSummary =\n value.length === 0 || allChecked ? (labelsProp?.all ?? placeholder ?? text.all) : text.selectedCount(value.length);\n\n const toggle = (o: ComboOption<V>) => {\n if (lock.locked) return;\n if (valueSet.has(o.value)) onChange(value.filter((v) => v !== o.value));\n else onChange([...value, o.value]);\n // Stays open, as a pressed row always has: this control answers \"which of these\",\n // which is rarely one of them.\n };\n // A chosen disabled row stays chosen, an unchosen one stays out — the row refuses to be\n // toggled, by a press or by the two actions.\n const selectAll = () => onChange(options.filter((o) => !o.disabled || valueSet.has(o.value)).map((o) => o.value));\n const clearAll = () => onChange(options.filter((o) => o.disabled && valueSet.has(o.value)).map((o) => o.value));\n const canSelectAll = options.some((o) => !o.disabled && !valueSet.has(o.value));\n const canClear = options.some((o) => !o.disabled && valueSet.has(o.value));\n\n const trigger = (\n <button\n ref={setTrigger}\n id={id}\n type=\"button\"\n // The focusable kind of disabled while locked (Button's rule): native `disabled`\n // would take the field out of the tab order, and the reason with it.\n disabled={lock.locked ? undefined : disabled}\n aria-disabled={lock.locked || undefined}\n // A combobox, not a bare button: this trigger carried `aria-invalid`, which\n // `button` does not support, so a required-and-empty filter painted its rose\n // border and announced nothing (ESLint's `role-supports-aria-props`, the\n // audit's §a11y). The role that describes it is the one that also supports\n // the attribute.\n role=\"combobox\"\n aria-expanded={open}\n aria-controls={listboxId}\n aria-haspopup=\"listbox\"\n // The label is a floating <span>, not a <label for>, so without this the\n // trigger announces only its summary — \"3 selected\", with nothing saying\n // three of WHAT. The label AND the summary, because `aria-label` replaces\n // the content rather than adding to it; the same composition its two\n // siblings use, so a form row of pickers reads the same way throughout.\n // A caller's own name wins: two filters labelled \"Accounts\" on one screen have\n // to be told apart, and only the caller knows by what.\n aria-label={ariaLabel ?? (typeof label === \"string\" ? common.fieldValue(label, selectedSummary) : undefined)}\n onClick={() => {\n if (!inert) setOpen((v) => !v);\n }}\n // Down/Up opens the list from the closed trigger, per the APG; Enter and\n // Space already do through the button's own click.\n onKeyDown={(e) => {\n if (inert) return;\n if (e.key === \"ArrowDown\" || e.key === \"ArrowUp\") {\n e.preventDefault();\n setOpen(true);\n }\n }}\n aria-invalid={field.isInvalid || undefined}\n aria-required={ariaRequired}\n // On the TRIGGER, where focus lands — the `aria-describedby` a caller put on\n // the component is taken off the wrapper for the same reason.\n aria-describedby={field.describedBy}\n className={cn(\n FIELD_TRIGGER,\n \"pe-9\",\n label !== undefined && FIELD_FLOATING_PAD,\n // FIELD_TRIGGER's hover would still light a trigger nothing can open.\n \"disabled:hover:bg-surface-2\",\n // Dimmed like EntityCombobox's and MultiEntityCombobox's disabled triggers;\n // only the label was, so a disabled MultiSelect read as live (showcase audit).\n \"disabled:cursor-not-allowed disabled:opacity-50\",\n lock.locked && \"cursor-not-allowed opacity-50\",\n field.isInvalid && FIELD_INVALID,\n )}\n >\n {/* The summary IS the field's value, so it inherits FIELD_BASE's ink rather\n than restating a lighter one (Keksdose dev#477). An ellipsis at Normal; from\n Large up it wraps and the trigger grows, as DatePicker's value does (text\n size §10.17, round 0.34 T1). */}\n <span className=\"truncate-until-large\">{selectedSummary}</span>\n {!inert && <FieldChevron />}\n </button>\n );\n\n return (\n // `rest` dresses the wrapper; the name belongs to the trigger below it, which is\n // the thing with a role. Spread FIRST so the ARIA the trigger composes, and the\n // handlers that open the list, cannot be replaced by accident from outside.\n <div {...rest} className={cn(\"relative\", className)}>\n {label !== undefined && field.labelHint === undefined && (\n <FieldLabel className={inert ? \"opacity-50\" : undefined}>{label}</FieldLabel>\n )}\n <EndHintRow hint={label === undefined ? field.labelHint : undefined}>\n {lock.locked ? (\n // Button's frame: the line under the trigger, or the Tooltip with a hidden copy\n // for `aria-describedby`.\n <LockedReason lock={lock} display={disabledReasonDisplay} className=\"block\">\n {trigger}\n </LockedReason>\n ) : (\n trigger\n )}\n </EndHintRow>\n {/* With a \"?\" the label shares the top strip with it — after the trigger, so the\n \"?\" follows the control in the tab order, as on Input and Select. */}\n {label !== undefined && field.labelHint !== undefined && (\n <StaticLabelRow hint={field.labelHint}>\n <FieldLabel className={cn(LABEL_IN_ROW, inert && \"opacity-50\")}>{label}</FieldLabel>\n </StaticLabelRow>\n )}\n {field.below}\n <ComboboxPanel\n core={core}\n listboxId={listboxId}\n multi\n // On a phone the list is a full-screen sheet, which needs the field's own name\n // to say what it asks for (live #200).\n sheetTitle={label ?? placeholder}\n searchPlaceholder={text.search}\n isSelected={(v) => valueSet.has(v)}\n onChoose={toggle}\n showCreate={false}\n onCreate={() => {}}\n createContent={null}\n // At least the trigger's width and 16rem, growing with the rows to 32rem: the\n // hints are why it was widened at all (keksdose #147).\n width={{ minRem: 16, fit: true, maxRem: 32 }}\n className={panelClassName}\n // Reached by Tab from the search box (the panel's rows are not tab stops), and\n // `aria-disabled` with a guarded click rather than `disabled`: a pressed \"Clear\"\n // with nothing left to clear disabled itself under the focus, which dropped it to\n // <body> — JumpCalendar's arrows are the rule (refactor audit 2026-10-30, INP-9).\n toolbar={\n <div className=\"flex items-center justify-between gap-2 px-2 py-1.5 text-xs\">\n <button\n type=\"button\"\n onClick={() => {\n if (canSelectAll) selectAll();\n }}\n aria-disabled={!canSelectAll || undefined}\n className={TOOLBAR_BUTTON}\n >\n {text.selectAll}\n </button>\n <button\n type=\"button\"\n onClick={() => {\n if (canClear) clearAll();\n }}\n aria-disabled={!canClear || undefined}\n className={TOOLBAR_BUTTON}\n >\n {text.clear}\n </button>\n </div>\n }\n />\n </div>\n );\n}\n"],"mappings":"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","names":[]}
@@ -4,6 +4,7 @@ import { useMemo, useRef } from "react";
4
4
  import { createPortal } from "react-dom";
5
5
  import { X } from "lucide-react";
6
6
  import { cn } from "../lib/cn.js";
7
+ import { hasContent } from "../lib/presence.js";
7
8
  import { mergeRefs } from "./field-parts.js";
8
9
  import { CLIPS_ATTRIBUTE } from "../lib/clipping.js";
9
10
  import { DropdownSearchHeader } from "./dropdown.js";
@@ -38,7 +39,7 @@ function PickerSheet({
38
39
  ...rest
39
40
  }) {
40
41
  const labels = useKitLabels("pickerSheet", DEFAULT_PICKER_SHEET_LABELS, { close: closeLabel, done: doneLabel });
41
- const hasFooter = footer !== void 0 && footer !== null && footer !== false || Boolean(done);
42
+ const hasFooter = hasContent(footer) || Boolean(done);
42
43
  useBodyScrollLock(open);
43
44
  useOverlayHistory(open, onClose);
44
45
  const vv = useVisualViewport(open);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/picker-sheet.tsx"],"sourcesContent":["import { useMemo, useRef } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { X } from \"lucide-react\";\nimport type { ComponentPropsWithoutRef, KeyboardEvent, ReactNode, Ref, RefObject } from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { mergeRefs } from \"./field-parts\";\nimport { CLIPS_ATTRIBUTE } from \"../lib/clipping\";\nimport { DropdownSearchHeader } from \"./dropdown\";\nimport { Button } from \"./button\";\nimport { useBodyScrollLock } from \"../hooks/use-body-scroll-lock\";\nimport { useOverlayHistory } from \"../hooks/use-overlay-history\";\nimport { useFocusTrap } from \"../hooks/use-focus-trap\";\nimport { takeEscape, useDismissLayer } from \"../hooks/use-dismiss\";\nimport { useVisualViewport } from \"../hooks/use-anchored-panel\";\nimport { DEFAULT_PICKER_SHEET_LABELS, useKitLabels } from \"../i18n/kit-labels\";\n\n/**\n * Two of the div's own attributes are omitted because this component already owns\n * the name: `title` here is the sheet's HEADING (and a ReactNode), not the browser's\n * tooltip string, and `onClose` is \"the sheet was dismissed\", not the DOM's\n * `<dialog>` close event. Everything else a caller can pass reaches the sheet.\n */\nexport interface PickerSheetProps extends Omit<ComponentPropsWithoutRef<\"div\">, \"title\" | \"onClose\"> {\n open: boolean;\n onClose: () => void;\n /** The field's own label — a sheet that fills the screen has to say what it is\n * asking for, which the anchored panel got for free by sitting under it. */\n title?: ReactNode;\n query: string;\n onQueryChange: (value: string) => void;\n searchPlaceholder?: string;\n /** The search box's accessible name. Default: `searchPlaceholder` — a placeholder is\n * gone the moment a character is typed, and is no name to many readers at all. */\n searchLabel?: string;\n inputRef?: RefObject<HTMLInputElement | null>;\n /** Default: `pickerSheet.close` from the {@link UiKitProvider}, else \"Close\". */\n closeLabel?: string;\n /**\n * The list's keyboard, on the search box — where focus is while the sheet is up:\n * Up/Down/Home/End/Enter (round 0.34 Wave 2, BUG-5). `ComboboxPanel` passes its own;\n * without it arrows and Enter in the sheet did nothing at all.\n */\n onSearchKeyDown?: (e: KeyboardEvent<HTMLInputElement>) => void;\n /** The `role=\"listbox\"` the search box filters, for its `aria-controls`. */\n listboxId?: string;\n /** The option the arrow keys are on, for the search box's `aria-activedescendant`. */\n activeId?: string;\n children: ReactNode;\n /**\n * A row pinned under the scrolling list — {@link FullBleedDialog}'s `footer`, on the\n * picker's sheet (kk run 79 gap 2). Outside the scroller, so it stays put however long\n * the list; padded for the home indicator, since it is the sheet's bottom edge.\n */\n footer?: ReactNode;\n /**\n * The kit's one sticky \"Done\" at the foot of the sheet (Marcel, keksdose #412): the\n * button a multi-select sheet is left by once its rows are ticked. Ticks apply as\n * they are made, so it closes (`onClose`) rather than saves. After any `footer`.\n * {@link ComboboxPanel} turns it on for every `multi` list — `MultiSelect` and\n * `MultiEntityCombobox` — and a single-select sheet, which closes on the tap that\n * picks, has no use for it. Label: `pickerSheet.done`, else \"Done\" (`doneLabel`).\n */\n done?: boolean;\n /** Default: `pickerSheet.done` from the {@link UiKitProvider}, else \"Done\". */\n doneLabel?: string;\n /** The sheet, where the rest of the props go (0.34, API-2, refactor audit 2026-10) —\n * merged with the sheet's own ref, which its focus trap holds. */\n ref?: Ref<HTMLDivElement>;\n}\n\n/**\n * The PHONE presentation of a picker: a full-screen dialog with a search box at\n * the top and the list filling everything below it (Keksdose live #200).\n *\n * *\"Paid as full screen dialog with input. Similar to the account select that\n * already appears as full screen.\"* The account field is a native `<select>`, and\n * a phone browser renders that as a full-screen list — so the app's own pickers,\n * which are anchored dropdown panels, were the odd ones out: a 320px-tall panel\n * squeezed between the field and the keyboard, showing three or four rows of a\n * list that might have two hundred entries.\n *\n * What this fixes beyond size: the panel had to be *placed* (above/below, tracking\n * the visual viewport as the keyboard opened — see `useAnchoredPanel`), and every\n * one of those decisions is a chance to be wrong on a screen this small. A sheet\n * has no placement. It also gives the search input somewhere unambiguous to live,\n * which is the other half of the request: *\"add filler possibility to the account\n * select or even better right to the hoc the account select is derived from\"* —\n * so this lives in the shared picker, not in one form's copy of it.\n *\n * Rows are `min-h-11` (44px): the touch target the rest of the app uses. The list\n * scrolls, the header does not, and the body behind it is scroll-locked so a drag\n * that overshoots the list does not move the page underneath.\n */\nexport function PickerSheet({\n open,\n onClose,\n title,\n query,\n onQueryChange,\n searchPlaceholder,\n searchLabel,\n inputRef,\n closeLabel,\n onSearchKeyDown,\n listboxId,\n activeId,\n children,\n footer,\n done,\n doneLabel,\n \"aria-label\": ariaLabel,\n className,\n style,\n ref,\n ...rest\n}: PickerSheetProps) {\n // Every picker in the kit opens one of these on a phone, and most of them pass\n // their own `closeLabel` through as `undefined` — so this lookup is what makes the\n // one way out of a full-screen sheet speak the app's language without each of\n // them having to (see the prop's note in `EntityCombobox`).\n const labels = useKitLabels(\"pickerSheet\", DEFAULT_PICKER_SHEET_LABELS, { close: closeLabel, done: doneLabel });\n const hasFooter = (footer !== undefined && footer !== null && footer !== false) || Boolean(done);\n useBodyScrollLock(open);\n /**\n * Back closes THE SHEET, not the dialog it was opened from (Keksdose live #309:\n * *\"Mouse Back does not only close the select but also the whole edit or create\n * dialog which is cumbersome.\"*).\n *\n * The sheet is a full-screen overlay over whatever opened it, and on a phone that\n * is almost always the row editor — which DOES push a history entry\n * (`data-table.tsx`). So the sheet was invisible to Back: one press popped the\n * editor's entry and took the whole dialog with it, discarding an edit in progress\n * in order to dismiss a list.\n *\n * `useOverlayHistory` was built for exactly this nesting — its stack unwinds\n * last-in-first-out, and the U-12 regression test in its own suite is written\n * around \"a Modal and a PickerSheet inside it\". The sheet simply never called it.\n *\n * Nothing else has to change: the hook is a no-op while `open` is false, and it\n * pops its own entry when the sheet closes some other way (the X, a row, the\n * backdrop) so history does not accumulate husks.\n */\n useOverlayHistory(open, onClose);\n // The visible region, while the sheet is up — see the `style` below for why a\n // full-screen sheet cannot simply be `inset-0` on a phone (live #328).\n const vv = useVisualViewport(open);\n // A fallback ref so a caller that does not need the handle still gets the opening\n // focus, rather than the behaviour depending on whether a prop was passed. What that\n // focus is FOR is on the trap below.\n const ownRef = useRef<HTMLInputElement | null>(null);\n const searchRef = inputRef ?? ownRef;\n\n const sheetRef = useRef<HTMLDivElement>(null);\n const setSheet = useMemo(() => mergeRefs(sheetRef, ref), [ref]);\n /**\n * The sheet said `aria-modal=\"true\"` and then left focus on the field behind it, so\n * a screen reader was told to hide the page the user's cursor was still standing in\n * (the audit's *\"Only Modal manages focus\"*). Through the shared hook rather than\n * the bare `.focus()` that used to live here: the hook re-reads the tabbable list on\n * every Tab, which this panel needs more than most — its list is filtered as you\n * type, so the rows a trap captured at open time are gone by the second keystroke.\n *\n * **`initialFocus` is the search box**, not the container, and this is the one place\n * in the package that overrides `Modal`'s rule. The sheet exists in order to be typed\n * into (live #212: *\"make the text input filter active on select click\"*), and the\n * callers cannot do it themselves — `Combobox`'s field runs\n * `setOpen(true); sheetInputRef.current?.focus()` in a single `onFocus`, so the focus\n * call happens while this sheet is still unmounted and the ref is null. Only the\n * sheet knows when its own input exists. Popping the keyboard is the POINT here,\n * where in a form dialog it is the cost.\n *\n * **`restoreFocus` is off**, which is the uncomfortable half. Every caller in this\n * kit opens the sheet from the field's own `onFocus` handler, so handing focus back\n * to that field on close re-opens the sheet the user has just dismissed — a row tap\n * would select a value and immediately put the list back over it. The field that\n * opened it is the only thing that knows whether re-focusing it means \"open\"; a sheet\n * that cannot tell must not guess. The cost is that focus lands on `<body>` after a\n * dismissal, which on the phone this shape is for costs nothing, and on a pointer\n * device is what happened before this change too.\n */\n useFocusTrap(sheetRef, {\n active: open,\n restoreFocus: false,\n initialFocus: () => searchRef.current,\n });\n\n // The sheet is almost always the INNERMOST overlay — it opens from a field inside a\n // `Modal` or a `FullBleedDialog`. It is a layer on the Escape stack (Wave 2, DUP-1),\n // above the dialog, and takes the key on its own element: one press dismissing this\n // list and the dialog underneath with it was Keksdose live #309 (*\"Mouse Back does\n // not only close the select but also the whole edit or create dialog which is\n // cumbersome\"*), which the Back gesture already learned, on the key a desktop user\n // reaches for instead.\n useDismissLayer(open, onClose);\n const handleKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {\n takeEscape(e);\n };\n\n if (!open || typeof document === \"undefined\") return null;\n return createPortal(\n /* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions -- the dialog\n container, per the APG dialog pattern: `onKeyDown` is Escape-to-close for whatever\n inside it holds focus, and `onMouseDown` only stops propagation (live #328). */\n <div\n // Spread FIRST, and every attribute below it deliberately wins: `style`,\n // `onMouseDown` and `onKeyDown` here are not styling choices, they are live\n // #328, dev#477 and live #309 respectively, and a caller who passed one by\n // accident would re-open a bug three apps have already paid for. `style` is\n // MERGED rather than won outright (see below), which is the one case where a\n // caller has something to add.\n {...rest}\n ref={setSheet}\n // A kit layer and a bottom layer a docked tour card stays clear of (0.50,\n // docs/tours-harmonization.md §11.6).\n data-kit-layer=\"\"\n data-tour-obstruction=\"\"\n role=\"dialog\"\n aria-modal=\"true\"\n // Without it the container cannot take focus, and `useFocusTrap` has nothing to\n // fall back to when the list is empty and the search box is the only thing in\n // here — see the hook's \"nothing tabbable\" branch.\n tabIndex={-1}\n onKeyDown={handleKeyDown}\n // The caller's own name wins over the heading: a sheet titled \"Account\" in a\n // form with two of them needs to say WHICH, and `title` is what the user reads\n // rather than what a reader announces.\n aria-label={ariaLabel ?? (typeof title === \"string\" ? title : undefined)}\n // The sheet is PORTALLED to <body>, so it is not inside the field's own\n // wrapper — and `useDropdown` closes on any document mousedown landing\n // outside that wrapper. Tapping a row therefore unmounted the row (open →\n // false) before its CLICK could fire, so the tap selected nothing at all and\n // the sheet just vanished (Keksdose dev#477).\n //\n // It bit whichever sheet commits on `onClick` — the payee field's, since live\n // #200, where it hid behind the free-text value the search box was already\n // setting; and the account field's the moment dev#477 moved it onto the inline\n // picker. `ComboboxPanel`'s rows happen to commit on `onMouseDown`, so they\n // beat the document listener and were never affected — which is luck, not a\n // design, and exactly why the fix belongs to the sheet.\n //\n // Stopping the mousedown here rather than teaching every caller about the\n // portal: the sheet is modal and full-screen, so \"a click in here is not an\n // outside click\" is a property of the sheet, not of whoever opened it.\n onMouseDown={(e) => e.stopPropagation()}\n // ⚠️ `inset-0` was the whole of Keksdose live #328: *\"When the keyboard overlays\n // the entries I cannot scroll past them … to see also the last entries. Happens\n // when browsing the account select.\"*\n //\n // This sheet FOCUSES ITS OWN SEARCH BOX on open (see above), so the keyboard is\n // up every single time it is used on a phone. On Android that shrinks the visual\n // viewport and leaves the LAYOUT viewport alone — `inset-0` is the layout\n // viewport — so the sheet kept its full height with its bottom third behind the\n // keyboard. The list below is `flex-1 overflow-y-auto`, so it sized itself to\n // that hidden height too: it scrolled to ITS end while the last rows were still\n // under the keys, and no gesture could bring them out. Nothing was clipped and\n // nothing looked broken, which is why it reads as \"I cannot scroll past them\".\n //\n // `visualViewport` is the API that answers rather than hints (the live #292\n // lesson): it reports the region actually on screen, keyboard and pinch-pan\n // included, and it fires on the keyboard opening and closing. Pinned to that box,\n // the sheet ends where the keys begin and the list's own scroll covers the rest.\n // `useVisualViewport` returns null when there is nothing to correct, and then\n // this is character-for-character the sheet that was here before.\n // A caller's `style` is kept and the correction laid OVER it: the three values\n // below are the whole of live #328, so they are not negotiable, but a z-index\n // or a transition from outside has nothing to do with them. Without a visual\n // viewport to correct for this is character-for-character what the caller passed.\n style={vv ? { ...style, top: vv.top, height: vv.height, bottom: \"auto\" } : style}\n className={cn(\"fixed inset-0 z-50 flex flex-col bg-surface outline-none\", className)}\n >\n <div className=\"flex items-center gap-2 border-b border-subtle px-3 py-2\">\n <span className=\"min-w-0 flex-1 truncate text-sm font-semibold text-primary\">{title}</span>\n <button\n type=\"button\"\n aria-label={labels.close}\n onClick={onClose}\n className=\"rounded p-1.5 text-muted hover:bg-hover\"\n >\n <X className=\"size-5\" />\n </button>\n </div>\n <DropdownSearchHeader\n query={query}\n onQueryChange={onQueryChange}\n inputRef={searchRef}\n placeholder={searchPlaceholder}\n aria-label={searchLabel ?? searchPlaceholder}\n // Focus is in this box while the sheet is up, so this is where the list's keys\n // land and what has to name the list and the row the arrows are on (BUG-5).\n onKeyDown={onSearchKeyDown}\n listboxId={listboxId}\n activeId={activeId}\n />\n {/* min-h-0 so the LIST scrolls rather than the dialog growing past the\n viewport — a flex child defaults to min-height:auto. */}\n {/* Marked as the scroller it is (`CLIPS_ATTRIBUTE`), so a Tooltip in a row —\n `Combobox`'s `optionAdornment` — portals out under jsdom as it does in the\n browser rather than joining the option's name (keksdose E8). */}\n <div {...{ [CLIPS_ATTRIBUTE]: \"\" }} className=\"min-h-0 flex-1 overflow-y-auto overscroll-contain\">\n {children}\n </div>\n {hasFooter && (\n <div\n data-picker-sheet-footer=\"\"\n className=\"flex shrink-0 flex-wrap items-center justify-end gap-2 border-t border-subtle bg-surface px-3 pt-3\"\n style={{ paddingBottom: \"max(0.75rem, env(safe-area-inset-bottom))\" }}\n >\n {footer}\n {done && (\n // Full width: the one thing left to do on the sheet, under the thumb.\n <Button type=\"button\" className=\"flex-1\" onClick={onClose}>\n {labels.done}\n </Button>\n )}\n </div>\n )}\n </div>,\n document.body,\n );\n}\n\n/** A row inside a {@link PickerSheet} — the phone-sized version of a dropdown row,\n * so a list is comfortable to hit with a thumb rather than merely legible. */\nexport const SHEET_ROW_CLASS = cn(\n \"flex min-h-11 w-full items-center gap-2 px-4 py-2 text-start text-base\",\n \"text-primary hover:bg-hover\",\n);\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/components/picker-sheet.tsx"],"sourcesContent":["import { useMemo, useRef } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { X } from \"lucide-react\";\nimport type { ComponentPropsWithoutRef, KeyboardEvent, ReactNode, Ref, RefObject } from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { hasContent } from \"../lib/presence\";\nimport { mergeRefs } from \"./field-parts\";\nimport { CLIPS_ATTRIBUTE } from \"../lib/clipping\";\nimport { DropdownSearchHeader } from \"./dropdown\";\nimport { Button } from \"./button\";\nimport { useBodyScrollLock } from \"../hooks/use-body-scroll-lock\";\nimport { useOverlayHistory } from \"../hooks/use-overlay-history\";\nimport { useFocusTrap } from \"../hooks/use-focus-trap\";\nimport { takeEscape, useDismissLayer } from \"../hooks/use-dismiss\";\nimport { useVisualViewport } from \"../hooks/use-anchored-panel\";\nimport { DEFAULT_PICKER_SHEET_LABELS, useKitLabels } from \"../i18n/kit-labels\";\n\n/**\n * Two of the div's own attributes are omitted because this component already owns\n * the name: `title` here is the sheet's HEADING (and a ReactNode), not the browser's\n * tooltip string, and `onClose` is \"the sheet was dismissed\", not the DOM's\n * `<dialog>` close event. Everything else a caller can pass reaches the sheet.\n */\nexport interface PickerSheetProps extends Omit<ComponentPropsWithoutRef<\"div\">, \"title\" | \"onClose\"> {\n open: boolean;\n onClose: () => void;\n /** The field's own label — a sheet that fills the screen has to say what it is\n * asking for, which the anchored panel got for free by sitting under it. */\n title?: ReactNode;\n query: string;\n onQueryChange: (value: string) => void;\n searchPlaceholder?: string;\n /** The search box's accessible name. Default: `searchPlaceholder` — a placeholder is\n * gone the moment a character is typed, and is no name to many readers at all. */\n searchLabel?: string;\n inputRef?: RefObject<HTMLInputElement | null>;\n /** Default: `pickerSheet.close` from the {@link UiKitProvider}, else \"Close\". */\n closeLabel?: string;\n /**\n * The list's keyboard, on the search box — where focus is while the sheet is up:\n * Up/Down/Home/End/Enter (round 0.34 Wave 2, BUG-5). `ComboboxPanel` passes its own;\n * without it arrows and Enter in the sheet did nothing at all.\n */\n onSearchKeyDown?: (e: KeyboardEvent<HTMLInputElement>) => void;\n /** The `role=\"listbox\"` the search box filters, for its `aria-controls`. */\n listboxId?: string;\n /** The option the arrow keys are on, for the search box's `aria-activedescendant`. */\n activeId?: string;\n children: ReactNode;\n /**\n * A row pinned under the scrolling list — {@link FullBleedDialog}'s `footer`, on the\n * picker's sheet (kk run 79 gap 2). Outside the scroller, so it stays put however long\n * the list; padded for the home indicator, since it is the sheet's bottom edge.\n */\n footer?: ReactNode;\n /**\n * The kit's one sticky \"Done\" at the foot of the sheet (Marcel, keksdose #412): the\n * button a multi-select sheet is left by once its rows are ticked. Ticks apply as\n * they are made, so it closes (`onClose`) rather than saves. After any `footer`.\n * {@link ComboboxPanel} turns it on for every `multi` list — `MultiSelect` and\n * `MultiEntityCombobox` — and a single-select sheet, which closes on the tap that\n * picks, has no use for it. Label: `pickerSheet.done`, else \"Done\" (`doneLabel`).\n */\n done?: boolean;\n /** Default: `pickerSheet.done` from the {@link UiKitProvider}, else \"Done\". */\n doneLabel?: string;\n /** The sheet, where the rest of the props go (0.34, API-2, refactor audit 2026-10) —\n * merged with the sheet's own ref, which its focus trap holds. */\n ref?: Ref<HTMLDivElement>;\n}\n\n/**\n * The PHONE presentation of a picker: a full-screen dialog with a search box at\n * the top and the list filling everything below it (Keksdose live #200).\n *\n * *\"Paid as full screen dialog with input. Similar to the account select that\n * already appears as full screen.\"* The account field is a native `<select>`, and\n * a phone browser renders that as a full-screen list — so the app's own pickers,\n * which are anchored dropdown panels, were the odd ones out: a 320px-tall panel\n * squeezed between the field and the keyboard, showing three or four rows of a\n * list that might have two hundred entries.\n *\n * What this fixes beyond size: the panel had to be *placed* (above/below, tracking\n * the visual viewport as the keyboard opened — see `useAnchoredPanel`), and every\n * one of those decisions is a chance to be wrong on a screen this small. A sheet\n * has no placement. It also gives the search input somewhere unambiguous to live,\n * which is the other half of the request: *\"add filler possibility to the account\n * select or even better right to the hoc the account select is derived from\"* —\n * so this lives in the shared picker, not in one form's copy of it.\n *\n * Rows are `min-h-11` (44px): the touch target the rest of the app uses. The list\n * scrolls, the header does not, and the body behind it is scroll-locked so a drag\n * that overshoots the list does not move the page underneath.\n */\nexport function PickerSheet({\n open,\n onClose,\n title,\n query,\n onQueryChange,\n searchPlaceholder,\n searchLabel,\n inputRef,\n closeLabel,\n onSearchKeyDown,\n listboxId,\n activeId,\n children,\n footer,\n done,\n doneLabel,\n \"aria-label\": ariaLabel,\n className,\n style,\n ref,\n ...rest\n}: PickerSheetProps) {\n // Every picker in the kit opens one of these on a phone, and most of them pass\n // their own `closeLabel` through as `undefined` — so this lookup is what makes the\n // one way out of a full-screen sheet speak the app's language without each of\n // them having to (see the prop's note in `EntityCombobox`).\n const labels = useKitLabels(\"pickerSheet\", DEFAULT_PICKER_SHEET_LABELS, { close: closeLabel, done: doneLabel });\n // `hasContent`, so a `footer` of \"\" draws no empty bar (refactor audit 2026-10-30, DUP-14).\n const hasFooter = hasContent(footer) || Boolean(done);\n useBodyScrollLock(open);\n /**\n * Back closes THE SHEET, not the dialog it was opened from (Keksdose live #309:\n * *\"Mouse Back does not only close the select but also the whole edit or create\n * dialog which is cumbersome.\"*).\n *\n * The sheet is a full-screen overlay over whatever opened it, and on a phone that\n * is almost always the row editor — which DOES push a history entry\n * (`data-table.tsx`). So the sheet was invisible to Back: one press popped the\n * editor's entry and took the whole dialog with it, discarding an edit in progress\n * in order to dismiss a list.\n *\n * `useOverlayHistory` was built for exactly this nesting — its stack unwinds\n * last-in-first-out, and the U-12 regression test in its own suite is written\n * around \"a Modal and a PickerSheet inside it\". The sheet simply never called it.\n *\n * Nothing else has to change: the hook is a no-op while `open` is false, and it\n * pops its own entry when the sheet closes some other way (the X, a row, the\n * backdrop) so history does not accumulate husks.\n */\n useOverlayHistory(open, onClose);\n // The visible region, while the sheet is up — see the `style` below for why a\n // full-screen sheet cannot simply be `inset-0` on a phone (live #328).\n const vv = useVisualViewport(open);\n // A fallback ref so a caller that does not need the handle still gets the opening\n // focus, rather than the behaviour depending on whether a prop was passed. What that\n // focus is FOR is on the trap below.\n const ownRef = useRef<HTMLInputElement | null>(null);\n const searchRef = inputRef ?? ownRef;\n\n const sheetRef = useRef<HTMLDivElement>(null);\n const setSheet = useMemo(() => mergeRefs(sheetRef, ref), [ref]);\n /**\n * The sheet said `aria-modal=\"true\"` and then left focus on the field behind it, so\n * a screen reader was told to hide the page the user's cursor was still standing in\n * (the audit's *\"Only Modal manages focus\"*). Through the shared hook rather than\n * the bare `.focus()` that used to live here: the hook re-reads the tabbable list on\n * every Tab, which this panel needs more than most — its list is filtered as you\n * type, so the rows a trap captured at open time are gone by the second keystroke.\n *\n * **`initialFocus` is the search box**, not the container, and this is the one place\n * in the package that overrides `Modal`'s rule. The sheet exists in order to be typed\n * into (live #212: *\"make the text input filter active on select click\"*), and the\n * callers cannot do it themselves — `Combobox`'s field runs\n * `setOpen(true); sheetInputRef.current?.focus()` in a single `onFocus`, so the focus\n * call happens while this sheet is still unmounted and the ref is null. Only the\n * sheet knows when its own input exists. Popping the keyboard is the POINT here,\n * where in a form dialog it is the cost.\n *\n * **`restoreFocus` is off**, which is the uncomfortable half. `Combobox` and\n * `InlineEntityCombobox` open the sheet from the field's own `onFocus` handler, so\n * handing focus back to that field on close re-opens the sheet the user has just\n * dismissed — a row tap would select a value and immediately put the list back over\n * it. The field that opened it is the only thing that knows whether re-focusing it\n * means \"open\"; a sheet that cannot tell must not guess. So the caller does it in\n * `onClose` where it can: `ComboboxPanel`'s triggers open on a click, and its\n * `onClose` hands focus back to the trigger (0.52, refactor audit 2026-10-30, INP-7) —\n * a screen-reader user is in exactly this layout, and <body> was where they landed.\n */\n useFocusTrap(sheetRef, {\n active: open,\n restoreFocus: false,\n initialFocus: () => searchRef.current,\n });\n\n // The sheet is almost always the INNERMOST overlay — it opens from a field inside a\n // `Modal` or a `FullBleedDialog`. It is a layer on the Escape stack (Wave 2, DUP-1),\n // above the dialog, and takes the key on its own element: one press dismissing this\n // list and the dialog underneath with it was Keksdose live #309 (*\"Mouse Back does\n // not only close the select but also the whole edit or create dialog which is\n // cumbersome\"*), which the Back gesture already learned, on the key a desktop user\n // reaches for instead.\n useDismissLayer(open, onClose);\n const handleKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {\n takeEscape(e);\n };\n\n if (!open || typeof document === \"undefined\") return null;\n return createPortal(\n /* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions -- the dialog\n container, per the APG dialog pattern: `onKeyDown` is Escape-to-close for whatever\n inside it holds focus, and `onMouseDown` only stops propagation (live #328). */\n <div\n // Spread FIRST, and every attribute below it deliberately wins: `style`,\n // `onMouseDown` and `onKeyDown` here are not styling choices, they are live\n // #328, dev#477 and live #309 respectively, and a caller who passed one by\n // accident would re-open a bug three apps have already paid for. `style` is\n // MERGED rather than won outright (see below), which is the one case where a\n // caller has something to add.\n {...rest}\n ref={setSheet}\n // A kit layer and a bottom layer a docked tour card stays clear of (0.50,\n // docs/tours-harmonization.md §11.6).\n data-kit-layer=\"\"\n data-tour-obstruction=\"\"\n role=\"dialog\"\n aria-modal=\"true\"\n // Without it the container cannot take focus, and `useFocusTrap` has nothing to\n // fall back to when the list is empty and the search box is the only thing in\n // here — see the hook's \"nothing tabbable\" branch.\n tabIndex={-1}\n onKeyDown={handleKeyDown}\n // The caller's own name wins over the heading: a sheet titled \"Account\" in a\n // form with two of them needs to say WHICH, and `title` is what the user reads\n // rather than what a reader announces.\n aria-label={ariaLabel ?? (typeof title === \"string\" ? title : undefined)}\n // The sheet is PORTALLED to <body>, so it is not inside the field's own\n // wrapper — and `useDropdown` closes on any document mousedown landing\n // outside that wrapper. Tapping a row therefore unmounted the row (open →\n // false) before its CLICK could fire, so the tap selected nothing at all and\n // the sheet just vanished (Keksdose dev#477).\n //\n // It bit whichever sheet commits on `onClick` — the payee field's, since live\n // #200, where it hid behind the free-text value the search box was already\n // setting; and the account field's the moment dev#477 moved it onto the inline\n // picker. `ComboboxPanel`'s rows happen to commit on `onMouseDown`, so they\n // beat the document listener and were never affected — which is luck, not a\n // design, and exactly why the fix belongs to the sheet.\n //\n // Stopping the mousedown here rather than teaching every caller about the\n // portal: the sheet is modal and full-screen, so \"a click in here is not an\n // outside click\" is a property of the sheet, not of whoever opened it.\n onMouseDown={(e) => e.stopPropagation()}\n // ⚠️ `inset-0` was the whole of Keksdose live #328: *\"When the keyboard overlays\n // the entries I cannot scroll past them … to see also the last entries. Happens\n // when browsing the account select.\"*\n //\n // This sheet FOCUSES ITS OWN SEARCH BOX on open (see above), so the keyboard is\n // up every single time it is used on a phone. On Android that shrinks the visual\n // viewport and leaves the LAYOUT viewport alone — `inset-0` is the layout\n // viewport — so the sheet kept its full height with its bottom third behind the\n // keyboard. The list below is `flex-1 overflow-y-auto`, so it sized itself to\n // that hidden height too: it scrolled to ITS end while the last rows were still\n // under the keys, and no gesture could bring them out. Nothing was clipped and\n // nothing looked broken, which is why it reads as \"I cannot scroll past them\".\n //\n // `visualViewport` is the API that answers rather than hints (the live #292\n // lesson): it reports the region actually on screen, keyboard and pinch-pan\n // included, and it fires on the keyboard opening and closing. Pinned to that box,\n // the sheet ends where the keys begin and the list's own scroll covers the rest.\n // `useVisualViewport` returns null when there is nothing to correct, and then\n // this is character-for-character the sheet that was here before.\n // A caller's `style` is kept and the correction laid OVER it: the three values\n // below are the whole of live #328, so they are not negotiable, but a z-index\n // or a transition from outside has nothing to do with them. Without a visual\n // viewport to correct for this is character-for-character what the caller passed.\n style={vv ? { ...style, top: vv.top, height: vv.height, bottom: \"auto\" } : style}\n className={cn(\"fixed inset-0 z-50 flex flex-col bg-surface outline-none\", className)}\n >\n <div className=\"flex items-center gap-2 border-b border-subtle px-3 py-2\">\n <span className=\"min-w-0 flex-1 truncate text-sm font-semibold text-primary\">{title}</span>\n <button\n type=\"button\"\n aria-label={labels.close}\n onClick={onClose}\n className=\"rounded p-1.5 text-muted hover:bg-hover\"\n >\n <X className=\"size-5\" />\n </button>\n </div>\n <DropdownSearchHeader\n query={query}\n onQueryChange={onQueryChange}\n inputRef={searchRef}\n placeholder={searchPlaceholder}\n aria-label={searchLabel ?? searchPlaceholder}\n // Focus is in this box while the sheet is up, so this is where the list's keys\n // land and what has to name the list and the row the arrows are on (BUG-5).\n onKeyDown={onSearchKeyDown}\n listboxId={listboxId}\n activeId={activeId}\n />\n {/* min-h-0 so the LIST scrolls rather than the dialog growing past the\n viewport — a flex child defaults to min-height:auto. */}\n {/* Marked as the scroller it is (`CLIPS_ATTRIBUTE`), so a Tooltip in a row —\n `Combobox`'s `optionAdornment` — portals out under jsdom as it does in the\n browser rather than joining the option's name (keksdose E8). */}\n <div {...{ [CLIPS_ATTRIBUTE]: \"\" }} className=\"min-h-0 flex-1 overflow-y-auto overscroll-contain\">\n {children}\n </div>\n {hasFooter && (\n <div\n data-picker-sheet-footer=\"\"\n className=\"flex shrink-0 flex-wrap items-center justify-end gap-2 border-t border-subtle bg-surface px-3 pt-3\"\n style={{ paddingBottom: \"max(0.75rem, env(safe-area-inset-bottom))\" }}\n >\n {footer}\n {done && (\n // Full width: the one thing left to do on the sheet, under the thumb.\n <Button type=\"button\" className=\"flex-1\" onClick={onClose}>\n {labels.done}\n </Button>\n )}\n </div>\n )}\n </div>,\n document.body,\n );\n}\n\n/** A row inside a {@link PickerSheet} — the phone-sized version of a dropdown row,\n * so a list is comfortable to hit with a thumb rather than merely legible. */\nexport const SHEET_ROW_CLASS = cn(\n \"flex min-h-11 w-full items-center gap-2 px-4 py-2 text-start text-base\",\n \"text-primary hover:bg-hover\",\n);\n"],"mappings":"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","names":[]}