@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
@@ -12,6 +12,7 @@ import {
12
12
  type DisabledReasonDisplay,
13
13
  } from "./field-parts";
14
14
  import { FOCUS_RING } from "./focus-ring";
15
+ import type { CommitScope } from "./write-lock";
15
16
 
16
17
  /**
17
18
  * A checkbox that is still `<input type="checkbox">`.
@@ -134,7 +135,7 @@ export interface CheckboxProps extends Omit<InputHTMLAttributes<HTMLInputElement
134
135
  * locked the `disabledReason` way with the lock's reason (which wins over its own).
135
136
  * No provider, or an unlocked one: no effect. Button's `commit`, for keksdose K3.
136
137
  */
137
- commit?: boolean;
138
+ commit?: CommitScope;
138
139
  }
139
140
 
140
141
  /**
@@ -10,7 +10,7 @@ import { DisabledReasonLine, useDisabledReasonLine, type DisabledReasonDisplay }
10
10
  import { DEFAULT_COMMON_LABELS, useKitLabels, useKitLink, useKitLocale } from "../i18n/kit-labels";
11
11
  import { pickLinkRenderer } from "./text-link";
12
12
  import { Tooltip } from "./tooltip";
13
- import { useCommitReason } from "./write-lock";
13
+ import { useCommitReason, type CommitScope } from "./write-lock";
14
14
 
15
15
  /**
16
16
  * A chip: a compact pill carrying one value.
@@ -137,7 +137,9 @@ const TONE: Record<ChipTone, { idle: string; selected: string }> = {
137
137
  // outline toggle that stays an outline when on has no visible state.
138
138
  const OUTLINE: Record<ChipTone, string> = {
139
139
  neutral: "border-[var(--border-strong)] bg-transparent text-[var(--text-secondary)]",
140
- brand: "border-[var(--brand)] bg-transparent text-[var(--brand)]",
140
+ // The brand AS TEXT is `--brand-muted` (0.33): `--brand` itself is solved to 3:1, a
141
+ // fill's boundary, and as text it fell to 3.0:1 in the derived dark presets.
142
+ brand: "border-[var(--brand)] bg-transparent text-brand-muted",
141
143
  danger: "border-[var(--danger-border)] bg-transparent text-[var(--danger)]",
142
144
  warning: "border-[var(--warning-border)] bg-transparent text-[var(--warning)]",
143
145
  success: "border-[var(--success-border)] bg-transparent text-[var(--success)]",
@@ -151,25 +153,27 @@ const OUTLINE: Record<ChipTone, string> = {
151
153
  orange: "border-[var(--hue-orange-border)] bg-transparent text-[var(--hue-orange)]",
152
154
  };
153
155
 
154
- // `solid`: the fill under its contrast pair. Only brand and danger have a declared
155
- // `-contrast` token; the other hues are 700s in the light theme and 300s in the dark
156
- // one, so `--text-inverse` (the surface colour) is the side of the pair that contrasts
157
- // with them in both — which is what the inverse token is for. The border goes
158
- // transparent rather than away, so a solid chip is exactly as tall as a soft one.
156
+ // `solid`: the fill under its own foreground, `<fill>-contrast` (0.33, colour-roles
157
+ // §5.1): white in light, the tone's 950 (the page, for money) on the dark theme's
158
+ // pastels. Until 0.33 only brand and danger had one and the rest borrowed
159
+ // `--text-inverse`, the surface colour — which left light warning at 4.24:1 and orange
160
+ // at 4.38:1. Neutral keeps the inverse pair: a count pill, louder than the neutral
161
+ // fill. The border goes transparent rather than away, so a solid chip is exactly as
162
+ // tall as a soft one.
159
163
  const SOLID: Record<ChipTone, string> = {
160
- neutral: "border-transparent bg-[var(--bg-inverse)] text-[var(--text-inverse)]",
161
- brand: "border-transparent bg-[var(--brand)] text-[var(--brand-contrast)]",
162
- danger: "border-transparent bg-[var(--danger)] text-[var(--danger-contrast)]",
163
- warning: "border-transparent bg-[var(--warning)] text-[var(--text-inverse)]",
164
- success: "border-transparent bg-[var(--success)] text-[var(--text-inverse)]",
165
- info: "border-transparent bg-[var(--info)] text-[var(--text-inverse)]",
166
- income: "border-transparent bg-[var(--money-income)] text-[var(--text-inverse)]",
167
- expense: "border-transparent bg-[var(--money-expense)] text-[var(--text-inverse)]",
168
- blue: "border-transparent bg-[var(--hue-blue)] text-[var(--text-inverse)]",
169
- indigo: "border-transparent bg-[var(--hue-indigo)] text-[var(--text-inverse)]",
170
- purple: "border-transparent bg-[var(--hue-purple)] text-[var(--text-inverse)]",
171
- teal: "border-transparent bg-[var(--hue-teal)] text-[var(--text-inverse)]",
172
- orange: "border-transparent bg-[var(--hue-orange)] text-[var(--text-inverse)]",
164
+ neutral: "border-transparent bg-inverse text-inverse",
165
+ brand: "border-transparent bg-brand text-brand-contrast",
166
+ danger: "border-transparent bg-danger text-danger-contrast",
167
+ warning: "border-transparent bg-warning text-warning-contrast",
168
+ success: "border-transparent bg-success text-success-contrast",
169
+ info: "border-transparent bg-info text-info-contrast",
170
+ income: "border-transparent bg-money-pos text-money-income-contrast",
171
+ expense: "border-transparent bg-money-neg text-money-expense-contrast",
172
+ blue: "border-transparent bg-hue-blue text-hue-blue-contrast",
173
+ indigo: "border-transparent bg-hue-indigo text-hue-indigo-contrast",
174
+ purple: "border-transparent bg-hue-purple text-hue-purple-contrast",
175
+ teal: "border-transparent bg-hue-teal text-hue-teal-contrast",
176
+ orange: "border-transparent bg-hue-orange text-hue-orange-contrast",
173
177
  };
174
178
 
175
179
  /** `dot`: the fill of the dot itself — the tone's strongest colour, which is the one
@@ -380,7 +384,8 @@ export function refreshChipEdges(): void {
380
384
  for (let pass = 0; pass <= n && pending.length; pass++) {
381
385
  const reads = pending.map((el) => {
382
386
  const r = el.getBoundingClientRect();
383
- const label = el.querySelector<HTMLElement>(".truncate");
387
+ // The text label (CHIP_LABEL_TRUNCATE; `.truncate` before 0.33).
388
+ const label = el.querySelector<HTMLElement>(".truncate-until-large");
384
389
  // Not laid out (a hidden ancestor, jsdom), or truncated at its container's edge,
385
390
  // where a wider chip would overflow: leave it alone. So too a label wrapped onto
386
391
  // a second line at Large (0.32.1): the chip is already as wide as its row lets it
@@ -511,16 +516,16 @@ const CHIP_BASE = `${CHIP_PILL} ${CHIP_RING}`;
511
516
  * "…". keksdose's "Zur Prüfung zurücklegen" read "Zur Prüfung zu…" on a phone at Extra
512
517
  * large, which is a different instruction. No size has a fixed height (padding and
513
518
  * `min-h-*` only), so the pill grows with its lines; the mark before the text stays
514
- * centred on them, as in a pill. A `large:` class, so Normal keeps its one-line chip.
519
+ * centred on them, as in a pill. A `large:` rule, so Normal keeps its one-line chip.
515
520
  *
516
- * `break-words`, not `[overflow-wrap:anywhere]`: a status chip often sits in a table
517
- * cell, and `anywhere` lowers the label's min-content width to one letter, so an
518
- * auto-sized column can squeeze it to a letter per line (StatusDot's label did, in the
519
- * showcase's tone table at 360 px, Extra large). `break-word` keeps each word whole
520
- * wherever the width is the content's to choose, and still breaks one that cannot fit
521
- * the row at all.
521
+ * 0.33 (§10.17): the kit's one `truncate-until-large` (tokens.css) in place of this
522
+ * file's own `truncate` + `large:whitespace-normal large:break-words`, as StatusDot's
523
+ * label took it. The utility breaks with `overflow-wrap: anywhere`, which lowers the
524
+ * label's min-content width to one letter: a status chip in an auto-sized table column
525
+ * wants the column given a width at Large, or it is squeezed to a letter per line
526
+ * (StatusDot's label was, in the showcase's tone table at 360 px, Extra large).
522
527
  */
523
- const CHIP_LABEL_WRAP_AT_LARGE = "large:whitespace-normal large:break-words";
528
+ const CHIP_LABEL_TRUNCATE = "truncate-until-large";
524
529
 
525
530
  /**
526
531
  * An interactive chip's touch target at Large and Extra large (docs/text-size-harmonization.md
@@ -720,7 +725,7 @@ export type ChipProps = ChipBaseProps &
720
725
  /** This chip COMMITS — pressing it saves. Under a locked
721
726
  * {@link WriteLockProvider} it takes the `disabledReason` path with the lock's
722
727
  * reason, as {@link Button}'s `commit` does. */
723
- commit?: boolean;
728
+ commit?: CommitScope;
724
729
  }
725
730
  );
726
731
 
@@ -841,7 +846,7 @@ export const Chip = forwardRef<HTMLElement, ChipProps>(function Chip(
841
846
  ) : (
842
847
  <Icon className={cn(s.icon, "shrink-0")} aria-hidden />
843
848
  ))}
844
- {/* Text truncates at Normal and wraps at Large (CHIP_LABEL_WRAP_AT_LARGE). Mixed
849
+ {/* Text truncates at Normal and wraps at Large (CHIP_LABEL_TRUNCATE). Mixed
845
850
  children (an icon and a word) sit in a row instead: as a plain span, the svg —
846
851
  a block under Tailwind's preflight — stacked above the text (keksdose live
847
852
  #358). */}
@@ -849,7 +854,7 @@ export const Chip = forwardRef<HTMLElement, ChipProps>(function Chip(
849
854
  className={cn(
850
855
  "min-w-0",
851
856
  typeof children === "string" || typeof children === "number"
852
- ? cn("truncate", CHIP_LABEL_WRAP_AT_LARGE)
857
+ ? CHIP_LABEL_TRUNCATE
853
858
  : "inline-flex items-center gap-1 [&>svg]:size-[1em] [&>svg]:shrink-0",
854
859
  )}
855
860
  >
@@ -247,13 +247,15 @@ const META_TONE: Record<ActionCardMetaTone, string> = {
247
247
  };
248
248
 
249
249
  /** The colour of {@link ActionCardProps.icon}. `default` is the secondary text colour
250
- * the icon has always had; `brand` tints it (keksdose's privacy enrolment). */
250
+ * the icon has always had; `brand` tints it (keksdose's privacy enrolment) with the
251
+ * brand as text, `--brand-muted` (0.33, docs/colour-roles-harmonization.md §5.5):
252
+ * `--brand` read 3.03:1 on the card's hover in the derived dark presets. */
251
253
  export type ActionCardIconTone = "default" | "muted" | "brand" | "warning" | "danger" | "info" | "success";
252
254
 
253
255
  const ICON_TONE: Record<ActionCardIconTone, string> = {
254
256
  default: "text-[var(--text-secondary)]",
255
257
  ...META_TONE,
256
- brand: "text-[var(--brand)]",
258
+ brand: "text-brand-muted",
257
259
  };
258
260
 
259
261
  /** What {@link ActionCardProps.renderLink} (and the provider's `linkComponent`) is
@@ -899,11 +899,14 @@ export function ComboboxPanel<V extends string | number>({
899
899
  onKeyDown={onKeyDown}
900
900
  // `placement` keeps the panel inside the region actually on screen: on a phone
901
901
  // the search box below pulls up the keyboard, and a panel pinned under a
902
- // low trigger would otherwise sit entirely behind it (feedback #135).
902
+ // low trigger would otherwise sit entirely behind it (feedback #135). Flipped
903
+ // above, it hangs from its bottom edge, so a list shorter than its cap still meets
904
+ // the trigger (keksdose K1); the edge not in use is cleared, a caller's included.
903
905
  style={{
904
906
  ...style,
905
907
  position: "fixed",
906
- top: placement.top,
908
+ top: placement.above ? undefined : placement.top,
909
+ bottom: placement.above ? placement.bottom : undefined,
907
910
  ...inline,
908
911
  width: rect.width,
909
912
  // 220 px at Normal, in rem so the panel's text keeps its room at Large (§3.2).
@@ -34,7 +34,7 @@ import {
34
34
  useLockReason,
35
35
  } from "./field-parts";
36
36
  import { hasMessage, mergeDescribedBy } from "./choice-parts";
37
- import { useCommitReason } from "./write-lock";
37
+ import { useCommitReason, type CommitScope } from "./write-lock";
38
38
 
39
39
  // One look for both combobox flavors below — the suggestion list and its rows
40
40
  // must stay pixel-identical between the free-text and the id-keyed variant (and
@@ -112,7 +112,7 @@ function SuggestionList({
112
112
  panelRef: RefObject<HTMLDivElement | null>;
113
113
  children: ReactNode;
114
114
  }) {
115
- const { rect, top, maxHeight } = useAnchoredPanel(anchorRef, true, { preferredHeight: 256 });
115
+ const { rect, top, bottom, maxHeight, above } = useAnchoredPanel(anchorRef, true, { preferredHeight: 256 });
116
116
  const dir = useAnchorDir(anchorRef, true);
117
117
  if (!rect) return null;
118
118
  return createPortal(
@@ -124,7 +124,9 @@ function SuggestionList({
124
124
  // Portalled out of the form's `dir`; the field's is put back. The list is
125
125
  // exactly the field's width, so `left` places it in either direction.
126
126
  dir={dir}
127
- style={{ top, left: rect.left, width: rect.width }}
127
+ // Flipped above, the list hangs from its bottom edge: it filters as the user
128
+ // types, and a short one placed by `top` floated above the field (keksdose K1).
129
+ style={{ ...(above ? { bottom } : { top }), left: rect.left, width: rect.width }}
128
130
  >
129
131
  <ul id={id} role="listbox" data-clips="" className={LIST_CLASS} style={{ maxHeight }}>
130
132
  {children}
@@ -207,7 +209,7 @@ export interface ComboboxProps
207
209
  /** This field COMMITS. Under a locked {@link WriteLockProvider} it is locked the
208
210
  * `disabledReason` way with the lock's reason; inside a form with its own Save, leave
209
211
  * it off and put `commit` on the Save. No provider, or an unlocked one: no effect. */
210
- commit?: boolean;
212
+ commit?: CommitScope;
211
213
  /** Heading an option belongs under. Supplying it makes this list read exactly
212
214
  * like {@link InlineEntityCombobox}'s — one heading per group with its rows
213
215
  * indented beneath — instead of a flat list (feedback #136 rework: the payee
@@ -849,7 +851,7 @@ export interface InlineEntityComboboxProps<V extends string | number, C extends
849
851
  * A picker inside a form with its own Save stays editable under the lock — leave this
850
852
  * off there and put `commit` on the Save. No provider, or an unlocked one: no effect.
851
853
  */
852
- commit?: boolean;
854
+ commit?: CommitScope;
853
855
  /**
854
856
  * A last row that makes a new record from what was typed — keksdose G9, whose "Create
855
857
  * cash account" had to become a separate button because this picker had no such row.
@@ -894,7 +896,7 @@ export interface InlineEntityComboboxProps<V extends string | number, C extends
894
896
  * one explanation a phone cannot show), and `onCreate` is not called. Not needed with
895
897
  * `commit`: a locked field opens no list at all.
896
898
  */
897
- createCommit?: boolean;
899
+ createCommit?: CommitScope;
898
900
  /**
899
901
  * The `<input>` — the focusable control, the one a reader types in — as a React 19
900
902
  * ref prop (0.24, kastlan). Its account picker is this component inside an
@@ -6,7 +6,7 @@ import { TypedConfirmField, typedMatches } from "./danger-confirm";
6
6
  import type { TypedMatch } from "./danger-confirm";
7
7
  import { DialogFrame } from "./dialog-frame";
8
8
  import { Button } from "./ui";
9
- import { useWriteLock } from "./write-lock";
9
+ import { useWriteLock, writeLockFor, type CommitScope } from "./write-lock";
10
10
 
11
11
  /**
12
12
  * The `confirmDialog` namespace of `<UiKitProvider labels>`: the two buttons' fallback
@@ -83,8 +83,12 @@ export interface ConfirmOptions {
83
83
  * host sits — it is `aria-disabled` with the lock's reason, so a read-only page (a
84
84
  * demo, a viewer's budget) can't confirm a delete through a dialog. One prop for the
85
85
  * many confirm sites an app has (kastlan's 0.31 adoption counted ~23).
86
+ *
87
+ * 0.33: a {@link CommitScope} — `confirm({ commit: COMMIT_EXCEPT_BILLING })` for a
88
+ * confirm a lapsed plan still allows (removing access, the account's own settings;
89
+ * docs/billing-harmonization.md §12.36).
86
90
  */
87
- commit?: boolean;
91
+ commit?: CommitScope;
88
92
  }
89
93
 
90
94
  export type ConfirmFn = (options: ConfirmOptions) => Promise<boolean>;
@@ -107,12 +111,15 @@ export function useConfirm(): ConfirmFn {
107
111
  throw new Error("useConfirm() must be called below a <ConfirmProvider>.");
108
112
  }
109
113
  // The lock of the CALLER's place: the dialog host usually sits above the app's
110
- // `WriteLockProvider`, so it could not read the lock itself.
114
+ // `WriteLockProvider`, so it could not read the lock itself. Each call's own `commit`
115
+ // scope is applied to it when the call runs (`writeLockFor`, §12.36).
111
116
  const lock = useWriteLock();
112
117
  return useCallback(
113
- (options: ConfirmOptions) =>
114
- confirm(options.commit && lock.locked ? ({ ...options, lockReason: lock.reason } as ConfirmOptions) : options),
115
- [confirm, lock.locked, lock.reason],
118
+ (options: ConfirmOptions) => {
119
+ const scoped = writeLockFor(lock, options.commit);
120
+ return confirm(scoped.locked ? ({ ...options, lockReason: scoped.reason } as ConfirmOptions) : options);
121
+ },
122
+ [confirm, lock],
116
123
  );
117
124
  }
118
125
 
@@ -13,7 +13,7 @@ import {
13
13
  type ComboOption,
14
14
  } from "./combobox-core";
15
15
  import { assignRef, hasMessage, mergeDescribedBy } from "./choice-parts";
16
- import { useCommitReason } from "./write-lock";
16
+ import { useCommitReason, type CommitScope } from "./write-lock";
17
17
  import {
18
18
  DEFAULT_COMBOBOX_LABELS,
19
19
  DEFAULT_COMMON_LABELS,
@@ -147,7 +147,7 @@ export interface CountrySelectProps<Clearable extends boolean = false>
147
147
  * reason. A country field inside a form that has its own Save stays editable under
148
148
  * the lock — leave this off there, and put `commit` on the Save.
149
149
  */
150
- commit?: boolean;
150
+ commit?: CommitScope;
151
151
  /** The language the names are in. Default: the `<UiKitProvider locale>`, else
152
152
  * English (see {@link countryName}). */
153
153
  locale?: string;
@@ -6,7 +6,7 @@ import { Button, Input, Label, Spinner } from "./ui";
6
6
  import type { ButtonVariant, InputProps } from "./ui";
7
7
  import { Checkbox } from "./checkbox";
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
  * How typed text is compared with the text it has to repeat — shared by
@@ -567,7 +567,7 @@ export interface DangerConfirmProps extends Omit<ComponentPropsWithoutRef<"div">
567
567
  * provider no longer threads `lockedReason={lock.locked ? lock.reason : undefined}`
568
568
  * into every tile. No provider, or an unlocked one: no effect.
569
569
  */
570
- commit?: boolean;
570
+ commit?: CommitScope;
571
571
  /** Controlled armed state. The parent can then collapse the tile from a mutation's
572
572
  * own `onSuccess` without returning a promise. */
573
573
  armed?: boolean;
@@ -246,6 +246,23 @@ export interface DataTableProps<T> {
246
246
  rowHref?: (row: T) => string | undefined;
247
247
  expandedRow?: (row: T) => ReactNode | null;
248
248
  isExpanded?: (row: T) => boolean;
249
+ /**
250
+ * Extra classes for a row — the `<tr>` on desktop, the card on mobile. Merged last,
251
+ * so a `hover:` fill here replaces the row's own `hover:bg-[var(--bg-hover)]`.
252
+ *
253
+ * **A row you paint has to pick its own hover** (0.33, docs/colour-roles-harmonization.md
254
+ * §12.2): the kit cannot see your rest colour, and `--bg-hover` is mixed against the
255
+ * card. The rule: a hover differs from its rest by 1.08:1 or more, and every text role
256
+ * on it keeps 4.5:1.
257
+ * - A row that rests on the well (`bg-surface-2`) hovers to the CARD,
258
+ * `hover:bg-surface`: in light the well is the page, and `--bg-hover` sits 1.04:1
259
+ * from it.
260
+ * - A row whose well MARKS A STATE (selected) keeps its fill and draws a 1 px inside
261
+ * line instead, since the card would read as "not selected":
262
+ * `bg-surface-2 hover:bg-surface-2 hover:outline hover:-outline-offset-1 hover:outline-strong`.
263
+ * An outline, not a shadow: a shadow on a `<tr>` is not painted by every engine.
264
+ * - Not `--bg-active` for a selected row: its muted and money text fall to 3.8–4.7:1.
265
+ */
249
266
  rowClassName?: (row: T) => string | undefined;
250
267
  /**
251
268
  * Extra DOM attributes for a row's container — the `<tr>` on desktop, the
@@ -1199,15 +1216,9 @@ export function DataTable<T>({
1199
1216
  label: a.label,
1200
1217
  icon: a.icon,
1201
1218
  onCommit: () => void runRowAction(a.action, row),
1202
- // Each fill's own text colour — the fills are pastels in dark mode (0.26).
1203
- className:
1204
- a.tone === "danger"
1205
- ? "bg-[var(--danger)] text-[var(--danger-contrast)]"
1206
- : "bg-[var(--brand)] text-[var(--brand-contrast)]",
1207
- armedClassName:
1208
- a.tone === "danger"
1209
- ? "bg-[var(--danger-hover)] text-[var(--danger-contrast)]"
1210
- : "bg-[var(--brand-hover)] text-[var(--brand-contrast)]",
1219
+ // The row action's tone, painted by SwipeableRow: its soft wash idle, its fill
1220
+ // and that fill's foreground armed (0.33).
1221
+ tone: a.tone === "danger" ? "danger" : "brand",
1211
1222
  });
1212
1223
  return {
1213
1224
  start: swipes.filter((a) => a.swipe === "start").map(toSwipe),
@@ -17,7 +17,7 @@ import { splitTriggerAria } from "./trigger-aria";
17
17
  import type { TriggerAria } from "./trigger-aria";
18
18
  import { Button, FIELD_BASE, FIELD_TRIGGER, FIELD_FLOATING_PAD, FIELD_INVALID } from "./ui";
19
19
  import { FOCUS_RING_WIDTH } from "./focus-ring";
20
- import { FieldBox, FieldLabelLine, useFieldMessages } from "./field-parts";
20
+ import { FieldBox, FieldLabelLine, LABEL_ABOVE_BOX, useFieldMessages } from "./field-parts";
21
21
  import { FullBleedDialog } from "./full-bleed-dialog";
22
22
  import { usePhoneLayout, type RemLength } from "../hooks/use-breakpoint";
23
23
  import { DEFAULT_MINI_CALENDAR_LABELS, MiniCalendar, type MiniCalendarProps } from "./mini-calendar";
@@ -210,11 +210,12 @@ function CustomTrigger({
210
210
  * The trigger's value wraps instead of truncating at Large and Extra large (0.32.1, the
211
211
  * 360 px Extra-large sweep; docs/text-size-harmonization.md §4 "nothing truncates"). A
212
212
  * range ("01/09/2026 – 30/09/2026") is 312 px at 150 %, and on a 360 px phone it read
213
- * "01/09/2026 – 30/…" — its end, the half a reader needs, was the half cut off. It breaks
214
- * at its spaces; a single date breaks only where it cannot fit at all. A `large:` class,
215
- * so Normal keeps its one-line field.
213
+ * "01/09/2026 – 30/…" — its end, the half a reader needs, was the half cut off. A
214
+ * `large:` class, so Normal keeps its one-line field: since 0.33 (§10.17) the kit's one
215
+ * `truncate-until-large` (tokens.css) in place of this file's own `truncate` +
216
+ * `large:whitespace-normal large:break-words`.
216
217
  */
217
- const VALUE_WRAP_AT_LARGE = "large:whitespace-normal large:break-words";
218
+ const VALUE_TRUNCATE = "truncate-until-large";
218
219
 
219
220
  /**
220
221
  * The field-shaped button that opens the calendar.
@@ -330,7 +331,7 @@ function DateFieldTrigger({
330
331
 
331
332
  return (
332
333
  <button {...attrs}>
333
- <span id={valueId} className={cn("truncate", VALUE_WRAP_AT_LARGE, !hasValue && "text-[var(--text-placeholder)]")}>
334
+ <span id={valueId} className={cn(VALUE_TRUNCATE, !hasValue && "text-[var(--text-placeholder)]")}>
334
335
  {/* `|| " "` (a NON-BREAKING space) — triggerText is "" when there is no value and no
335
336
  placeholder was passed. An empty span has no line box, so the
336
337
  trigger collapses to its padding and sits shorter than every
@@ -515,7 +516,9 @@ function DateField({
515
516
  return (
516
517
  // The caller's attributes land here, on the field's own box — the trigger inside is
517
518
  // named by `aria-labelledby` and must keep the id pair it is given.
518
- <div {...wrapperRest} ref={rootRef} className={cn("relative", className)}>
519
+ // At Large a labelled box is a grid: the label above, the calendar glyph and the
520
+ // clear × measured against the trigger's row (LABEL_ABOVE_BOX, 0.33 §10.17).
521
+ <div {...wrapperRest} ref={rootRef} className={cn("relative", label !== undefined && LABEL_ABOVE_BOX, className)}>
519
522
  {label !== undefined && <FieldLabelLine label={label} hint={labelHint} />}
520
523
  {/* The visible FieldLabel is a plain span, not a `<label htmlFor>`, so it names
521
524
  nothing on its own — this hidden twin is what the trigger is named by.
@@ -341,8 +341,18 @@ export function DropdownPanel({
341
341
  dir={dir}
342
342
  // The caller's `style` is kept underneath, but the placement is measured rather
343
343
  // than chosen (dev#548: an `overflow` ancestor clipped a third of this panel
344
- // away), so those five values are the panel's own.
345
- style={{ ...style, position: "fixed", top: anchored.top, left, width, maxHeight: anchored.maxHeight }}
344
+ // away), so these values are the panel's own. Flipped above, it hangs from its
345
+ // bottom edge, so a list shorter than its cap still meets the trigger (keksdose
346
+ // K1); the edge not in use is cleared, a caller's included.
347
+ style={{
348
+ ...style,
349
+ position: "fixed",
350
+ top: anchored.above ? undefined : anchored.top,
351
+ bottom: anchored.above ? anchored.bottom : undefined,
352
+ left,
353
+ width,
354
+ maxHeight: anchored.maxHeight,
355
+ }}
346
356
  className={cn(
347
357
  "z-50 flex flex-col overflow-hidden rounded-md border border-[var(--border)] bg-[var(--bg-surface)] shadow-lg",
348
358
  // The list, not the panel, is what scrolls: the search header has to stay put
@@ -23,7 +23,7 @@ import {
23
23
  useLockReason,
24
24
  } from "./field-parts";
25
25
  import { assignRef, mergeDescribedBy } from "./choice-parts";
26
- import { useCommitReason } from "./write-lock";
26
+ import { useCommitReason, type CommitScope } from "./write-lock";
27
27
 
28
28
  export type { ComboClearValue, ComboOption } from "./combobox-core";
29
29
 
@@ -89,7 +89,7 @@ export interface EntityComboboxProps<V extends string | number, C extends ComboC
89
89
  * A picker inside a form with its own Save stays editable under the lock: leave this
90
90
  * off there and put `commit` on the Save. No provider, or an unlocked one: no effect.
91
91
  */
92
- commit?: boolean;
92
+ commit?: CommitScope;
93
93
  /** When set, a "create" row appears for a non-empty query with no exact match. */
94
94
  onCreate?: (query: string) => void;
95
95
  createLabel?: (query: string) => string;
@@ -106,7 +106,7 @@ export interface EntityComboboxProps<V extends string | number, C extends ComboC
106
106
  * taken, with the lock's reason as its second line — the picker itself stays live.
107
107
  * keksdose dev#496: on the read-only demo choosing is draft state, minting is not.
108
108
  */
109
- createCommit?: boolean;
109
+ createCommit?: CommitScope;
110
110
  /** Required and unanswered — {@link FIELD_INVALID}. See {@link Input}'s `invalid`. */
111
111
  invalid?: boolean;
112
112
  /** What is wrong with the value, as {@link Input}'s `error`: rendered under the
@@ -5,7 +5,7 @@ import { cn } from "../lib/cn";
5
5
  import { useInlineFacts } from "../hooks/use-large-text";
6
6
  import { FOCUS_RING } from "./focus-ring";
7
7
  import { Tooltip, type TooltipSide } from "./tooltip";
8
- import { useCommitReason } from "./write-lock";
8
+ import { useCommitReason, type CommitScope } from "./write-lock";
9
9
  import { hasMessage, mergeDescribedBy } from "./choice-parts";
10
10
 
11
11
  /**
@@ -34,12 +34,45 @@ import { hasMessage, mergeDescribedBy } from "./choice-parts";
34
34
  export const STATIC_LABEL_TYPE =
35
35
  "text-caption leading-tight text-[var(--text-muted)] peer-disabled:opacity-50";
36
36
 
37
+ /**
38
+ * A field's label at Large and Extra large (0.33, docs/text-size-harmonization.md
39
+ * §10.17): a static label ABOVE the field — the phone card's "label above value" (§4) —
40
+ * not a label squeezed into the field's top strip, where a long one ended in "…". It
41
+ * stops being absolute, so it wraps like any text (`truncate-until-large` beside it); it
42
+ * takes the pointer, since nothing of the field lies under it any more; and the field
43
+ * no longer reserves the strip for it (`FIELD_FLOATING_PAD`). In the first row of a box
44
+ * that is a grid at Large ({@link LABEL_ABOVE_BOX}), wherever it sits in the DOM — a
45
+ * floating label FOLLOWS its input, for `peer-*`. `large:` classes, so it costs no
46
+ * render and is right on the first paint; Normal keeps the floating label.
47
+ *
48
+ * The placement only: a label element adds `large:block` (a `<span>` or `<label>` in
49
+ * flow), a label ROW keeps its `flex`.
50
+ */
51
+ export const LABEL_ABOVE_AT_LARGE =
52
+ "large:static large:row-start-1 large:mb-1 large:max-w-none large:pointer-events-auto";
53
+
54
+ /**
55
+ * The box a field's label is positioned in — {@link FloatingField}'s `relative` wrapper,
56
+ * a date or month picker's root — at Large, while it holds a label above
57
+ * ({@link LABEL_ABOVE_AT_LARGE}): one column, the label's row over the field's. The
58
+ * field's own absolute parts in the box (a chevron, a clear ×, a reveal toggle, a
59
+ * calculator) are placed in the second row, so their `top-1/2` and `inset-y-*` still
60
+ * measure the FIELD and not label and field together; the label's own elements carry
61
+ * `data-field-label` and stay in the first. Only on a box with a visible label: without
62
+ * one the second row would be empty.
63
+ */
64
+ export const LABEL_ABOVE_BOX =
65
+ "large:grid large:grid-cols-[minmax(0,1fr)] large:[&>.absolute:not([data-field-label])]:row-start-2";
66
+
37
67
  // A field that always has a value (select / dropdown trigger) keeps the label
38
- // permanently in the floated position — small, in the top strip, value below.
68
+ // permanently in the floated position — small, in the top strip, value below. At Large,
69
+ // a label above the field (LABEL_ABOVE_AT_LARGE).
39
70
  export const FLOATING_LABEL_STATIC = cn(
40
71
  "pointer-events-none absolute start-3 top-1",
41
72
  STATIC_LABEL_TYPE,
42
- "max-w-[calc(100%-1.5rem)] truncate",
73
+ "max-w-[calc(100%-1.5rem)] truncate-until-large",
74
+ LABEL_ABOVE_AT_LARGE,
75
+ "large:block",
43
76
  );
44
77
 
45
78
  /**
@@ -53,7 +86,9 @@ export interface FieldLabelProps extends ComponentPropsWithoutRef<"span"> {
53
86
 
54
87
  export function FieldLabel({ children, className, ...rest }: FieldLabelProps) {
55
88
  return (
56
- <span {...rest} className={cn(FLOATING_LABEL_STATIC, "z-10", className)}>
89
+ // `data-field-label`: a label of the box it stands in, kept in the first row at Large
90
+ // (LABEL_ABOVE_BOX).
91
+ <span data-field-label="" {...rest} className={cn(FLOATING_LABEL_STATIC, "z-10", className)}>
57
92
  {children}
58
93
  </span>
59
94
  );
@@ -207,9 +242,10 @@ export function FieldCaption({ parts, className }: { parts: FieldHintParts; clas
207
242
  * Classes that take a static floating label (`FLOATING_LABEL_STATIC`: `FieldLabel`,
208
243
  * `ComboboxFieldLabel`) out of its own absolute slot and into a {@link StaticLabelRow}.
209
244
  * `static` wins over `absolute` through `cn`'s merge; `min-w-0` lets it truncate in
210
- * the row.
245
+ * the row. At Large the ROW stands above the field, so the label inside it drops the
246
+ * margin a label above takes alone (`large:mb-0`), and stays level with its "?".
211
247
  */
212
- export const LABEL_IN_ROW = "static min-w-0";
248
+ export const LABEL_IN_ROW = "static min-w-0 large:mb-0";
213
249
 
214
250
  /**
215
251
  * A static label and its "?" on one line, in the top strip of a field that lays out its
@@ -222,7 +258,10 @@ export const LABEL_IN_ROW = "static min-w-0";
222
258
  */
223
259
  export function StaticLabelRow({ hint, children }: { hint: ReactNode; children: ReactNode }) {
224
260
  return (
225
- <div className="pointer-events-none absolute start-3 end-9 top-1 z-10 flex items-center gap-1">
261
+ <div
262
+ data-field-label=""
263
+ className={cn("pointer-events-none absolute start-3 end-9 top-1 z-10 flex items-center gap-1", LABEL_ABOVE_AT_LARGE)}
264
+ >
226
265
  {children}
227
266
  <span className="pointer-events-auto flex shrink-0 items-center">{hint}</span>
228
267
  </div>
@@ -270,7 +309,7 @@ export interface LockReason {
270
309
  * switch, a checkbox or a select that saves on change, a list row whose click writes.
271
310
  * The lock's reason wins over the control's own (`useCommitReason`).
272
311
  */
273
- export function useLockReason(commit: boolean | undefined, disabledReason: ReactNode): LockReason {
312
+ export function useLockReason(commit: CommitScope | undefined, disabledReason: ReactNode): LockReason {
274
313
  const reason = useCommitReason(commit, disabledReason);
275
314
  const reasonId = useId();
276
315
  return { reason, locked: hasMessage(reason), reasonId };
@@ -510,9 +549,12 @@ export function FieldLabelLine({ label, hint }: { label: ReactNode; hint?: React
510
549
  if (!hasContent(hint)) return <FieldLabel>{label}</FieldLabel>;
511
550
  return (
512
551
  // `z-10` as FieldLabel has it: the trigger after it is `relative` and would paint
513
- // over the row (and take the "?"'s hover) otherwise.
514
- <div className="pointer-events-none absolute start-3 end-9 top-1 z-10 flex items-center gap-1">
515
- <FieldLabel className="static min-w-0 max-w-full">{label}</FieldLabel>
552
+ // over the row (and take the "?"'s hover) otherwise. Above the field at Large.
553
+ <div
554
+ data-field-label=""
555
+ className={cn("pointer-events-none absolute start-3 end-9 top-1 z-10 flex items-center gap-1", LABEL_ABOVE_AT_LARGE)}
556
+ >
557
+ <FieldLabel className={cn(LABEL_IN_ROW, "max-w-full")}>{label}</FieldLabel>
516
558
  <span className="pointer-events-auto flex shrink-0 items-center">{hint}</span>
517
559
  </div>
518
560
  );
@@ -570,12 +612,17 @@ export function LabelStrip({
570
612
  children: ReactNode;
571
613
  }) {
572
614
  return (
573
- <div {...rest} className={cn("relative min-w-0", LABEL_STRIP_PAD[pad], className)}>
574
- <div className="pointer-events-none absolute inset-x-3 top-1 flex items-center gap-1">
615
+ // At Large the row is a label above the group, in flow (0.33, §10.17), and the
616
+ // strip's padding goes: nothing lies under it any more.
617
+ <div {...rest} className={cn("relative min-w-0", LABEL_STRIP_PAD[pad], "large:pt-0", className)}>
618
+ <div
619
+ data-field-label=""
620
+ className={cn("pointer-events-none absolute inset-x-3 top-1 flex items-center gap-1", LABEL_ABOVE_AT_LARGE)}
621
+ >
575
622
  {/* `static`: FieldLabel positions itself absolutely; here the ROW is
576
623
  positioned, so the label and its "?" are centred on one line by the flex
577
624
  layout (dev#468). */}
578
- <FieldLabel id={labelId} className={cn("static min-w-0 max-w-full", disabled && "opacity-50")}>
625
+ <FieldLabel id={labelId} className={cn(LABEL_IN_ROW, "max-w-full", disabled && "opacity-50")}>
579
626
  {label}
580
627
  </FieldLabel>
581
628
  {hasContent(hint) && <span className="pointer-events-auto flex shrink-0 items-center">{hint}</span>}