@eifi1/ui-kit 0.32.2 → 0.33.1

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 (400) hide show
  1. package/README.md +17 -9
  2. package/dist/account/delete-account-setting.d.ts +11 -10
  3. package/dist/account/delete-account-setting.js +2 -1
  4. package/dist/account/delete-account-setting.js.map +1 -1
  5. package/dist/account/email-change-setting.d.ts +5 -0
  6. package/dist/account/email-change-setting.js +4 -3
  7. package/dist/account/email-change-setting.js.map +1 -1
  8. package/dist/account/sessions-setting.d.ts +4 -0
  9. package/dist/account/sessions-setting.js +3 -2
  10. package/dist/account/sessions-setting.js.map +1 -1
  11. package/dist/admin/admin-action-confirm.d.ts +11 -10
  12. package/dist/admin/admin-action-confirm.js.map +1 -1
  13. package/dist/admin/invitations-panel.d.ts +11 -10
  14. package/dist/admin/invitations-panel.js +4 -3
  15. package/dist/admin/invitations-panel.js.map +1 -1
  16. package/dist/admin/plan-change.d.ts +101 -0
  17. package/dist/admin/plan-change.js +210 -0
  18. package/dist/admin/plan-change.js.map +1 -0
  19. package/dist/admin/reviewer-scope-editor.d.ts +11 -10
  20. package/dist/admin/reviewer-scope-editor.js.map +1 -1
  21. package/dist/admin/role-select.d.ts +8 -7
  22. package/dist/admin/role-select.js.map +1 -1
  23. package/dist/admin/transfer-ownership-dialog.d.ts +11 -10
  24. package/dist/admin/user-roster.d.ts +8 -7
  25. package/dist/admin/user-roster.js +2 -2
  26. package/dist/admin/user-roster.js.map +1 -1
  27. package/dist/auth/auth-errors.d.ts +13 -2
  28. package/dist/auth/auth-errors.js +5 -1
  29. package/dist/auth/auth-errors.js.map +1 -1
  30. package/dist/auth/complete-name-dialog.d.ts +4 -1
  31. package/dist/auth/complete-name-dialog.js +2 -1
  32. package/dist/auth/complete-name-dialog.js.map +1 -1
  33. package/dist/auth/register-form.d.ts +11 -10
  34. package/dist/billing/billing-banner.d.ts +14 -11
  35. package/dist/billing/billing-banner.js.map +1 -1
  36. package/dist/billing/billing-labels.d.ts +11 -10
  37. package/dist/billing/billing-labels.js +10 -1
  38. package/dist/billing/billing-labels.js.map +1 -1
  39. package/dist/billing/billing-lock.d.ts +40 -12
  40. package/dist/billing/billing-lock.js +9 -3
  41. package/dist/billing/billing-lock.js.map +1 -1
  42. package/dist/billing/billing-overview.d.ts +155 -0
  43. package/dist/billing/billing-overview.js +2 -0
  44. package/dist/billing/billing-overview.js.map +1 -0
  45. package/dist/billing/billing-standing.d.ts +11 -10
  46. package/dist/billing/checkout-processing.d.ts +218 -0
  47. package/dist/billing/checkout-processing.js +198 -0
  48. package/dist/billing/checkout-processing.js.map +1 -0
  49. package/dist/billing/checkout-return.d.ts +51 -0
  50. package/dist/billing/checkout-return.js +43 -0
  51. package/dist/billing/checkout-return.js.map +1 -0
  52. package/dist/billing/pay-page.d.ts +215 -0
  53. package/dist/billing/pay-page.js +113 -0
  54. package/dist/billing/pay-page.js.map +1 -0
  55. package/dist/billing/plan-card.d.ts +11 -10
  56. package/dist/billing/plan-limit-notice.d.ts +11 -10
  57. package/dist/billing/plan-limit-toast.d.ts +144 -0
  58. package/dist/billing/plan-limit-toast.js +40 -0
  59. package/dist/billing/plan-limit-toast.js.map +1 -0
  60. package/dist/billing/subscription-actions.d.ts +39 -15
  61. package/dist/billing/subscription-actions.js +9 -2
  62. package/dist/billing/subscription-actions.js.map +1 -1
  63. package/dist/billing/subscription-status-chip.d.ts +10 -9
  64. package/dist/bin/eifi1-pay-page.mjs +166 -0
  65. package/dist/chart.d.ts +11 -10
  66. package/dist/components/account-chips.d.ts +6 -1
  67. package/dist/components/account-settings.d.ts +4 -1
  68. package/dist/components/account-settings.js +2 -1
  69. package/dist/components/account-settings.js.map +1 -1
  70. package/dist/components/amount-input.d.ts +11 -10
  71. package/dist/components/autocomplete.d.ts +2 -1
  72. package/dist/components/autocomplete.js +5 -3
  73. package/dist/components/autocomplete.js.map +1 -1
  74. package/dist/components/button-group.d.ts +11 -10
  75. package/dist/components/button-group.js +1 -1
  76. package/dist/components/button-group.js.map +1 -1
  77. package/dist/components/calculator.d.ts +11 -10
  78. package/dist/components/calculator.js +3 -3
  79. package/dist/components/calculator.js.map +1 -1
  80. package/dist/components/checkbox-group.d.ts +2 -1
  81. package/dist/components/checkbox-group.js.map +1 -1
  82. package/dist/components/checkbox.d.ts +2 -1
  83. package/dist/components/checkbox.js.map +1 -1
  84. package/dist/components/chip.d.ts +2 -1
  85. package/dist/components/chip.js +19 -17
  86. package/dist/components/chip.js.map +1 -1
  87. package/dist/components/choice-card.d.ts +3 -1
  88. package/dist/components/choice-card.js +1 -1
  89. package/dist/components/choice-card.js.map +1 -1
  90. package/dist/components/combobox-core.js +2 -1
  91. package/dist/components/combobox-core.js.map +1 -1
  92. package/dist/components/combobox.d.ts +4 -3
  93. package/dist/components/combobox.js +2 -2
  94. package/dist/components/combobox.js.map +1 -1
  95. package/dist/components/confirm-dialog.d.ts +11 -10
  96. package/dist/components/confirm-dialog.js +6 -3
  97. package/dist/components/confirm-dialog.js.map +1 -1
  98. package/dist/components/copy-button.d.ts +12 -11
  99. package/dist/components/country-select.d.ts +2 -1
  100. package/dist/components/country-select.js.map +1 -1
  101. package/dist/components/danger-confirm.d.ts +11 -10
  102. package/dist/components/danger-confirm.js.map +1 -1
  103. package/dist/components/data-table-cells.d.ts +12 -1
  104. package/dist/components/data-table-filter-popover.d.ts +12 -1
  105. package/dist/components/data-table-filters.d.ts +12 -1
  106. package/dist/components/data-table-labels.d.ts +12 -1
  107. package/dist/components/data-table-pagination.d.ts +12 -1
  108. package/dist/components/data-table.d.ts +12 -1
  109. package/dist/components/data-table.js +3 -3
  110. package/dist/components/data-table.js.map +1 -1
  111. package/dist/components/date-picker.js +6 -4
  112. package/dist/components/date-picker.js.map +1 -1
  113. package/dist/components/dropdown.js +9 -1
  114. package/dist/components/dropdown.js.map +1 -1
  115. package/dist/components/entity-combobox.d.ts +3 -2
  116. package/dist/components/entity-combobox.js.map +1 -1
  117. package/dist/components/facing-pair.d.ts +11 -10
  118. package/dist/components/field-parts.d.ts +32 -4
  119. package/dist/components/field-parts.js +54 -19
  120. package/dist/components/field-parts.js.map +1 -1
  121. package/dist/components/file-button.d.ts +11 -10
  122. package/dist/components/file-button.js +3 -2
  123. package/dist/components/file-button.js.map +1 -1
  124. package/dist/components/file-dropzone.d.ts +12 -11
  125. package/dist/components/file-dropzone.js.map +1 -1
  126. package/dist/components/floating-panel.js +5 -5
  127. package/dist/components/floating-panel.js.map +1 -1
  128. package/dist/components/form-actions.d.ts +10 -9
  129. package/dist/components/form-actions.js +3 -3
  130. package/dist/components/form-actions.js.map +1 -1
  131. package/dist/components/full-bleed-dialog.js.map +1 -1
  132. package/dist/components/iban-input.d.ts +11 -10
  133. package/dist/components/inline-edit-field.d.ts +2 -1
  134. package/dist/components/inline-edit-field.js +6 -6
  135. package/dist/components/inline-edit-field.js.map +1 -1
  136. package/dist/components/language-select.d.ts +11 -10
  137. package/dist/components/legal-page.d.ts +11 -10
  138. package/dist/components/legal.d.ts +11 -10
  139. package/dist/components/list.d.ts +2 -1
  140. package/dist/components/list.js +4 -5
  141. package/dist/components/list.js.map +1 -1
  142. package/dist/components/modal.js.map +1 -1
  143. package/dist/components/money-field.d.ts +11 -10
  144. package/dist/components/month-picker.js +54 -44
  145. package/dist/components/month-picker.js.map +1 -1
  146. package/dist/components/multi-entity-combobox.d.ts +3 -2
  147. package/dist/components/multi-entity-combobox.js.map +1 -1
  148. package/dist/components/number-field.d.ts +18 -10
  149. package/dist/components/number-field.js.map +1 -1
  150. package/dist/components/number-input.d.ts +37 -10
  151. package/dist/components/number-input.js +27 -12
  152. package/dist/components/number-input.js.map +1 -1
  153. package/dist/components/numpad-sheet.d.ts +11 -10
  154. package/dist/components/numpad-sheet.js +2 -2
  155. package/dist/components/numpad-sheet.js.map +1 -1
  156. package/dist/components/phone-input.d.ts +11 -10
  157. package/dist/components/popover.js +6 -2
  158. package/dist/components/popover.js.map +1 -1
  159. package/dist/components/row-actions.d.ts +6 -5
  160. package/dist/components/row-actions.js +130 -32
  161. package/dist/components/row-actions.js.map +1 -1
  162. package/dist/components/series-chart.d.ts +11 -10
  163. package/dist/components/settings-fields.d.ts +11 -10
  164. package/dist/components/share-card.d.ts +8 -7
  165. package/dist/components/share-card.js +26 -10
  166. package/dist/components/share-card.js.map +1 -1
  167. package/dist/components/skeleton.d.ts +10 -2
  168. package/dist/components/skeleton.js.map +1 -1
  169. package/dist/components/status-dot.d.ts +1 -0
  170. package/dist/components/status-dot.js +2 -2
  171. package/dist/components/status-dot.js.map +1 -1
  172. package/dist/components/swatch-picker.d.ts +2 -1
  173. package/dist/components/swatch-picker.js.map +1 -1
  174. package/dist/components/swipeable-row.d.ts +88 -17
  175. package/dist/components/swipeable-row.js +46 -9
  176. package/dist/components/swipeable-row.js.map +1 -1
  177. package/dist/components/switch.d.ts +2 -1
  178. package/dist/components/switch.js.map +1 -1
  179. package/dist/components/table.d.ts +2 -1
  180. package/dist/components/table.js +6 -0
  181. package/dist/components/table.js.map +1 -1
  182. package/dist/components/text-link.d.ts +10 -9
  183. package/dist/components/time-input.d.ts +11 -10
  184. package/dist/components/toggle-group.d.ts +2 -1
  185. package/dist/components/toggle-group.js +1 -1
  186. package/dist/components/toggle-group.js.map +1 -1
  187. package/dist/components/translation-review-editor.d.ts +4 -0
  188. package/dist/components/translation-review-editor.js +4 -3
  189. package/dist/components/translation-review-editor.js.map +1 -1
  190. package/dist/components/translation-review-parts.d.ts +1 -0
  191. package/dist/components/translation-review.d.ts +1 -0
  192. package/dist/components/translation-review.js +15 -18
  193. package/dist/components/translation-review.js.map +1 -1
  194. package/dist/components/ui.d.ts +10 -9
  195. package/dist/components/ui.js +47 -27
  196. package/dist/components/ui.js.map +1 -1
  197. package/dist/components/use-table-state.d.ts +12 -1
  198. package/dist/components/user-avatar.d.ts +1 -0
  199. package/dist/components/write-lock.d.ts +101 -13
  200. package/dist/components/write-lock.js +37 -13
  201. package/dist/components/write-lock.js.map +1 -1
  202. package/dist/{data-table-labels-kpAAVi6g.d.ts → data-table-labels-BJ5JE_X2.d.ts} +17 -0
  203. package/dist/data-table.d.ts +12 -1
  204. package/dist/{delete-account-setting-Dy4kJgUm.d.ts → delete-account-setting-DAgGBrbn.d.ts} +393 -42
  205. package/dist/feedback/feedback-attachment.d.ts +11 -10
  206. package/dist/feedback/feedback-attachment.js +7 -7
  207. package/dist/feedback/feedback-attachment.js.map +1 -1
  208. package/dist/feedback/feedback-context.d.ts +11 -10
  209. package/dist/feedback/feedback-crash.d.ts +11 -10
  210. package/dist/feedback/feedback-dialog.d.ts +11 -10
  211. package/dist/feedback/feedback-inbox.d.ts +10 -9
  212. package/dist/feedback/feedback-inbox.js +7 -3
  213. package/dist/feedback/feedback-inbox.js.map +1 -1
  214. package/dist/feedback/feedback-labels.d.ts +11 -10
  215. package/dist/feedback/feedback-menu.d.ts +4 -3
  216. package/dist/feedback/feedback-record.d.ts +11 -10
  217. package/dist/feedback/feedback-row-detail.d.ts +11 -10
  218. package/dist/feedback/feedback-row-detail.js.map +1 -1
  219. package/dist/feedback/feedback-status-undo.d.ts +11 -10
  220. package/dist/feedback/feedback-submit.d.ts +11 -10
  221. package/dist/feedback/feedback-swipe.d.ts +11 -10
  222. package/dist/feedback/feedback-swipe.js +3 -6
  223. package/dist/feedback/feedback-swipe.js.map +1 -1
  224. package/dist/feedback/feedback-table.d.ts +11 -10
  225. package/dist/feedback/feedback-table.js.map +1 -1
  226. package/dist/feedback/feedback-thread.d.ts +11 -10
  227. package/dist/feedback/feedback-thread.js +5 -1
  228. package/dist/feedback/feedback-thread.js.map +1 -1
  229. package/dist/feedback.d.ts +11 -10
  230. package/dist/hooks/use-anchored-panel.d.ts +37 -7
  231. package/dist/hooks/use-anchored-panel.js +6 -7
  232. package/dist/hooks/use-anchored-panel.js.map +1 -1
  233. package/dist/hooks/use-file-drop.d.ts +11 -10
  234. package/dist/hooks/use-focus-trap.d.ts +8 -0
  235. package/dist/hooks/use-focus-trap.js +17 -11
  236. package/dist/hooks/use-focus-trap.js.map +1 -1
  237. package/dist/i18n/defaults.d.ts +11 -10
  238. package/dist/i18n/defaults.js +2 -0
  239. package/dist/i18n/defaults.js.map +1 -1
  240. package/dist/i18n/german.d.ts +11 -10
  241. package/dist/i18n/german.js +37 -1
  242. package/dist/i18n/german.js.map +1 -1
  243. package/dist/i18n/kit-labels.d.ts +5 -4
  244. package/dist/i18n/kit-labels.js.map +1 -1
  245. package/dist/i18n/languages.d.ts +11 -10
  246. package/dist/i18n/locales/de-CH.d.ts +11 -10
  247. package/dist/i18n/locales/en.d.ts +11 -10
  248. package/dist/i18n/locales/es.d.ts +11 -10
  249. package/dist/i18n/locales/es.js +35 -1
  250. package/dist/i18n/locales/es.js.map +1 -1
  251. package/dist/i18n/locales/fr.d.ts +11 -10
  252. package/dist/i18n/locales/fr.js +37 -1
  253. package/dist/i18n/locales/fr.js.map +1 -1
  254. package/dist/i18n/locales/hu.d.ts +11 -10
  255. package/dist/i18n/locales/hu.js +38 -1
  256. package/dist/i18n/locales/hu.js.map +1 -1
  257. package/dist/i18n/locales/it.d.ts +11 -10
  258. package/dist/i18n/locales/it.js +35 -1
  259. package/dist/i18n/locales/it.js.map +1 -1
  260. package/dist/i18n/locales/zh.d.ts +11 -10
  261. package/dist/i18n/locales/zh.js +35 -1
  262. package/dist/i18n/locales/zh.js.map +1 -1
  263. package/dist/i18n/plurals.d.ts +20 -1
  264. package/dist/i18n/plurals.js +11 -0
  265. package/dist/i18n/plurals.js.map +1 -1
  266. package/dist/i18n/review.d.ts +11 -10
  267. package/dist/i18n/review.js +8 -1
  268. package/dist/i18n/review.js.map +1 -1
  269. package/dist/index.d.ts +11 -6
  270. package/dist/index.js +40 -2
  271. package/dist/index.js.map +1 -1
  272. package/dist/landing/landing-actions.d.ts +11 -10
  273. package/dist/landing/landing-sections.d.ts +11 -10
  274. package/dist/landing/public-header.d.ts +11 -10
  275. package/dist/landing/routing.d.ts +11 -10
  276. package/dist/pay/.well-known/apple-developer-merchantid-domain-association +1 -0
  277. package/dist/pay/index.html +30 -0
  278. package/dist/pay/pay.css +126 -0
  279. package/dist/pay/pay.js +198 -0
  280. package/dist/rhf/fields.d.ts +11 -10
  281. package/dist/rhf/form.d.ts +11 -10
  282. package/dist/rhf.d.ts +11 -10
  283. package/dist/settings/settings-section.d.ts +11 -10
  284. package/dist/shell/app-shell.d.ts +10 -9
  285. package/dist/shell/top-bar-brand.d.ts +11 -10
  286. package/dist/shell/topbar-action-menu.d.ts +1 -0
  287. package/dist/shell.d.ts +11 -10
  288. package/dist/testing/css-variables.d.ts +74 -0
  289. package/dist/testing/css-variables.generated.d.ts +7 -0
  290. package/dist/testing/css-variables.generated.js +8 -0
  291. package/dist/testing/css-variables.generated.js.map +1 -0
  292. package/dist/testing/css-variables.js +93 -0
  293. package/dist/testing/css-variables.js.map +1 -0
  294. package/dist/testing/index.d.ts +1 -0
  295. package/dist/testing/index.js +12 -0
  296. package/dist/testing/index.js.map +1 -0
  297. package/dist/theme/contrast-tokens.d.ts +44 -1
  298. package/dist/theme/contrast-tokens.js +21 -0
  299. package/dist/theme/contrast-tokens.js.map +1 -1
  300. package/dist/theme/contrast.d.ts +2 -2
  301. package/dist/theme/palette-derive.js +60 -11
  302. package/dist/theme/palette-derive.js.map +1 -1
  303. package/dist/theme/palette-presets.js +45 -43
  304. package/dist/theme/palette-presets.js.map +1 -1
  305. package/dist/wizard/stepper-nav.d.ts +12 -11
  306. package/dist/wizard/stepper-nav.js.map +1 -1
  307. package/dist/wizard.d.ts +11 -10
  308. package/package.json +10 -2
  309. package/src/account/delete-account-setting.tsx +6 -1
  310. package/src/account/email-change-setting.tsx +9 -3
  311. package/src/account/sessions-setting.tsx +7 -2
  312. package/src/admin/admin-action-confirm.tsx +2 -1
  313. package/src/admin/invitations-panel.tsx +9 -3
  314. package/src/admin/plan-change.tsx +487 -0
  315. package/src/admin/reviewer-scope-editor.tsx +2 -1
  316. package/src/admin/role-select.tsx +2 -1
  317. package/src/admin/user-roster.tsx +5 -3
  318. package/src/auth/auth-errors.ts +25 -2
  319. package/src/auth/complete-name-dialog.tsx +6 -2
  320. package/src/billing/billing-banner.tsx +3 -1
  321. package/src/billing/billing-labels.ts +45 -0
  322. package/src/billing/billing-lock.ts +36 -3
  323. package/src/billing/billing-overview.ts +58 -0
  324. package/src/billing/checkout-processing.ts +391 -0
  325. package/src/billing/checkout-return.ts +90 -0
  326. package/src/billing/pay-page.ts +246 -0
  327. package/src/billing/plan-limit-toast.tsx +91 -0
  328. package/src/billing/subscription-actions.tsx +41 -8
  329. package/src/components/account-settings.tsx +6 -2
  330. package/src/components/autocomplete.tsx +10 -4
  331. package/src/components/button-group.tsx +4 -1
  332. package/src/components/calculator.tsx +10 -6
  333. package/src/components/checkbox-group.tsx +2 -2
  334. package/src/components/checkbox.tsx +2 -1
  335. package/src/components/chip.tsx +37 -32
  336. package/src/components/choice-card.tsx +4 -2
  337. package/src/components/combobox-core.tsx +5 -2
  338. package/src/components/combobox.tsx +8 -6
  339. package/src/components/confirm-dialog.tsx +13 -6
  340. package/src/components/country-select.tsx +2 -2
  341. package/src/components/danger-confirm.tsx +2 -2
  342. package/src/components/data-table.tsx +20 -9
  343. package/src/components/date-picker.tsx +10 -7
  344. package/src/components/dropdown.tsx +12 -2
  345. package/src/components/entity-combobox.tsx +3 -3
  346. package/src/components/field-parts.tsx +61 -14
  347. package/src/components/file-button.tsx +5 -4
  348. package/src/components/file-dropzone.tsx +2 -2
  349. package/src/components/floating-panel.tsx +7 -5
  350. package/src/components/form-actions.tsx +12 -5
  351. package/src/components/full-bleed-dialog.tsx +6 -0
  352. package/src/components/inline-edit-field.tsx +13 -13
  353. package/src/components/list.tsx +10 -9
  354. package/src/components/modal.tsx +5 -0
  355. package/src/components/month-picker.tsx +8 -2
  356. package/src/components/multi-entity-combobox.tsx +3 -3
  357. package/src/components/number-field.tsx +7 -0
  358. package/src/components/number-input.tsx +71 -11
  359. package/src/components/numpad-sheet.tsx +8 -4
  360. package/src/components/popover.tsx +6 -2
  361. package/src/components/row-actions.tsx +208 -58
  362. package/src/components/share-card.tsx +67 -13
  363. package/src/components/skeleton.tsx +10 -2
  364. package/src/components/status-dot.tsx +7 -7
  365. package/src/components/swatch-picker.tsx +2 -2
  366. package/src/components/swipeable-row.tsx +138 -27
  367. package/src/components/switch.tsx +2 -1
  368. package/src/components/table.tsx +8 -1
  369. package/src/components/toggle-group.tsx +5 -3
  370. package/src/components/translation-review-editor.tsx +8 -3
  371. package/src/components/translation-review.tsx +18 -22
  372. package/src/components/ui.tsx +92 -43
  373. package/src/components/write-lock.tsx +150 -24
  374. package/src/feedback/feedback-attachment.tsx +13 -9
  375. package/src/feedback/feedback-inbox.tsx +10 -6
  376. package/src/feedback/feedback-row-detail.tsx +3 -2
  377. package/src/feedback/feedback-swipe.tsx +6 -11
  378. package/src/feedback/feedback-table.tsx +2 -1
  379. package/src/feedback/feedback-thread.tsx +20 -1
  380. package/src/hooks/use-anchored-panel.ts +60 -14
  381. package/src/hooks/use-focus-trap.ts +55 -12
  382. package/src/i18n/defaults.ts +2 -0
  383. package/src/i18n/german.ts +41 -0
  384. package/src/i18n/kit-labels.tsx +4 -0
  385. package/src/i18n/locales/es.ts +35 -0
  386. package/src/i18n/locales/fr.ts +41 -0
  387. package/src/i18n/locales/hu.ts +39 -0
  388. package/src/i18n/locales/it.ts +36 -0
  389. package/src/i18n/locales/zh.ts +34 -0
  390. package/src/i18n/plurals.ts +29 -0
  391. package/src/i18n/review.ts +7 -0
  392. package/src/index.ts +51 -5
  393. package/src/testing/css-variables.generated.ts +112 -0
  394. package/src/testing/css-variables.ts +191 -0
  395. package/src/testing/index.ts +13 -0
  396. package/src/theme/contrast-tokens.ts +53 -2
  397. package/src/theme/palette-derive.ts +87 -10
  398. package/src/theme/palette-presets.ts +45 -43
  399. package/src/wizard/stepper-nav.tsx +2 -1
  400. package/tokens.css +324 -46
@@ -5,7 +5,7 @@ import { useAnnounce } from "../hooks/use-announce";
5
5
  import { useKitFileLabels, useKitLabels } from "../i18n/kit-labels";
6
6
  import { Button, Spinner } from "./ui";
7
7
  import { Tooltip } from "./tooltip";
8
- import { useCommitReason } from "./write-lock";
8
+ import { useCommitReason, type CommitScope } from "./write-lock";
9
9
 
10
10
  /**
11
11
  * A button that opens the file picker — the shape all three apps kept writing by hand
@@ -444,7 +444,7 @@ export interface FileButtonProps
444
444
  * one: no effect. A picker that only fills a form the user saves later is not a
445
445
  * commit, and is left live — the form's Save carries the lock.
446
446
  */
447
- commit?: boolean;
447
+ commit?: CommitScope;
448
448
  }
449
449
 
450
450
  /** A reason that is really there — `null`, `false` and `""` are no reason. */
@@ -612,7 +612,8 @@ export const FileButton = forwardRef<HTMLButtonElement, FileButtonProps>(functio
612
612
  }
613
613
  return (
614
614
  // One inline row, so the name sits beside the button wherever the button sits; the
615
- // name truncates rather than pushing the row wider, and the full list is in the
615
+ // name truncates rather than pushing the row wider (at Normal: from Large up it wraps,
616
+ // `truncate-until-large`, text size §10.17), and the full list is in the
616
617
  // kit's own Tooltip — not a native `title` (0.16.0 shipped one: the one-tooltip rule
617
618
  // since keksdose dev#523, and a title never opens on touch). Lazy, so the closed
618
619
  // bubble adds nothing to the read-out's text.
@@ -628,7 +629,7 @@ export const FileButton = forwardRef<HTMLButtonElement, FileButtonProps>(functio
628
629
  className="flex min-w-0 text-sm text-[var(--text-secondary)]"
629
630
  >
630
631
  <Tooltip label={picked.map((f) => f.name).join(", ")} lazy className="min-w-0">
631
- <span className="block truncate">{pickedText}</span>
632
+ <span className="block truncate-until-large">{pickedText}</span>
632
633
  </Tooltip>
633
634
  </span>
634
635
  {picker.element}
@@ -15,7 +15,7 @@ import {
15
15
  import type { FilePickHandler, FilePickerLabels, FileRejection } from "./file-button";
16
16
  import { useDragTarget } from "../hooks/use-file-drop";
17
17
  import { toast } from "./toast";
18
- import { useCommitReason } from "./write-lock";
18
+ import { useCommitReason, type CommitScope } from "./write-lock";
19
19
 
20
20
  /**
21
21
  * Where a refused file's message goes.
@@ -160,7 +160,7 @@ export interface FileDropzoneProps extends Omit<ComponentPropsWithoutRef<"div">,
160
160
  * reason (which wins over one of its own). No provider, or an unlocked one: no
161
161
  * effect. A zone that only stages a file for a form saved later is not a commit.
162
162
  */
163
- commit?: boolean;
163
+ commit?: CommitScope;
164
164
  /**
165
165
  * Replace the zone's body — the icon, the text and the chosen-file list — with your
166
166
  * own, given the zone's state: keksdose's spinner-plus-"uploading", its multi-shot
@@ -243,7 +243,7 @@ export function FloatingActionButton({
243
243
  pressed === false &&
244
244
  "bg-[var(--bg-surface)] text-[var(--text-secondary)] hover:bg-[var(--bg-hover)] hover:text-[var(--text-primary)]",
245
245
  pressed === true &&
246
- "border-[var(--brand)] bg-[var(--brand-bg)] text-[var(--brand)] hover:bg-[var(--brand-bg-hover)]",
246
+ "border-[var(--brand)] bg-[var(--brand-bg)] text-[var(--brand)] hover:bg-[var(--brand-bg-hover)] hover:text-brand-muted",
247
247
  "outline-none focus-visible:ring-offset-2",
248
248
  FOCUS_RING,
249
249
  className,
@@ -650,10 +650,12 @@ export interface FloatingActionProps extends Omit<ButtonHTMLAttributes<HTMLButto
650
650
  ref?: Ref<HTMLButtonElement>;
651
651
  }
652
652
 
653
+ // Each fill under its own foreground (0.33): warning borrowed `--text-inverse`, the
654
+ // surface colour, which was 4.24:1 on the light warning fill.
653
655
  const BADGE_TONE: Record<NonNullable<FloatingActionProps["badgeTone"]>, string> = {
654
- warning: "bg-[var(--warning)] text-[var(--text-inverse)]",
655
- danger: "bg-[var(--danger)] text-[var(--danger-contrast)]",
656
- brand: "bg-[var(--brand)] text-[var(--brand-contrast)]",
656
+ warning: "bg-warning text-warning-contrast",
657
+ danger: "bg-danger text-danger-contrast",
658
+ brand: "bg-brand text-brand-contrast",
657
659
  };
658
660
 
659
661
  /**
@@ -695,7 +697,7 @@ export function FloatingAction({
695
697
  variant === "primary"
696
698
  ? "bg-[var(--brand)] text-[var(--brand-contrast)] hover:bg-[var(--brand-hover)]"
697
699
  : pressed && !isLink && pressedStyle === "tint"
698
- ? "bg-[var(--brand-bg)] text-[var(--brand)] hover:bg-[var(--brand-bg-hover)]"
700
+ ? "bg-[var(--brand-bg)] text-[var(--brand)] hover:bg-[var(--brand-bg-hover)] hover:text-brand-muted"
699
701
  : "text-[var(--text-secondary)] hover:bg-[var(--bg-hover)] hover:text-[var(--text-primary)]",
700
702
  className,
701
703
  );
@@ -8,6 +8,7 @@ import { useLargeText } from "../hooks/use-large-text";
8
8
  import { isApplePlatform } from "../hooks/use-hotkey";
9
9
  import { Button, Spinner, type ButtonProps, type ButtonSize, type ButtonVariant } from "./ui";
10
10
  import { useWriteLock } from "./write-lock";
11
+ import type { CommitScope } from "./write-lock";
11
12
 
12
13
  /** The words {@link FormActions} renders on its own behalf — the `form` namespace. */
13
14
  export interface FormActionsLabels {
@@ -197,8 +198,11 @@ export interface FormActionsProps extends Omit<ComponentPropsWithoutRef<"div">,
197
198
  * keksdose wrote `submitDisabled={lock.locked || …}` and
198
199
  * `submitDisabledReason={lock.locked ? lock.reason : undefined}` at each form; this is
199
200
  * that pair, read from the provider.
201
+ *
202
+ * 0.33: a {@link CommitScope} — `COMMIT_EXCEPT_BILLING` for a form a lapsed plan still
203
+ * allows (the account's own settings, docs/billing-harmonization.md §12.36).
200
204
  */
201
- commit?: boolean;
205
+ commit?: CommitScope;
202
206
  /** The save button's variant. Default `brand`; `danger` for a save that destroys
203
207
  * (kastlan's `destructive` flag on its own FormActions). */
204
208
  submitVariant?: ButtonVariant;
@@ -392,7 +396,9 @@ export function FormActions({
392
396
  // column stretches but which keeps its control at the control's own width.
393
397
  const full = stacked ? "w-full" : undefined;
394
398
  const labels = useKitLabels("form", DEFAULT_FORM_ACTIONS_LABELS);
395
- const lock = useWriteLock();
399
+ // The lock as Save sees it: with its scope, so a billing lock leaves a
400
+ // `COMMIT_EXCEPT_BILLING` form's shortcut live too.
401
+ const lock = useWriteLock(commit);
396
402
  const rootRef = useRef<HTMLDivElement>(null);
397
403
  const saveRef = useRef<HTMLButtonElement | null>(null);
398
404
  // The caller's own `submitProps.ref` still gets the element.
@@ -422,7 +428,7 @@ export function FormActions({
422
428
  }, [pending]);
423
429
  // Read by the shortcut's listener at the moment of the key press, so the listener
424
430
  // itself is attached once and never sees a stale `pending`.
425
- const canSave = !pending && !submitDisabled && !(commit && lock.locked);
431
+ const canSave = !pending && !submitDisabled && !lock.locked;
426
432
  const canSaveRef = useRef(canSave);
427
433
  useEffect(() => {
428
434
  canSaveRef.current = canSave;
@@ -535,8 +541,9 @@ export function FormActions({
535
541
  disabled={pending || submitDisabled}
536
542
  disabledReason={pending ? undefined : submitDisabledReason}
537
543
  // Not while pending: the save already left, and the lock's look over the
538
- // spinner would say it had not.
539
- commit={commit && !pending}
544
+ // spinner would say it had not. A ternary, not `commit && !pending`, which
545
+ // turned a `{ except }` scope into `true` (§12.36).
546
+ commit={pending ? false : commit}
540
547
  aria-busy={pending || undefined}
541
548
  className={cn(submitProps?.className, full)}
542
549
  >
@@ -154,6 +154,12 @@ export function FullBleedDialog({
154
154
  // This is the phone's row EDITOR and its first child is usually an input; focusing it
155
155
  // would pull the software keyboard up over half the form before the user has said
156
156
  // which field they came for. `Modal` made the same call for the same reason.
157
+ //
158
+ // That default only decides when nothing inside asked for focus. A field the app
159
+ // autofocuses on purpose keeps it (0.33.1): keksdose's create card autofocuses its
160
+ // amount, and the trap used to take focus straight back to the dialog — and on a
161
+ // phone the numpad sheet, which shows only while the field is focused, went with it
162
+ // (live #402). Closing still hands focus back to whatever opened the dialog.
157
163
  useFocusTrap(dialogRef, { active: open });
158
164
 
159
165
  const handleKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {
@@ -6,7 +6,7 @@ import { useKitLabels } from "../i18n/kit-labels";
6
6
  import { Input, Spinner } from "./ui";
7
7
  import { FOCUS_RING } from "./focus-ring";
8
8
  import { Tooltip } from "./tooltip";
9
- import { useCommitReason } from "./write-lock";
9
+ import { useCommitReason, type CommitScope } from "./write-lock";
10
10
 
11
11
  /**
12
12
  * Every string {@link InlineEditField} says on its own behalf — the `inlineEdit`
@@ -101,7 +101,7 @@ interface InlineEditFieldBaseProps<T> {
101
101
  /** This field COMMITS — under a locked {@link WriteLockProvider} it takes the
102
102
  * `disabledReason` path with the lock's reason (which wins over its own). No
103
103
  * provider, or an unlocked one: no effect. */
104
- commit?: boolean;
104
+ commit?: CommitScope;
105
105
  /** The value is the user's own data: `data-private` on the display and the editor,
106
106
  * so the app's demo-mode blur covers both. */
107
107
  redact?: boolean;
@@ -164,13 +164,13 @@ function RenderEditor<T>({ render, ...props }: InlineEditorProps<T> & { render:
164
164
  * big type cannot recover a cut-off end by squinting. A `large:` class, so a table of
165
165
  * these keeps its one-line cells at Normal.
166
166
  *
167
- * `break-words`, not `[overflow-wrap:anywhere]`: these live in table cells (keksdose's
168
- * budget and invoice lines), and `anywhere` lowers the value's min-content width to one
169
- * letter, so an auto-sized column could squeeze an amount to a digit per line. With
170
- * `break-word` a column keeps each word whole; the button is `w-full`, so a word wider
171
- * than a sized cell still breaks.
167
+ * 0.33 (§10.17): the kit's one `truncate-until-large` (tokens.css) in place of this
168
+ * file's own `truncate` + `large:whitespace-normal large:break-words`. The utility
169
+ * breaks with `overflow-wrap: anywhere`, which lowers the value's min-content width: in
170
+ * an auto-sized table column (keksdose's budget and invoice lines) give the column a
171
+ * width, or an amount can be squeezed to a digit per line.
172
172
  */
173
- const VALUE_WRAP_AT_LARGE = "large:whitespace-normal large:break-words";
173
+ const VALUE_TRUNCATE = "truncate-until-large";
174
174
 
175
175
  /** `onCommit`'s result, if it is a promise. */
176
176
  function asPromise(result: unknown): Promise<unknown> | null {
@@ -432,7 +432,7 @@ export function InlineEditField<T = string>(props: InlineEditFieldProps<T>) {
432
432
  let body: ReactNode;
433
433
  if (readOnly) {
434
434
  body = (
435
- <span {...private_} className={cn("block min-w-0 truncate", VALUE_WRAP_AT_LARGE, alignClass)}>
435
+ <span {...private_} className={cn("block min-w-0", VALUE_TRUNCATE, alignClass)}>
436
436
  {content}
437
437
  </span>
438
438
  );
@@ -508,8 +508,8 @@ export function InlineEditField<T = string>(props: InlineEditFieldProps<T>) {
508
508
  aria-describedby={reasonId}
509
509
  onClick={(e) => e.preventDefault()}
510
510
  className={cn(
511
- "block w-full min-w-0 cursor-not-allowed truncate rounded-sm focus:outline-none",
512
- VALUE_WRAP_AT_LARGE,
511
+ "block w-full min-w-0 cursor-not-allowed rounded-sm focus:outline-none",
512
+ VALUE_TRUNCATE,
513
513
  FOCUS_RING,
514
514
  alignClass,
515
515
  )}
@@ -533,8 +533,8 @@ export function InlineEditField<T = string>(props: InlineEditFieldProps<T>) {
533
533
  {...private_}
534
534
  onClick={() => setOpen(true)}
535
535
  className={cn(
536
- "block w-full min-w-0 truncate rounded-sm underline-offset-2 hover:underline focus:outline-none",
537
- VALUE_WRAP_AT_LARGE,
536
+ "block w-full min-w-0 rounded-sm underline-offset-2 hover:underline focus:outline-none",
537
+ VALUE_TRUNCATE,
538
538
  FOCUS_RING,
539
539
  alignClass,
540
540
  )}
@@ -12,6 +12,7 @@ import { useKitLabels, useKitLink } from "../i18n/kit-labels";
12
12
  import { pickLinkRenderer } from "./text-link";
13
13
  import { mergeDescribedBy } from "./choice-parts";
14
14
  import { LockedReason, useLockReason } from "./field-parts";
15
+ import type { CommitScope } from "./write-lock";
15
16
 
16
17
  /* ── Labels ───────────────────────────────────────────────────────────────── */
17
18
 
@@ -122,11 +123,11 @@ const TARGET_RING = cn(FOCUS_RING, "focus-visible:ring-inset");
122
123
  * §4 "ListItem: title, subtitle and meta wrap instead of truncating" at Large and Extra
123
124
  * large (docs/text-size-harmonization.md). `large:` variants over the Normal look, so
124
125
  * a list keeps its one-height rows at Normal and costs no render to switch: a one-line
125
- * `truncate` wraps (and breaks a word too long for the row), a `line-clamp-2` lets go.
126
- * An ellipsis hides the end of a name, which is the part a reader who needs big type
127
- * cannot recover by squinting.
126
+ * line is `truncate-until-large` (tokens.css, 0.33 §10.17: `truncate`, and at Large a
127
+ * wrap that breaks a word too long for the row — the kit's one name for what this file
128
+ * spelt as `WRAP_AT_LARGE`), a `line-clamp-2` lets go. An ellipsis hides the end of a
129
+ * name, which is the part a reader who needs big type cannot recover by squinting.
128
130
  */
129
- const WRAP_AT_LARGE = "large:whitespace-normal large:[overflow-wrap:anywhere]";
130
131
  const UNCLAMP_AT_LARGE = "large:line-clamp-none";
131
132
 
132
133
  interface ListItemBaseProps {
@@ -341,7 +342,7 @@ export type ListItemProps = ListItemBaseProps &
341
342
  * the `disabledReason` way with the lock's reason (which wins over its own). No
342
343
  * provider, or an unlocked one: no effect. Button's `commit`, for keksdose K3.
343
344
  */
344
- commit?: boolean;
345
+ commit?: CommitScope;
345
346
  href?: never;
346
347
  renderLink?: never;
347
348
  external?: never;
@@ -528,7 +529,7 @@ export const ListItem = forwardRef<HTMLElement, ListItemProps>(function ListItem
528
529
  now-wrapping title into a column one word wide (§4, 240 px effective width). */}
529
530
  <span className={cn("flex min-w-0 flex-1 flex-col", trailing != null && "large:basis-40")}>
530
531
  {overline != null && (
531
- <span className={cn("block truncate text-caption font-medium leading-snug text-[var(--text-muted)]", WRAP_AT_LARGE)}>
532
+ <span className="block truncate-until-large text-caption font-medium leading-snug text-[var(--text-muted)]">
532
533
  {overline}
533
534
  </span>
534
535
  )}
@@ -538,7 +539,7 @@ export const ListItem = forwardRef<HTMLElement, ListItemProps>(function ListItem
538
539
  className={cn(
539
540
  "block text-sm text-[var(--text-primary)]",
540
541
  titleLines === 1
541
- ? cn("truncate", WRAP_AT_LARGE)
542
+ ? "truncate-until-large"
542
543
  : titleLines === 2
543
544
  ? cn("line-clamp-2 break-words", UNCLAMP_AT_LARGE)
544
545
  : "break-words",
@@ -553,7 +554,7 @@ export const ListItem = forwardRef<HTMLElement, ListItemProps>(function ListItem
553
554
  <span
554
555
  className={cn(
555
556
  "block text-xs text-[var(--text-muted)]",
556
- subtitleLines === 2 ? cn("line-clamp-2 break-words", UNCLAMP_AT_LARGE) : cn("truncate", WRAP_AT_LARGE),
557
+ subtitleLines === 2 ? cn("line-clamp-2 break-words", UNCLAMP_AT_LARGE) : "truncate-until-large",
557
558
  )}
558
559
  >
559
560
  {subtitle}
@@ -563,7 +564,7 @@ export const ListItem = forwardRef<HTMLElement, ListItemProps>(function ListItem
563
564
  <span
564
565
  className={cn(
565
566
  "mt-0.5 text-caption leading-snug text-[var(--text-muted)]",
566
- metaWrap ? "flex flex-wrap items-center gap-1" : cn("block truncate", WRAP_AT_LARGE),
567
+ metaWrap ? "flex flex-wrap items-center gap-1" : "block truncate-until-large",
567
568
  )}
568
569
  >
569
570
  {meta}
@@ -333,6 +333,11 @@ function ModalPanel({
333
333
  // hook adds per-keystroke recomputation, a guard on restoring to a detached node,
334
334
  // and nesting, none of which this copy had. Released when `open` turns false, which
335
335
  // hands focus back to the trigger while the panel is still on its way out.
336
+ //
337
+ // "The panel itself" only when nothing inside asked for focus: a field the app
338
+ // autofocuses on purpose keeps it (0.33.1, keksdose live #402 — the same trap took
339
+ // an autofocused amount straight back in `FullBleedDialog`). Its keyboard is then the
340
+ // one the app chose to show; the trigger still gets focus back on close.
336
341
  useFocusTrap(panelRef, { active, initialFocus: "container" });
337
342
  const leaving = closing || exiting;
338
343
 
@@ -10,7 +10,7 @@ import { Popover } from "./popover";
10
10
  import { splitTriggerAria } from "./trigger-aria";
11
11
  import type { TriggerAria } from "./trigger-aria";
12
12
  import { useKitDateFormatter, useKitLabels, useKitLocale } from "../i18n/kit-labels";
13
- import { FieldBox, FieldLabelLine, hasContent, useFieldMessages } from "./field-parts";
13
+ import { FieldBox, FieldLabelLine, hasContent, LABEL_ABOVE_BOX, useFieldMessages } from "./field-parts";
14
14
 
15
15
  /**
16
16
  * Every string the month picker can speak. The month and year names are NOT here —
@@ -1005,7 +1005,13 @@ export function MonthPicker(props: MonthPickerProps) {
1005
1005
  const field = (
1006
1006
  // `w-fit` for the toolbar size: the calendar glyph is placed against this box, and a
1007
1007
  // full-width box would park it at the far end of the row, away from its trigger.
1008
- <div {...wrapperRest} ref={rootRef} className={cn("relative", small && "w-fit max-w-full", className)}>
1008
+ // At Large a labelled box is a grid: the label above, the calendar glyph measured
1009
+ // against the trigger's row (LABEL_ABOVE_BOX, 0.33 §10.17).
1010
+ <div
1011
+ {...wrapperRest}
1012
+ ref={rootRef}
1013
+ className={cn("relative", small && "w-fit max-w-full", label !== undefined && LABEL_ABOVE_BOX, className)}
1014
+ >
1009
1015
  {label !== undefined && <FieldLabelLine label={label} hint={messages.labelHint} />}
1010
1016
  {/* The hidden twin the trigger is named by — see DateField. `sr-only-fixed`,
1011
1017
  and inside this `relative` root either way. */}
@@ -26,7 +26,7 @@ import {
26
26
  useLockReason,
27
27
  } from "./field-parts";
28
28
  import { assignRef, mergeDescribedBy } from "./choice-parts";
29
- import { useCommitReason } from "./write-lock";
29
+ import { useCommitReason, type CommitScope } from "./write-lock";
30
30
 
31
31
  /** `onChange` is the kit's — "a selection was made", carrying values — rather
32
32
  * than the div's form event, so the DOM's spelling of it is omitted. */
@@ -65,7 +65,7 @@ export interface MultiEntityComboboxProps<V extends string | number>
65
65
  disabledReason?: ReactNode;
66
66
  /** This picker COMMITS — toggling saves. Locked the `disabledReason` way under a
67
67
  * locked {@link WriteLockProvider}. See {@link EntityCombobox}'s `commit`. */
68
- commit?: boolean;
68
+ commit?: CommitScope;
69
69
  /** When set, a "create" row appears for a non-empty query with no exact match.
70
70
  * The panel stays open (adding the new entity to `value` is the caller's job). */
71
71
  onCreate?: (query: string) => void;
@@ -75,7 +75,7 @@ export interface MultiEntityComboboxProps<V extends string | number>
75
75
  createEmptyLabel?: string;
76
76
  /** The create row is a write while toggling is not: locked on its own under a locked
77
77
  * {@link WriteLockProvider}. See {@link EntityCombobox}'s `createCommit`. */
78
- createCommit?: boolean;
78
+ createCommit?: CommitScope;
79
79
  /** Required and unanswered — {@link FIELD_INVALID}. See {@link Input}'s `invalid`. */
80
80
  invalid?: boolean;
81
81
  /** What is wrong with the value, as {@link Input}'s `error`: rendered under the
@@ -141,6 +141,13 @@ function roundTo(n: number, digits: number | undefined): number {
141
141
  * or a dot typed as the decimal in a comma locale); it is re-spelt in the locale's mark
142
142
  * when it commits.
143
143
  *
144
+ * LOCKED (0.33.1, Kurvenschmiede). A field that saves itself takes `commit` (or a
145
+ * `disabledReason` of its own), NumberInput's: under a locked `WriteLockProvider` it
146
+ * stays in the tab order showing its value, `readOnly`, with the lock's reason in the
147
+ * kit Tooltip and its description; the step keys, the calculator and the numpad are
148
+ * off, and nothing reaches `onValueChange` or `onCommit`. `disabled` takes it out of
149
+ * the tab order, which is what a lock must not do.
150
+ *
144
151
  * NOT FOR MONEY. `digits={2}` rounds, but a currency is more than two decimals: JPY has
145
152
  * none, a unit price has four, and the chip, the minor-unit default and `min`/`max` on
146
153
  * the SIGNED figure are {@link MoneyField}'s (number value) and {@link AmountInput}'s
@@ -5,7 +5,8 @@ import { NumberPadSheet, type NumberPadSheetLabels } from "./numpad-sheet";
5
5
  import { FIELD_BASE, FIELD_DISPLAY, FIELD_INVALID, FLOATING_INPUT_CLASS, FloatingField } from "./ui";
6
6
  import { CURRENCIES } from "./currency-select";
7
7
  import { hasMessage, mergeDescribedBy } from "./choice-parts";
8
- import { useInlineHint } from "./field-parts";
8
+ import { LockedReason, useInlineHint, useLockReason } from "./field-parts";
9
+ import type { CommitScope } from "./write-lock";
9
10
  import { cn } from "../lib/cn";
10
11
  import { commitExpression, formatResult } from "../lib/calc";
11
12
  import { decimalMark, localizeMark, readTyped, showsTyped } from "../lib/decimal-marks";
@@ -135,6 +136,32 @@ interface NumberInputProps {
135
136
  * numeric field that clamps what is typed as well. */
136
137
  min?: number;
137
138
  max?: number;
139
+ /**
140
+ * Why the value cannot be changed — {@link Button}'s `disabledReason`, for a number
141
+ * that SAVES itself: a setting committed on blur, a figure in a row editor
142
+ * (Kurvenschmiede's session page, 0.33.1). `disabled` takes the field out of the tab
143
+ * order, so the reason never reached a keyboard, and Kurvenschmiede wrote its own
144
+ * `CommitInput` to keep it in.
145
+ *
146
+ * With a reason the field stays in the tab order and shows its value, `readOnly` (the
147
+ * settled grey FIELD_BASE gives a `[readonly]` field) and `aria-disabled`, as the
148
+ * Combobox family's lock is. Nothing changes the value: the step keys do nothing, the
149
+ * calculator and the phone's numpad are not offered, and neither blur nor Enter
150
+ * commits — so `onChange` and `onCommit` are never called. Enter is swallowed, so the
151
+ * field does not submit the form around it. The reason is in the kit {@link Tooltip}
152
+ * and on the input's `aria-describedby`, between the caption and the error. Wins over
153
+ * `disabled`.
154
+ */
155
+ disabledReason?: ReactNode;
156
+ /**
157
+ * This field COMMITS — {@link Button}'s `commit`: under a locked
158
+ * {@link WriteLockProvider} it is locked the `disabledReason` way with the lock's reason
159
+ * (which wins over its own). A field whose value only reaches the server through the
160
+ * form's Save leaves this off and stays editable under the lock; the Save takes
161
+ * `commit`. No provider, or an unlocked one: no effect. {@link NumberField} and
162
+ * {@link IntegerField} pass it on.
163
+ */
164
+ commit?: CommitScope;
138
165
  }
139
166
 
140
167
  /** Decimal places of `n` as written ("0.25" → 2, "1e-7" → 7), for decimal-exact
@@ -289,11 +316,18 @@ export function NumberInput(props: NumberInputProps) {
289
316
  step,
290
317
  min,
291
318
  max,
319
+ disabledReason,
320
+ commit: commitScope,
292
321
  } = props;
293
322
  const generatedId = useId();
294
323
  const fieldId = id ?? generatedId;
295
324
  const hintId = useId();
296
325
  const errorId = useId();
326
+ // The lock: focusable, `readOnly`, nothing changes or commits — see `disabledReason`.
327
+ const lock = useLockReason(commitScope, disabledReason);
328
+ const locked = lock.locked;
329
+ // Locked wins over `disabled`: the point of a reason is a field a keyboard reaches.
330
+ const off = Boolean(disabled) && !locked;
297
331
  // §4 "No fact only in a tooltip" (docs/text-size-harmonization.md): at Large and on a
298
332
  // touch screen a FieldHint is its words, so it takes the caption's path below — the
299
333
  // other kit fields do the same through `useFieldHint`.
@@ -303,15 +337,17 @@ export function NumberInput(props: NumberInputProps) {
303
337
  const hasError = hasMessage(error);
304
338
  // Passed at all — even as `undefined` — the field keeps its box; see `error`.
305
339
  const reserve = "error" in props || "hint" in props;
306
- // The standing advice first, the news second: the caller's ids, the caption, the error.
307
- const describedBy = mergeDescribedBy(ariaDescribedBy, textHint && hintId, hasError && errorId);
340
+ // The standing advice first, the news second: the caller's ids, the caption, the
341
+ // error — a lock's reason between the last two, as on Select.
342
+ const describedBy = mergeDescribedBy(ariaDescribedBy, textHint && hintId, locked && lock.reasonId, hasError && errorId);
308
343
  const invalid = Boolean(invalidProp) || hasError || ariaInvalid === true || ariaInvalid === "true";
309
344
  const labelled = label !== undefined;
310
345
  // On phones we suppress the OS keyboard (inputMode="none" below) for our own
311
346
  // calculator numpad, so the desktop popover trigger is hidden (feedback #334).
312
347
  const isMobile = usePhoneLayout();
313
348
  const asDisplay = isMobile && variant === "display";
314
- const showCalc = calculator && !disabled && !isMobile;
349
+ // A locked field offers no calculator: its result would be a value.
350
+ const showCalc = calculator && !disabled && !locked && !isMobile;
315
351
  const [focused, setFocused] = useState(false);
316
352
  // The unit and the calculator's measured width, reserved as the field's end padding.
317
353
  const endRef = useRef<HTMLDivElement>(null);
@@ -338,7 +374,7 @@ export function NumberInput(props: NumberInputProps) {
338
374
  }, [hasEnd]);
339
375
  // On mobile, focusing opens the numpad bottom sheet instead of the native
340
376
  // keyboard (#334); the ref lets its "Done" blur → commit + close.
341
- const showNumpad = isMobile && !disabled && focused;
377
+ const showNumpad = isMobile && !disabled && !locked && focused;
342
378
  const inputRef = useRef<HTMLInputElement>(null);
343
379
 
344
380
  // The mark is the locale's; see the component's note. `derived` is the value in that
@@ -395,6 +431,12 @@ export function NumberInput(props: NumberInputProps) {
395
431
  };
396
432
 
397
433
  const onKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
434
+ // Locked: Enter submits no form and commits nothing, and no key steps the value.
435
+ // The arrows and Home/End stay the caret's, for a long value someone is reading.
436
+ if (locked) {
437
+ if (e.key === "Enter") e.preventDefault();
438
+ return;
439
+ }
398
440
  if (e.key === "Enter") return commit();
399
441
  // A zero, negative or non-finite step is no step: the keys stay the caret's.
400
442
  if (!step || !(step > 0) || !Number.isFinite(step) || e.altKey || e.ctrlKey || e.metaKey) return;
@@ -406,9 +448,12 @@ export function NumberInput(props: NumberInputProps) {
406
448
  stepBy(count);
407
449
  };
408
450
 
451
+ // While locked the field sits in the reason's Tooltip, which becomes its outermost
452
+ // box — so `className` (a width, a flex or grid cell) moves onto that, and the form
453
+ // keeps its layout, as on Select.
409
454
  const field = (
410
455
  <FloatingField
411
- className={cn("w-full", className)}
456
+ className={cn("w-full", !locked && className)}
412
457
  htmlFor={fieldId}
413
458
  label={label}
414
459
  srOnlyLabel={asDisplay}
@@ -425,16 +470,23 @@ export function NumberInput(props: NumberInputProps) {
425
470
  autoComplete="off"
426
471
  // eslint-disable-next-line jsx-a11y/no-autofocus -- a documented prop the caller opts into (off by default); the field never takes focus on its own.
427
472
  autoFocus={autoFocus}
428
- disabled={disabled}
473
+ disabled={off}
474
+ // Locked: focusable, `readOnly`, `aria-disabled` — see `disabledReason`.
475
+ readOnly={locked || undefined}
476
+ aria-disabled={locked || undefined}
429
477
  // Display mode has no floating label to feed the blank-placeholder trick,
430
478
  // and an empty borderless figure shows nothing — so it keeps whatever
431
479
  // placeholder the caller gave, falling back to a zero to aim at.
432
480
  placeholder={asDisplay ? (placeholder ?? "0") : labelled ? " " : placeholder}
433
481
  value={display}
434
- onChange={(e) => typed(e.target.value)}
482
+ onChange={(e) => {
483
+ if (!locked) typed(e.target.value);
484
+ }}
435
485
  onFocus={() => setFocused(true)}
436
486
  onBlur={() => {
437
- commit();
487
+ // Nothing commits under the lock — not even a draft typed before it arrived,
488
+ // which stays in the field for when it lifts.
489
+ if (!locked) commit();
438
490
  setFocused(false);
439
491
  }}
440
492
  onKeyDown={onKeyDown}
@@ -455,6 +507,7 @@ export function NumberInput(props: NumberInputProps) {
455
507
  inputClassName,
456
508
  showCalc && suffix !== undefined ? "pe-16" : showCalc ? "pe-9" : suffix !== undefined ? "pe-8" : undefined,
457
509
  invalid && FIELD_INVALID,
510
+ locked && "cursor-not-allowed",
458
511
  )}
459
512
  // The classes above are the fallback; once the end controls are measured the
460
513
  // field reserves exactly their width (+4px). A fixed pe-8 for any unit left a
@@ -511,13 +564,20 @@ export function NumberInput(props: NumberInputProps) {
511
564
  )}
512
565
  </FloatingField>
513
566
  );
514
- if (!textHint && !hasError && !reserve) return field;
567
+ const box = locked ? (
568
+ <LockedReason lock={lock} className={cn("block w-full", className)}>
569
+ {field}
570
+ </LockedReason>
571
+ ) : (
572
+ field
573
+ );
574
+ if (!textHint && !hasError && !reserve) return box;
515
575
  // Outside the field's own `relative` box, as Select's caption is: the calculator
516
576
  // and the unit are `inset-y-0` in it and would stretch down over a second line.
517
577
  // `className` stays on the field, so adding a caption cannot change what it styles.
518
578
  return (
519
579
  <div>
520
- {field}
580
+ {box}
521
581
  {textHint && (
522
582
  <p id={hintId} className="mt-1 text-caption leading-tight text-[var(--text-muted)]">
523
583
  {hint}
@@ -87,11 +87,15 @@ const PAD_KEYS: PadKey[] = [
87
87
  * thinner than an outward ring plus the neighbour it would spill onto — inset keeps the
88
88
  * ring inside the key it describes.
89
89
  *
90
- * --brand rather than --border-strong: the operator keys are filled with --border, and
91
- * a ring in the colour of the thing it surrounds is not an indicator. */
90
+ * --brand rather than --border-strong: the operator keys are filled with a step of the
91
+ * ink, and a ring in the colour of the thing it surrounds is not an indicator. */
92
92
  const PAD_BTN = `flex h-14 items-center justify-center rounded-lg text-lg font-medium transition-transform select-none focus:outline-none ${FOCUS_RING} focus-visible:ring-inset active:scale-[0.97]`;
93
- const PAD_DIGIT = "bg-[var(--bg-surface-2)] text-[var(--text-primary)] active:bg-[var(--border)]";
94
- const PAD_ACCENT = "bg-[var(--border)] text-[var(--text-primary)] active:bg-[var(--bg-surface-2)]";
93
+ // Digits on the well, operators a step down on `--bg-active`, each pressing toward the
94
+ // other. `--bg-active`, not `--border` (0.33, docs/colour-roles-harmonization.md §12.9):
95
+ // More contrast steps the border until it is a 3:1 LINE, and as a fill under the label
96
+ // that left 2.7–3.3:1; `--bg-active` carries the ink at 7:1 and up at both levels.
97
+ const PAD_DIGIT = "bg-surface-2 text-primary active:bg-active";
98
+ const PAD_ACCENT = "bg-active text-primary active:bg-surface-2";
95
99
 
96
100
  export function NumberPadSheet({
97
101
  value,
@@ -144,7 +144,10 @@ export function Popover({
144
144
  : 0;
145
145
  const pos = rect
146
146
  ? {
147
- top: placement.top,
147
+ // Flipped above, the panel hangs from its bottom edge, so one shorter than its
148
+ // cap still meets the trigger (keksdose K1); the edge not in use is cleared.
149
+ top: placement.above ? undefined : placement.top,
150
+ bottom: placement.above ? placement.bottom : undefined,
148
151
  left: Math.min(
149
152
  Math.max(EDGE, dir === "rtl" ? rect.left : rect.right - panelWidth),
150
153
  window.innerWidth - panelWidth - EDGE,
@@ -213,12 +216,13 @@ export function Popover({
213
216
  tabIndex={-1}
214
217
  onKeyDown={handleKeyDown}
215
218
  style={{
216
- // Merged over the caller's, never replaced by it: these four are measured
219
+ // Merged over the caller's, never replaced by it: these are measured
217
220
  // against the trigger every scroll and resize, and a panel that dropped
218
221
  // them would paint at the top-left of the viewport.
219
222
  ...style,
220
223
  position: "fixed",
221
224
  top: pos.top,
225
+ bottom: pos.bottom,
222
226
  left: pos.left,
223
227
  width: panelWidth,
224
228
  maxHeight: placement.maxHeight,