@eifi1/ui-kit 0.51.3 → 0.51.5

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 (780) hide show
  1. package/README.md +6 -6
  2. package/dist/account/account-settings-labels.js.map +1 -1
  3. package/dist/account/account-settings.js +11 -9
  4. package/dist/account/account-settings.js.map +1 -1
  5. package/dist/account/delete-account-setting.js +56 -65
  6. package/dist/account/delete-account-setting.js.map +1 -1
  7. package/dist/account/email-change-setting-labels.js.map +1 -1
  8. package/dist/account/email-change-setting.js.map +1 -1
  9. package/dist/account/passkeys-setting.js.map +1 -1
  10. package/dist/account/sessions-setting.js.map +1 -1
  11. package/dist/admin/admin-action-confirm.js +1 -3
  12. package/dist/admin/admin-action-confirm.js.map +1 -1
  13. package/dist/admin/admin-action-log.js.map +1 -1
  14. package/dist/admin/admin-area-labels.js +4 -1
  15. package/dist/admin/admin-area-labels.js.map +1 -1
  16. package/dist/admin/admin-overview.js +19 -11
  17. package/dist/admin/admin-overview.js.map +1 -1
  18. package/dist/admin/admin-sections.js.map +1 -1
  19. package/dist/admin/admin-tool-links.d.ts +1 -1
  20. package/dist/admin/admin-tool-links.js +7 -1
  21. package/dist/admin/admin-tool-links.js.map +1 -1
  22. package/dist/admin/invitations-panel.js +2 -13
  23. package/dist/admin/invitations-panel.js.map +1 -1
  24. package/dist/admin/mail-test-card.js.map +1 -1
  25. package/dist/admin/reviewer-scope-editor.js +4 -11
  26. package/dist/admin/reviewer-scope-editor.js.map +1 -1
  27. package/dist/admin/role-select.js.map +1 -1
  28. package/dist/admin/user-roster.js +1 -9
  29. package/dist/admin/user-roster.js.map +1 -1
  30. package/dist/auth/accept-invitation.js.map +1 -1
  31. package/dist/auth/auth-errors.js.map +1 -1
  32. package/dist/auth/forgot-password-form.js +23 -31
  33. package/dist/auth/forgot-password-form.js.map +1 -1
  34. package/dist/auth/register-form.js.map +1 -1
  35. package/dist/auth/reset-password-form.js.map +1 -1
  36. package/dist/auth/sign-in-form.js +13 -5
  37. package/dist/auth/sign-in-form.js.map +1 -1
  38. package/dist/auth/verify-email.js +1 -8
  39. package/dist/auth/verify-email.js.map +1 -1
  40. package/dist/billing/billing-banner.js.map +1 -1
  41. package/dist/billing/billing-labels.js.map +1 -1
  42. package/dist/billing/checkout-processing.js.map +1 -1
  43. package/dist/billing/plan-card.js +91 -85
  44. package/dist/billing/plan-card.js.map +1 -1
  45. package/dist/billing/plan-limit-toast.js.map +1 -1
  46. package/dist/billing/plan-limit.js.map +1 -1
  47. package/dist/billing/plan-price.js.map +1 -1
  48. package/dist/billing/plan-switch.js.map +1 -1
  49. package/dist/billing/subscription-actions.js.map +1 -1
  50. package/dist/bin/eifi1-pay-page.mjs +6 -1
  51. package/dist/components/account-chips.js.map +1 -1
  52. package/dist/components/alert-banner.js +1 -5
  53. package/dist/components/alert-banner.js.map +1 -1
  54. package/dist/components/amount-input.js +29 -12
  55. package/dist/components/amount-input.js.map +1 -1
  56. package/dist/components/authed-image.js.map +1 -1
  57. package/dist/components/autocomplete.js.map +1 -1
  58. package/dist/components/bulk-action-bar.js +1 -4
  59. package/dist/components/bulk-action-bar.js.map +1 -1
  60. package/dist/components/button-group.js.map +1 -1
  61. package/dist/components/button-look.js.map +1 -1
  62. package/dist/components/button.js +1 -7
  63. package/dist/components/button.js.map +1 -1
  64. package/dist/components/calculator.js +5 -5
  65. package/dist/components/calculator.js.map +1 -1
  66. package/dist/components/calendar-heatmap.js +4 -10
  67. package/dist/components/calendar-heatmap.js.map +1 -1
  68. package/dist/components/card.js +1 -8
  69. package/dist/components/card.js.map +1 -1
  70. package/dist/components/chart-zoom-core.js +3 -17
  71. package/dist/components/chart-zoom-core.js.map +1 -1
  72. package/dist/components/chart.d.ts +1 -1
  73. package/dist/components/chart.js +4 -22
  74. package/dist/components/chart.js.map +1 -1
  75. package/dist/components/checkbox-group.js +1 -4
  76. package/dist/components/checkbox-group.js.map +1 -1
  77. package/dist/components/chip-input.js +4 -1
  78. package/dist/components/chip-input.js.map +1 -1
  79. package/dist/components/chip.js +10 -5
  80. package/dist/components/chip.js.map +1 -1
  81. package/dist/components/choice-card.js +58 -74
  82. package/dist/components/choice-card.js.map +1 -1
  83. package/dist/components/column-mapper-labels.js.map +1 -1
  84. package/dist/components/column-mapper.js +167 -227
  85. package/dist/components/column-mapper.js.map +1 -1
  86. package/dist/components/combobox-core.js +5 -9
  87. package/dist/components/combobox-core.js.map +1 -1
  88. package/dist/components/combobox.js +1 -4
  89. package/dist/components/combobox.js.map +1 -1
  90. package/dist/components/confirm-dialog.js.map +1 -1
  91. package/dist/components/copy-button.js.map +1 -1
  92. package/dist/components/country-select.d.ts +5 -4
  93. package/dist/components/country-select.js +3 -19
  94. package/dist/components/country-select.js.map +1 -1
  95. package/dist/components/currency-option-list.js +13 -11
  96. package/dist/components/currency-option-list.js.map +1 -1
  97. package/dist/components/currency-select.js +1 -6
  98. package/dist/components/currency-select.js.map +1 -1
  99. package/dist/components/danger-confirm.js.map +1 -1
  100. package/dist/components/data-table-cells.js.map +1 -1
  101. package/dist/components/data-table-column-settings.js.map +1 -1
  102. package/dist/components/data-table-filter-popover.js +2 -18
  103. package/dist/components/data-table-filter-popover.js.map +1 -1
  104. package/dist/components/data-table-filters.js.map +1 -1
  105. package/dist/components/data-table-fit.js +7 -5
  106. package/dist/components/data-table-fit.js.map +1 -1
  107. package/dist/components/data-table-groups.js +3 -1
  108. package/dist/components/data-table-groups.js.map +1 -1
  109. package/dist/components/data-table-head.js +49 -57
  110. package/dist/components/data-table-head.js.map +1 -1
  111. package/dist/components/data-table-mobile.js +31 -44
  112. package/dist/components/data-table-mobile.js.map +1 -1
  113. package/dist/components/data-table-pagination.js.map +1 -1
  114. package/dist/components/data-table-row-actions.js +19 -16
  115. package/dist/components/data-table-row-actions.js.map +1 -1
  116. package/dist/components/data-table-row.js.map +1 -1
  117. package/dist/components/data-table-shared.js.map +1 -1
  118. package/dist/components/data-table-sort.js.map +1 -1
  119. package/dist/components/data-table-virtual.js +4 -1
  120. package/dist/components/data-table-virtual.js.map +1 -1
  121. package/dist/components/data-table.d.ts +8 -0
  122. package/dist/components/data-table.js +30 -39
  123. package/dist/components/data-table.js.map +1 -1
  124. package/dist/components/date-picker.js +1 -6
  125. package/dist/components/date-picker.js.map +1 -1
  126. package/dist/components/date-range-picker.js.map +1 -1
  127. package/dist/components/description-list.js +10 -10
  128. package/dist/components/description-list.js.map +1 -1
  129. package/dist/components/dialog-frame.js +9 -2
  130. package/dist/components/dialog-frame.js.map +1 -1
  131. package/dist/components/disclosure.js +17 -27
  132. package/dist/components/disclosure.js.map +1 -1
  133. package/dist/components/dropdown.js +25 -35
  134. package/dist/components/dropdown.js.map +1 -1
  135. package/dist/components/empty-state.js.map +1 -1
  136. package/dist/components/entity-combobox.js.map +1 -1
  137. package/dist/components/error-boundary.js.map +1 -1
  138. package/dist/components/field-base.js +5 -5
  139. package/dist/components/field-base.js.map +1 -1
  140. package/dist/components/field-parts.d.ts +1 -1
  141. package/dist/components/field-parts.js +10 -14
  142. package/dist/components/field-parts.js.map +1 -1
  143. package/dist/components/field-sync.js +8 -10
  144. package/dist/components/field-sync.js.map +1 -1
  145. package/dist/components/file-button.js.map +1 -1
  146. package/dist/components/file-dropzone.js.map +1 -1
  147. package/dist/components/floating-panel.js +4 -1
  148. package/dist/components/floating-panel.js.map +1 -1
  149. package/dist/components/focus-ring.js.map +1 -1
  150. package/dist/components/form-actions.js.map +1 -1
  151. package/dist/components/grouped-picker.js +11 -12
  152. package/dist/components/grouped-picker.js.map +1 -1
  153. package/dist/components/hover-menu.js +1 -4
  154. package/dist/components/hover-menu.js.map +1 -1
  155. package/dist/components/iban-input-labels.js.map +1 -1
  156. package/dist/components/icon-button.js +4 -1
  157. package/dist/components/icon-button.js.map +1 -1
  158. package/dist/components/icon-picker.js +6 -10
  159. package/dist/components/icon-picker.js.map +1 -1
  160. package/dist/components/image-grid.js +41 -50
  161. package/dist/components/image-grid.js.map +1 -1
  162. package/dist/components/inline-edit-field.js.map +1 -1
  163. package/dist/components/inline-entity-combobox.js +6 -12
  164. package/dist/components/inline-entity-combobox.js.map +1 -1
  165. package/dist/components/input.js +14 -2
  166. package/dist/components/input.js.map +1 -1
  167. package/dist/components/jump-calendar.js +1 -7
  168. package/dist/components/jump-calendar.js.map +1 -1
  169. package/dist/components/legal-labels.js +8 -2
  170. package/dist/components/legal-labels.js.map +1 -1
  171. package/dist/components/legal-page.d.ts +18 -1
  172. package/dist/components/legal-page.js +27 -2
  173. package/dist/components/legal-page.js.map +1 -1
  174. package/dist/components/legal.d.ts +40 -5
  175. package/dist/components/legal.js +41 -5
  176. package/dist/components/legal.js.map +1 -1
  177. package/dist/components/lightbox.js.map +1 -1
  178. package/dist/components/line-items.js +26 -36
  179. package/dist/components/line-items.js.map +1 -1
  180. package/dist/components/list-labels.js +3 -1
  181. package/dist/components/list-labels.js.map +1 -1
  182. package/dist/components/list.d.ts +18 -1
  183. package/dist/components/list.js +54 -13
  184. package/dist/components/list.js.map +1 -1
  185. package/dist/components/measured-grid.js +29 -53
  186. package/dist/components/measured-grid.js.map +1 -1
  187. package/dist/components/menu-item.js.map +1 -1
  188. package/dist/components/mini-calendar.js +13 -31
  189. package/dist/components/mini-calendar.js.map +1 -1
  190. package/dist/components/money-field.js.map +1 -1
  191. package/dist/components/month-picker.js +10 -21
  192. package/dist/components/month-picker.js.map +1 -1
  193. package/dist/components/multi-entity-combobox.js +3 -16
  194. package/dist/components/multi-entity-combobox.js.map +1 -1
  195. package/dist/components/multi-select.js.map +1 -1
  196. package/dist/components/nav-pills.js +10 -1
  197. package/dist/components/nav-pills.js.map +1 -1
  198. package/dist/components/number-field.js.map +1 -1
  199. package/dist/components/number-input.js +1 -7
  200. package/dist/components/number-input.js.map +1 -1
  201. package/dist/components/numpad-sheet.js +1 -4
  202. package/dist/components/numpad-sheet.js.map +1 -1
  203. package/dist/components/one-time-code-input.js +1 -7
  204. package/dist/components/one-time-code-input.js.map +1 -1
  205. package/dist/components/page-contents.js +13 -22
  206. package/dist/components/page-contents.js.map +1 -1
  207. package/dist/components/page-header.js +1 -11
  208. package/dist/components/page-header.js.map +1 -1
  209. package/dist/components/password-strength-labels.js.map +1 -1
  210. package/dist/components/password-strength.js +2 -8
  211. package/dist/components/password-strength.js.map +1 -1
  212. package/dist/components/phone-input.js +42 -57
  213. package/dist/components/phone-input.js.map +1 -1
  214. package/dist/components/picker-sheet.js.map +1 -1
  215. package/dist/components/pie-chart.js +1 -8
  216. package/dist/components/pie-chart.js.map +1 -1
  217. package/dist/components/popover.js.map +1 -1
  218. package/dist/components/progress-bar.js +16 -9
  219. package/dist/components/progress-bar.js.map +1 -1
  220. package/dist/components/qr-code.js +1 -8
  221. package/dist/components/qr-code.js.map +1 -1
  222. package/dist/components/row-actions.js +5 -1
  223. package/dist/components/row-actions.js.map +1 -1
  224. package/dist/components/search-field.js.map +1 -1
  225. package/dist/components/select.js +28 -38
  226. package/dist/components/select.js.map +1 -1
  227. package/dist/components/separator.js +1 -5
  228. package/dist/components/separator.js.map +1 -1
  229. package/dist/components/series-chart-axes.js +4 -1
  230. package/dist/components/series-chart-axes.js.map +1 -1
  231. package/dist/components/series-chart-budget.js.map +1 -1
  232. package/dist/components/series-chart-ticks.js.map +1 -1
  233. package/dist/components/series-chart.js +29 -45
  234. package/dist/components/series-chart.js.map +1 -1
  235. package/dist/components/settings-fields.js +21 -17
  236. package/dist/components/settings-fields.js.map +1 -1
  237. package/dist/components/share-card.js +4 -9
  238. package/dist/components/share-card.js.map +1 -1
  239. package/dist/components/sign-chip.js.map +1 -1
  240. package/dist/components/signature-pad.js +4 -13
  241. package/dist/components/signature-pad.js.map +1 -1
  242. package/dist/components/signed-amount.js +12 -5
  243. package/dist/components/signed-amount.js.map +1 -1
  244. package/dist/components/skeleton.js +2 -17
  245. package/dist/components/skeleton.js.map +1 -1
  246. package/dist/components/slider.js +13 -36
  247. package/dist/components/slider.js.map +1 -1
  248. package/dist/components/sparkline.js.map +1 -1
  249. package/dist/components/stat-tile.js +14 -10
  250. package/dist/components/stat-tile.js.map +1 -1
  251. package/dist/components/swatch-picker.js +14 -4
  252. package/dist/components/swatch-picker.js.map +1 -1
  253. package/dist/components/swipeable-row.d.ts +1 -1
  254. package/dist/components/swipeable-row.js +1 -8
  255. package/dist/components/swipeable-row.js.map +1 -1
  256. package/dist/components/table-skeleton.js +4 -1
  257. package/dist/components/table-skeleton.js.map +1 -1
  258. package/dist/components/table.js +12 -9
  259. package/dist/components/table.js.map +1 -1
  260. package/dist/components/tabs.js +5 -24
  261. package/dist/components/tabs.js.map +1 -1
  262. package/dist/components/text-link.js.map +1 -1
  263. package/dist/components/text.js.map +1 -1
  264. package/dist/components/tile-radio.js +1 -7
  265. package/dist/components/tile-radio.js.map +1 -1
  266. package/dist/components/time-input.js.map +1 -1
  267. package/dist/components/toast.js.map +1 -1
  268. package/dist/components/toggle-group.js +6 -1
  269. package/dist/components/toggle-group.js.map +1 -1
  270. package/dist/components/toggle-legend.js +2 -11
  271. package/dist/components/toggle-legend.js.map +1 -1
  272. package/dist/components/tooltip.js +2 -22
  273. package/dist/components/tooltip.js.map +1 -1
  274. package/dist/components/translation-review-editor.js +1 -4
  275. package/dist/components/translation-review-editor.js.map +1 -1
  276. package/dist/components/translation-review-parts.d.ts +1 -1
  277. package/dist/components/translation-review-parts.js +7 -1
  278. package/dist/components/translation-review-parts.js.map +1 -1
  279. package/dist/components/translation-review.js +18 -7
  280. package/dist/components/translation-review.js.map +1 -1
  281. package/dist/components/tree-view.js +14 -10
  282. package/dist/components/tree-view.js.map +1 -1
  283. package/dist/components/treemap.js +1 -13
  284. package/dist/components/treemap.js.map +1 -1
  285. package/dist/components/trigger-aria.js.map +1 -1
  286. package/dist/components/use-table-state.js +14 -11
  287. package/dist/components/use-table-state.js.map +1 -1
  288. package/dist/components/user-avatar.js +3 -26
  289. package/dist/components/user-avatar.js.map +1 -1
  290. package/dist/demo/demo-start.js +8 -4
  291. package/dist/demo/demo-start.js.map +1 -1
  292. package/dist/eslint/index.js +1 -1
  293. package/dist/feedback/feedback-attachment-labels.js.map +1 -1
  294. package/dist/feedback/feedback-attachment.js +1 -8
  295. package/dist/feedback/feedback-attachment.js.map +1 -1
  296. package/dist/feedback/feedback-capture.d.ts +1 -1
  297. package/dist/feedback/feedback-capture.js +3 -1
  298. package/dist/feedback/feedback-capture.js.map +1 -1
  299. package/dist/feedback/feedback-comment-thread.js +27 -13
  300. package/dist/feedback/feedback-comment-thread.js.map +1 -1
  301. package/dist/feedback/feedback-context.js +1 -4
  302. package/dist/feedback/feedback-context.js.map +1 -1
  303. package/dist/feedback/feedback-dialog.js.map +1 -1
  304. package/dist/feedback/feedback-inbox.js.map +1 -1
  305. package/dist/feedback/feedback-public-list.js +7 -1
  306. package/dist/feedback/feedback-public-list.js.map +1 -1
  307. package/dist/feedback/feedback-record.js +1 -5
  308. package/dist/feedback/feedback-record.js.map +1 -1
  309. package/dist/feedback/feedback-replies.js +25 -10
  310. package/dist/feedback/feedback-replies.js.map +1 -1
  311. package/dist/feedback/feedback-row-detail.js.map +1 -1
  312. package/dist/feedback/feedback-status-undo.js.map +1 -1
  313. package/dist/feedback/feedback-submit-dialog.js.map +1 -1
  314. package/dist/feedback/feedback-table.d.ts +3 -1
  315. package/dist/feedback/feedback-table.js +22 -5
  316. package/dist/feedback/feedback-table.js.map +1 -1
  317. package/dist/feedback/feedback-thread.js +2 -13
  318. package/dist/feedback/feedback-thread.js.map +1 -1
  319. package/dist/feedback/feedback-vocabulary.js.map +1 -1
  320. package/dist/feedback/feedback-vote-button.js.map +1 -1
  321. package/dist/hooks/use-anchored-rect.js.map +1 -1
  322. package/dist/hooks/use-document-title.js.map +1 -1
  323. package/dist/hooks/use-file-drop.js.map +1 -1
  324. package/dist/hooks/use-file-picker-labels.js.map +1 -1
  325. package/dist/hooks/use-file-picker.js.map +1 -1
  326. package/dist/hooks/use-focus-trap.js.map +1 -1
  327. package/dist/hooks/use-overlay-history.js +10 -5
  328. package/dist/hooks/use-overlay-history.js.map +1 -1
  329. package/dist/hooks/use-row-swipe.d.ts +1 -1
  330. package/dist/hooks/use-row-swipe.js +1 -6
  331. package/dist/hooks/use-row-swipe.js.map +1 -1
  332. package/dist/i18n/defaults.js +1 -4
  333. package/dist/i18n/defaults.js.map +1 -1
  334. package/dist/i18n/german.js +15 -4
  335. package/dist/i18n/german.js.map +1 -1
  336. package/dist/i18n/kit-labels.js +2 -8
  337. package/dist/i18n/kit-labels.js.map +1 -1
  338. package/dist/i18n/languages.js.map +1 -1
  339. package/dist/i18n/locales/en.js +19 -4
  340. package/dist/i18n/locales/en.js.map +2 -2
  341. package/dist/i18n/locales/es.js +15 -4
  342. package/dist/i18n/locales/es.js.map +1 -1
  343. package/dist/i18n/locales/fr.js +15 -4
  344. package/dist/i18n/locales/fr.js.map +1 -1
  345. package/dist/i18n/locales/hu.js +15 -4
  346. package/dist/i18n/locales/hu.js.map +1 -1
  347. package/dist/i18n/locales/it.js +15 -4
  348. package/dist/i18n/locales/it.js.map +1 -1
  349. package/dist/i18n/locales/zh.js +11 -3
  350. package/dist/i18n/locales/zh.js.map +1 -1
  351. package/dist/i18n/plurals.js.map +1 -1
  352. package/dist/i18n/review.js +15 -7
  353. package/dist/i18n/review.js.map +1 -1
  354. package/dist/i18n/shared-labels.js.map +1 -1
  355. package/dist/index.d.ts +17 -17
  356. package/dist/index.js +40 -23
  357. package/dist/index.js.map +1 -1
  358. package/dist/landing/access.js +1 -4
  359. package/dist/landing/access.js.map +1 -1
  360. package/dist/landing/landing-sections.js +35 -38
  361. package/dist/landing/landing-sections.js.map +1 -1
  362. package/dist/landing/page-seo.js.map +1 -1
  363. package/dist/lib/calc.js.map +1 -1
  364. package/dist/lib/clipboard-minus.js.map +1 -1
  365. package/dist/lib/dates.js.map +1 -1
  366. package/dist/lib/decimal-marks.js.map +1 -1
  367. package/dist/lib/device-settings.js +2 -1
  368. package/dist/lib/device-settings.js.map +1 -1
  369. package/dist/lib/direction.js.map +1 -1
  370. package/dist/lib/iban.js.map +1 -1
  371. package/dist/lib/logger.js +1 -5
  372. package/dist/lib/logger.js.map +1 -1
  373. package/dist/lib/person-name.js.map +1 -1
  374. package/dist/lib/phone.js.map +1 -1
  375. package/dist/lib/qr-encode.js +344 -8
  376. package/dist/lib/qr-encode.js.map +1 -1
  377. package/dist/lib/session-refresh.js.map +1 -1
  378. package/dist/lib/table-text.js +15 -3
  379. package/dist/lib/table-text.js.map +1 -1
  380. package/dist/lib/text-match.d.ts +20 -0
  381. package/dist/lib/text-match.js +11 -1
  382. package/dist/lib/text-match.js.map +1 -1
  383. package/dist/lib/translation-review.js.map +1 -1
  384. package/dist/notifications/notification-inbox.js +63 -44
  385. package/dist/notifications/notification-inbox.js.map +1 -1
  386. package/dist/notifications/notification-parts.js.map +1 -1
  387. package/dist/notifications/notification-types-setting.js.map +1 -1
  388. package/dist/notifications/notifications-labels.js.map +1 -1
  389. package/dist/notifications/push-device-setting.js +6 -1
  390. package/dist/notifications/push-device-setting.js.map +1 -1
  391. package/dist/notifications/push-devices-setting.js.map +1 -1
  392. package/dist/notifications/push-worker.js.map +1 -1
  393. package/dist/notifications/quiet-hours-setting.js.map +1 -1
  394. package/dist/notifications/use-push-device.js +4 -1
  395. package/dist/notifications/use-push-device.js.map +1 -1
  396. package/dist/offline/pwa-update-prompt.d.ts +1 -1
  397. package/dist/offline/pwa-update-prompt.js +7 -1
  398. package/dist/offline/pwa-update-prompt.js.map +1 -1
  399. package/dist/offline/query-persistence.js.map +1 -1
  400. package/dist/offline.d.ts +1 -1
  401. package/dist/offline.js +6 -1
  402. package/dist/offline.js.map +1 -1
  403. package/dist/pay/pay.css +8 -1
  404. package/dist/rhf/fields.d.ts +1 -2
  405. package/dist/rhf/fields.js +1 -10
  406. package/dist/rhf/fields.js.map +1 -1
  407. package/dist/rhf/form-core.js +1 -4
  408. package/dist/rhf/form-core.js.map +1 -1
  409. package/dist/rhf/line-items.js.map +1 -1
  410. package/dist/rhf/use-rhf-wizard-step.js.map +1 -1
  411. package/dist/search/command-palette.js +71 -62
  412. package/dist/search/command-palette.js.map +1 -1
  413. package/dist/search/global-search.js +34 -5
  414. package/dist/search/global-search.js.map +1 -1
  415. package/dist/search/search-index.js.map +1 -1
  416. package/dist/settings/device-reset-setting.js +1 -6
  417. package/dist/settings/device-reset-setting.js.map +1 -1
  418. package/dist/settings/settings-heading.js.map +1 -1
  419. package/dist/settings/settings-layout.js +9 -20
  420. package/dist/settings/settings-layout.js.map +1 -1
  421. package/dist/settings/use-settings-focus.d.ts +7 -0
  422. package/dist/settings/use-settings-focus.js +2 -0
  423. package/dist/settings/use-settings-focus.js.map +1 -1
  424. package/dist/settings/use-settings-route.js.map +1 -1
  425. package/dist/shell/app-shell.js +62 -31
  426. package/dist/shell/app-shell.js.map +1 -1
  427. package/dist/shell/auth-layout.js +1 -10
  428. package/dist/shell/auth-layout.js.map +1 -1
  429. package/dist/shell/dev-menu.d.ts +5 -0
  430. package/dist/shell/dev-menu.js +14 -7
  431. package/dist/shell/dev-menu.js.map +1 -1
  432. package/dist/shell/option-switcher-menu.js.map +1 -1
  433. package/dist/shell/sync-status-indicator.js +7 -6
  434. package/dist/shell/sync-status-indicator.js.map +1 -1
  435. package/dist/shell/top-bar-parts-labels.js.map +1 -1
  436. package/dist/shell/topbar-action-menu.d.ts +14 -1
  437. package/dist/shell/topbar-action-menu.js +85 -34
  438. package/dist/shell/topbar-action-menu.js.map +1 -1
  439. package/dist/shell/topbar-controls.js +1 -10
  440. package/dist/shell/topbar-controls.js.map +1 -1
  441. package/dist/shell/tour-menu.js +9 -1
  442. package/dist/shell/tour-menu.js.map +1 -1
  443. package/dist/shell.d.ts +1 -1
  444. package/dist/shell.js +2 -1
  445. package/dist/shell.js.map +1 -1
  446. package/dist/sync/db.js.map +1 -1
  447. package/dist/sync/engine.js +24 -6
  448. package/dist/sync/engine.js.map +1 -1
  449. package/dist/sync.d.ts +2 -2
  450. package/dist/sync.js +7 -1
  451. package/dist/sync.js.map +1 -1
  452. package/dist/testing/css-variables.js.map +1 -1
  453. package/dist/testing/index.d.ts +1 -1
  454. package/dist/testing/index.js +1 -3
  455. package/dist/testing/index.js.map +1 -1
  456. package/dist/testing/react.js.map +1 -1
  457. package/dist/testing/viewport.js +7 -4
  458. package/dist/testing/viewport.js.map +1 -1
  459. package/dist/theme/contrast-tokens.js +13 -2
  460. package/dist/theme/contrast-tokens.js.map +1 -1
  461. package/dist/theme/palette-derive.d.ts +1 -1
  462. package/dist/theme/palette-derive.js +18 -29
  463. package/dist/theme/palette-derive.js.map +1 -1
  464. package/dist/theme/palette-presets.js.map +1 -1
  465. package/dist/theme/palette-store.js.map +1 -1
  466. package/dist/theme/text-size.js.map +1 -1
  467. package/dist/theme/theme-store.js +1 -3
  468. package/dist/theme/theme-store.js.map +1 -1
  469. package/dist/tour/tour-catalogue.js.map +1 -1
  470. package/dist/tour/tour-dock.js +5 -1
  471. package/dist/tour/tour-dock.js.map +1 -1
  472. package/dist/tour/tour-engine.js +1 -4
  473. package/dist/tour/tour-engine.js.map +1 -1
  474. package/dist/tour/tour.js +21 -12
  475. package/dist/tour/tour.js.map +1 -1
  476. package/dist/tour/use-tour-progress.js.map +1 -1
  477. package/dist/tour/use-tour-url-sync.js.map +1 -1
  478. package/dist/wizard/stepper-nav.js +3 -24
  479. package/dist/wizard/stepper-nav.js.map +1 -1
  480. package/dist/wizard/types.js.map +1 -1
  481. package/dist/wizard/use-wizard.js +24 -28
  482. package/dist/wizard/use-wizard.js.map +1 -1
  483. package/dist/wizard/validation.js.map +1 -1
  484. package/dist/wizard/wizard-context.js.map +1 -1
  485. package/dist/wizard/wizard-step.d.ts +1 -1
  486. package/dist/wizard/wizard-step.js +1 -8
  487. package/dist/wizard/wizard-step.js.map +1 -1
  488. package/dist/wizard/wizard-summary.js.map +1 -1
  489. package/package.json +5 -2
  490. package/src/account/account-settings-labels.ts +1 -2
  491. package/src/account/account-settings.tsx +11 -17
  492. package/src/account/delete-account-setting.tsx +1 -6
  493. package/src/account/email-change-setting-labels.ts +1 -2
  494. package/src/account/email-change-setting.tsx +3 -1
  495. package/src/account/passkeys-setting.tsx +1 -3
  496. package/src/account/sessions-setting.tsx +4 -2
  497. package/src/admin/admin-action-confirm.tsx +4 -7
  498. package/src/admin/admin-action-log.tsx +29 -29
  499. package/src/admin/admin-area-labels.ts +4 -1
  500. package/src/admin/admin-overview.tsx +8 -2
  501. package/src/admin/admin-sections.tsx +3 -1
  502. package/src/admin/admin-tool-links.tsx +7 -1
  503. package/src/admin/invitations-panel.tsx +5 -11
  504. package/src/admin/mail-test-card.tsx +1 -2
  505. package/src/admin/reviewer-scope-editor.tsx +4 -9
  506. package/src/admin/role-select.tsx +4 -2
  507. package/src/admin/user-roster.tsx +2 -11
  508. package/src/auth/accept-invitation.tsx +1 -7
  509. package/src/auth/auth-errors.ts +1 -6
  510. package/src/auth/forgot-password-form.tsx +2 -7
  511. package/src/auth/register-form.tsx +7 -8
  512. package/src/auth/reset-password-form.tsx +2 -5
  513. package/src/auth/sign-in-form.tsx +15 -14
  514. package/src/auth/verify-email.tsx +2 -7
  515. package/src/billing/billing-banner.tsx +3 -7
  516. package/src/billing/billing-labels.ts +6 -4
  517. package/src/billing/checkout-processing.ts +0 -1
  518. package/src/billing/plan-card.tsx +9 -8
  519. package/src/billing/plan-limit-toast.tsx +1 -2
  520. package/src/billing/plan-limit.ts +1 -2
  521. package/src/billing/plan-price.ts +1 -4
  522. package/src/billing/plan-switch.tsx +1 -2
  523. package/src/billing/subscription-actions.tsx +5 -2
  524. package/src/components/account-chips.tsx +1 -7
  525. package/src/components/alert-banner.tsx +2 -8
  526. package/src/components/amount-input.tsx +206 -178
  527. package/src/components/authed-image.tsx +7 -4
  528. package/src/components/autocomplete.tsx +7 -9
  529. package/src/components/bulk-action-bar.tsx +2 -7
  530. package/src/components/button-group.tsx +1 -2
  531. package/src/components/button-look.ts +6 -17
  532. package/src/components/button.tsx +1 -4
  533. package/src/components/calculator.tsx +6 -9
  534. package/src/components/calendar-heatmap.tsx +5 -10
  535. package/src/components/card.tsx +9 -13
  536. package/src/components/chart-zoom-core.tsx +6 -33
  537. package/src/components/chart.tsx +8 -31
  538. package/src/components/checkbox-group.tsx +1 -4
  539. package/src/components/chip-input.tsx +6 -6
  540. package/src/components/chip.tsx +14 -26
  541. package/src/components/choice-card.tsx +4 -18
  542. package/src/components/column-mapper-labels.ts +3 -10
  543. package/src/components/column-mapper.tsx +33 -122
  544. package/src/components/combobox-core.tsx +17 -47
  545. package/src/components/combobox.tsx +2 -6
  546. package/src/components/confirm-dialog.tsx +1 -3
  547. package/src/components/copy-button.tsx +2 -4
  548. package/src/components/country-select.tsx +9 -21
  549. package/src/components/currency-option-list.tsx +16 -18
  550. package/src/components/currency-select.tsx +2 -7
  551. package/src/components/danger-confirm.tsx +8 -9
  552. package/src/components/data-table-cells.tsx +1 -2
  553. package/src/components/data-table-column-settings.tsx +1 -3
  554. package/src/components/data-table-filter-popover.tsx +5 -19
  555. package/src/components/data-table-filters.ts +1 -6
  556. package/src/components/data-table-fit.ts +7 -7
  557. package/src/components/data-table-groups.tsx +3 -1
  558. package/src/components/data-table-head.tsx +32 -46
  559. package/src/components/data-table-mobile.tsx +99 -108
  560. package/src/components/data-table-pagination.tsx +2 -6
  561. package/src/components/data-table-row-actions.tsx +25 -24
  562. package/src/components/data-table-row.tsx +16 -17
  563. package/src/components/data-table-shared.tsx +10 -16
  564. package/src/components/data-table-sort.ts +1 -2
  565. package/src/components/data-table-virtual.ts +9 -4
  566. package/src/components/data-table.tsx +497 -501
  567. package/src/components/date-picker.tsx +1 -7
  568. package/src/components/date-range-picker.tsx +3 -13
  569. package/src/components/description-list.tsx +7 -13
  570. package/src/components/dialog-frame.tsx +12 -3
  571. package/src/components/disclosure.tsx +8 -12
  572. package/src/components/dropdown.tsx +3 -11
  573. package/src/components/empty-state.tsx +1 -3
  574. package/src/components/entity-combobox.tsx +2 -6
  575. package/src/components/error-boundary.tsx +7 -7
  576. package/src/components/field-base.tsx +8 -14
  577. package/src/components/field-parts.tsx +12 -23
  578. package/src/components/field-sync.tsx +7 -11
  579. package/src/components/file-button.tsx +7 -3
  580. package/src/components/file-dropzone.tsx +7 -12
  581. package/src/components/floating-panel.tsx +44 -51
  582. package/src/components/focus-ring.ts +1 -2
  583. package/src/components/form-actions.tsx +1 -4
  584. package/src/components/grouped-picker.tsx +21 -16
  585. package/src/components/hover-menu.tsx +1 -4
  586. package/src/components/iban-input-labels.ts +1 -2
  587. package/src/components/icon-button.tsx +46 -48
  588. package/src/components/icon-picker.tsx +9 -20
  589. package/src/components/image-grid.tsx +1 -6
  590. package/src/components/inline-edit-field.tsx +1 -5
  591. package/src/components/inline-entity-combobox.tsx +11 -25
  592. package/src/components/input.tsx +14 -2
  593. package/src/components/jump-calendar.tsx +2 -6
  594. package/src/components/legal-labels.ts +8 -3
  595. package/src/components/legal-page.tsx +56 -6
  596. package/src/components/legal.tsx +83 -10
  597. package/src/components/lightbox.tsx +1 -3
  598. package/src/components/line-items.tsx +7 -15
  599. package/src/components/list-labels.ts +2 -0
  600. package/src/components/list.tsx +81 -27
  601. package/src/components/measured-grid.tsx +8 -30
  602. package/src/components/menu-item.tsx +1 -3
  603. package/src/components/mini-calendar.tsx +129 -159
  604. package/src/components/money-field.tsx +4 -2
  605. package/src/components/month-picker.tsx +14 -21
  606. package/src/components/multi-entity-combobox.tsx +6 -26
  607. package/src/components/multi-select.tsx +5 -9
  608. package/src/components/nav-pills.tsx +10 -2
  609. package/src/components/number-field.tsx +18 -15
  610. package/src/components/number-input.tsx +3 -19
  611. package/src/components/numpad-sheet.tsx +9 -13
  612. package/src/components/one-time-code-input.tsx +6 -11
  613. package/src/components/page-contents.tsx +2 -9
  614. package/src/components/page-header.tsx +5 -10
  615. package/src/components/password-strength-labels.ts +1 -2
  616. package/src/components/password-strength.tsx +6 -9
  617. package/src/components/phone-input.tsx +2 -13
  618. package/src/components/picker-sheet.tsx +2 -5
  619. package/src/components/pie-chart.tsx +6 -12
  620. package/src/components/popover.tsx +2 -1
  621. package/src/components/progress-bar.tsx +16 -25
  622. package/src/components/qr-code.tsx +2 -9
  623. package/src/components/row-actions.tsx +9 -1
  624. package/src/components/search-field.tsx +4 -5
  625. package/src/components/select.tsx +2 -10
  626. package/src/components/separator.tsx +1 -5
  627. package/src/components/series-chart-axes.ts +5 -5
  628. package/src/components/series-chart-budget.ts +1 -4
  629. package/src/components/series-chart-ticks.ts +1 -4
  630. package/src/components/series-chart.tsx +43 -83
  631. package/src/components/settings-fields.tsx +20 -15
  632. package/src/components/share-card.tsx +6 -14
  633. package/src/components/sign-chip.tsx +16 -16
  634. package/src/components/signature-pad.tsx +21 -13
  635. package/src/components/signed-amount.tsx +6 -14
  636. package/src/components/skeleton.tsx +2 -13
  637. package/src/components/slider.tsx +9 -20
  638. package/src/components/sparkline.tsx +2 -13
  639. package/src/components/stat-tile.tsx +13 -24
  640. package/src/components/swatch-picker.tsx +8 -3
  641. package/src/components/swipeable-row.tsx +2 -11
  642. package/src/components/table-skeleton.tsx +4 -1
  643. package/src/components/table.tsx +12 -8
  644. package/src/components/tabs.tsx +24 -39
  645. package/src/components/text-link.tsx +6 -6
  646. package/src/components/text.tsx +1 -2
  647. package/src/components/tile-radio.tsx +6 -19
  648. package/src/components/time-input.tsx +4 -5
  649. package/src/components/toast.tsx +3 -12
  650. package/src/components/toggle-group.tsx +9 -9
  651. package/src/components/toggle-legend.tsx +4 -16
  652. package/src/components/tooltip.tsx +9 -48
  653. package/src/components/translation-review-editor.tsx +4 -4
  654. package/src/components/translation-review-parts.tsx +7 -1
  655. package/src/components/translation-review.tsx +15 -16
  656. package/src/components/tree-view.tsx +17 -20
  657. package/src/components/treemap.tsx +3 -15
  658. package/src/components/trigger-aria.ts +1 -4
  659. package/src/components/use-table-state.ts +16 -17
  660. package/src/components/user-avatar.tsx +4 -21
  661. package/src/demo/demo-start.tsx +8 -4
  662. package/src/feedback/feedback-attachment-labels.ts +2 -1
  663. package/src/feedback/feedback-attachment.tsx +16 -29
  664. package/src/feedback/feedback-capture.ts +3 -1
  665. package/src/feedback/feedback-comment-thread.tsx +9 -6
  666. package/src/feedback/feedback-context.tsx +1 -4
  667. package/src/feedback/feedback-dialog.tsx +3 -4
  668. package/src/feedback/feedback-inbox.tsx +3 -9
  669. package/src/feedback/feedback-public-list.tsx +7 -1
  670. package/src/feedback/feedback-record.ts +6 -14
  671. package/src/feedback/feedback-replies.tsx +10 -5
  672. package/src/feedback/feedback-row-detail.tsx +2 -5
  673. package/src/feedback/feedback-status-undo.ts +1 -4
  674. package/src/feedback/feedback-submit-dialog.tsx +4 -4
  675. package/src/feedback/feedback-table.tsx +29 -9
  676. package/src/feedback/feedback-thread.tsx +4 -16
  677. package/src/feedback/feedback-vocabulary.ts +72 -82
  678. package/src/feedback/feedback-vote-button.tsx +9 -8
  679. package/src/hooks/use-anchored-rect.ts +1 -4
  680. package/src/hooks/use-document-title.ts +3 -1
  681. package/src/hooks/use-file-drop.ts +1 -3
  682. package/src/hooks/use-file-picker-labels.ts +1 -2
  683. package/src/hooks/use-file-picker.tsx +1 -5
  684. package/src/hooks/use-focus-trap.ts +1 -2
  685. package/src/hooks/use-overlay-history.ts +10 -5
  686. package/src/hooks/use-row-swipe.ts +1 -6
  687. package/src/i18n/defaults.ts +1 -4
  688. package/src/i18n/german.ts +42 -50
  689. package/src/i18n/kit-labels.tsx +5 -20
  690. package/src/i18n/languages.ts +2 -8
  691. package/src/i18n/locales/en.ts +15 -31
  692. package/src/i18n/locales/es.ts +44 -50
  693. package/src/i18n/locales/fr.ts +43 -52
  694. package/src/i18n/locales/hu.ts +35 -38
  695. package/src/i18n/locales/it.ts +44 -53
  696. package/src/i18n/locales/zh.ts +23 -33
  697. package/src/i18n/plurals.ts +4 -1
  698. package/src/i18n/review.ts +20 -7
  699. package/src/i18n/shared-labels.ts +1 -2
  700. package/src/index.ts +107 -32
  701. package/src/landing/access.ts +2 -7
  702. package/src/landing/landing-sections.tsx +14 -15
  703. package/src/landing/page-seo.ts +1 -3
  704. package/src/lib/calc.ts +4 -1
  705. package/src/lib/clipboard-minus.ts +4 -1
  706. package/src/lib/dates.ts +2 -10
  707. package/src/lib/decimal-marks.ts +1 -3
  708. package/src/lib/device-settings.ts +2 -1
  709. package/src/lib/direction.ts +1 -3
  710. package/src/lib/iban.ts +89 -9
  711. package/src/lib/logger.ts +1 -5
  712. package/src/lib/person-name.ts +1 -3
  713. package/src/lib/phone.ts +5 -8
  714. package/src/lib/qr-encode.ts +32 -8
  715. package/src/lib/session-refresh.ts +5 -6
  716. package/src/lib/table-text.ts +24 -6
  717. package/src/lib/text-match.ts +43 -1
  718. package/src/lib/translation-review.ts +1 -3
  719. package/src/notifications/notification-inbox.tsx +17 -4
  720. package/src/notifications/notification-parts.tsx +1 -2
  721. package/src/notifications/notification-types-setting.tsx +19 -18
  722. package/src/notifications/notifications-labels.ts +1 -2
  723. package/src/notifications/push-device-setting.tsx +7 -1
  724. package/src/notifications/push-devices-setting.tsx +7 -3
  725. package/src/notifications/push-worker.ts +10 -2
  726. package/src/notifications/quiet-hours-setting.tsx +4 -1
  727. package/src/notifications/use-push-device.ts +4 -1
  728. package/src/offline/pwa-update-prompt.tsx +7 -1
  729. package/src/offline/query-persistence.ts +9 -6
  730. package/src/offline.ts +6 -1
  731. package/src/rhf/fields.tsx +50 -19
  732. package/src/rhf/form-core.tsx +2 -6
  733. package/src/rhf/line-items.tsx +5 -7
  734. package/src/rhf/use-rhf-wizard-step.ts +3 -12
  735. package/src/search/command-palette.tsx +15 -23
  736. package/src/search/global-search.tsx +38 -8
  737. package/src/search/search-index.ts +5 -1
  738. package/src/settings/device-reset-setting.tsx +5 -8
  739. package/src/settings/settings-heading.tsx +3 -1
  740. package/src/settings/settings-layout.tsx +6 -13
  741. package/src/settings/use-settings-focus.ts +8 -0
  742. package/src/settings/use-settings-route.ts +4 -3
  743. package/src/shell/app-shell.tsx +48 -24
  744. package/src/shell/auth-layout.tsx +1 -6
  745. package/src/shell/dev-menu.tsx +18 -6
  746. package/src/shell/option-switcher-menu.tsx +1 -3
  747. package/src/shell/sync-status-indicator.tsx +14 -12
  748. package/src/shell/top-bar-parts-labels.ts +1 -3
  749. package/src/shell/topbar-action-menu.tsx +130 -45
  750. package/src/shell/topbar-controls.tsx +5 -19
  751. package/src/shell/tour-menu.tsx +9 -1
  752. package/src/shell.ts +1 -1
  753. package/src/sync/db.ts +5 -1
  754. package/src/sync/engine.ts +44 -12
  755. package/src/sync.ts +16 -2
  756. package/src/testing/css-variables.ts +1 -2
  757. package/src/testing/index.ts +1 -6
  758. package/src/testing/react.ts +1 -2
  759. package/src/testing/viewport.ts +9 -9
  760. package/src/theme/contrast-tokens.ts +13 -2
  761. package/src/theme/palette-derive.ts +24 -50
  762. package/src/theme/palette-presets.ts +0 -1
  763. package/src/theme/palette-store.ts +1 -4
  764. package/src/theme/text-size.ts +1 -1
  765. package/src/theme/theme-store.ts +2 -6
  766. package/src/tour/tour-catalogue.tsx +4 -2
  767. package/src/tour/tour-dock.ts +5 -1
  768. package/src/tour/tour-engine.ts +2 -6
  769. package/src/tour/tour.tsx +28 -12
  770. package/src/tour/use-tour-progress.ts +8 -2
  771. package/src/tour/use-tour-url-sync.ts +3 -1
  772. package/src/vite-env.d.ts +1 -4
  773. package/src/wizard/stepper-nav.tsx +10 -35
  774. package/src/wizard/types.ts +1 -4
  775. package/src/wizard/use-wizard.ts +28 -43
  776. package/src/wizard/validation.ts +1 -5
  777. package/src/wizard/wizard-context.tsx +2 -6
  778. package/src/wizard/wizard-step.tsx +1 -8
  779. package/src/wizard/wizard-summary.tsx +2 -6
  780. package/tokens.css +393 -152
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/icon-picker.tsx"],"sourcesContent":["import { useId, useMemo, useState } from \"react\";\nimport type { ComponentPropsWithRef, ComponentType, ReactNode } from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { DEFAULT_COMMON_LABELS, useKitLabels, useKitLocale, type LabelOverride } from \"../i18n/kit-labels\";\nimport { SearchField } from \"./search-field\";\nimport { TILE_SIZE, TileRadioGroup } from \"./tile-radio\";\nimport type { TileItem, TileSize } from \"./tile-radio\";\nimport { hasContent } from \"../lib/presence\";\nimport { foldForSearch } from \"../lib/text-match\";\nimport { LabelStrip, useFieldMessages, type FieldHelpProps } from \"./field-parts\";\nimport { DEFAULT_ICON_PICKER_LABELS } from \"./icon-picker-labels\";\n\nexport interface IconPickerLabels {\n /** The \"no icon\" tile's name, when `allowNone` is set. */\n none: string;\n /** Describes the group while `mixed` is set — a bulk edit over rows that disagree. */\n mixed: string;\n /** The search box's accessible name and placeholder, when `searchable` is set. */\n search: string;\n /** Shown in place of the tiles when the search matches nothing. */\n noResults: string;\n /** Announced as the search narrows the grid. Receives the number of matches. */\n resultCount: (count: number) => string;\n}\n\n// Defined in ./icon-picker-labels, which holds no component code (0.34, KS U8).\nexport { DEFAULT_ICON_PICKER_LABELS };\n\nexport interface IconOption<T extends string> {\n value: T;\n /** A Lucide icon, or any component that takes a `className`. Rendered `aria-hidden`\n * — the tile's name is `label`. */\n icon: ComponentType<{ className?: string; \"aria-hidden\"?: boolean }>;\n /** What the symbol IS, in the user's language (\"Game controller\") — not the icon\n * library's identifier (`Gamepad2`), which is nobody's word for anything. */\n label: string;\n /** More words the search matches (\"games\", \"play\"), besides `label`. */\n keywords?: string[];\n /** A remark read after the name and shown under it in the bubble (\"used by\n * Leisure\"). The tile wears a dot while it has one. */\n note?: string;\n /** Out of the choice and out of reach — skipped by the arrow keys and Tab. With a\n * `disabledReason` the tile stays reachable instead and says why. */\n disabled?: boolean;\n /**\n * Why THIS symbol cannot be chosen right now — {@link SwatchOption.disabledReason}'s\n * twin: the tile is `aria-disabled` but reachable by Tab and the arrow keys, the reason\n * shows in its bubble under its name and describes it, and choosing it does nothing.\n * Wins over `disabled`. Kept through a search like any other tile.\n */\n disabledReason?: ReactNode;\n}\n\nexport interface IconPickerProps<T extends string>\n extends Omit<ComponentPropsWithRef<\"div\">, \"onChange\" | \"children\" | \"defaultValue\">,\n FieldHelpProps {\n options: IconOption<T>[];\n /** The chosen icon, or `null` for none. */\n value: T | null;\n onChange: (value: T | null) => void;\n /** Lead with a \"no icon\" tile whose value is `null`. */\n allowNone?: boolean;\n /** See {@link SwatchPickerProps.mixed}: nothing is checked, and the group is\n * described as mixed. */\n mixed?: boolean;\n /**\n * \"automatic\" (default): an arrow key moves AND chooses, the APG radio group.\n * \"manual\": arrows move focus only; Space or Enter chooses. For a picker whose every\n * change is expensive — a bulk edit that saves as it goes.\n */\n activation?: \"automatic\" | \"manual\";\n /** A search box above the grid, matching `label` and `keywords`. For a set large\n * enough that scanning it is slower than typing. */\n searchable?: boolean;\n /** 28 / 32 / 44px tiles. Default \"md\"; \"lg\" is the touch-target size. */\n size?: TileSize;\n /**\n * Classes for every tile's frame, merged after the kit's own — so a caller's\n * `size-*` beats `size`'s. For a tile that has to follow a container query or a\n * breakpoint (keksdose grows its tiles to 40px in a narrow card:\n * `tileClassName=\"@max-md:size-10\"`) without reaching into the kit's markup with an\n * `[&_[role=radio]]` selector. The swatch dot / glyph inside keeps `size`'s\n * dimensions; the tile grows round it.\n */\n tileClassName?: string;\n disabled?: boolean;\n labels?: LabelOverride<IconPickerLabels>;\n /**\n * An 11px static label in a strip over the picker, naming the grid — keksdose K2,\n * {@link SwatchPickerProps.label}'s twin: a symbol picker standing in a form row\n * beside labelled fields, with its label on their line. With `searchable` the strip\n * is over the search box too, which keeps its own name. With a label, `className`\n * styles the wrapper.\n */\n label?: ReactNode;\n /** A {@link FieldHint} beside the label. Only with `label`. {@link Select}'s rule since\n * 0.34 (DUP-2, refactor audit 2026-10): plain TEXT, and a FieldHint at Large and on a\n * touch screen (§4), is a caption under the grid, describing it. */\n hint?: ReactNode;\n}\n\n/**\n * A grid of symbols that is one radio group, with an optional search.\n *\n * Name the group with `aria-label` or `aria-labelledby`. The name lands on the\n * radiogroup itself, not on the outer box: with `searchable` the outer box also holds\n * the search field, which has a name of its own.\n *\n * Keyboard is {@link SwatchPicker}'s: one tab stop, arrows to move (flipped in a\n * right-to-left page), Home and End.\n */\nexport function IconPicker<T extends string>({\n options,\n value,\n onChange,\n allowNone = false,\n mixed = false,\n activation = \"automatic\",\n searchable = false,\n size = \"md\",\n tileClassName,\n disabled = false,\n labels,\n label,\n hint,\n help,\n className,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledBy,\n \"aria-describedby\": ariaDescribedBy,\n ...rest\n}: IconPickerProps<T>) {\n const text = useKitLabels(\"iconPicker\", DEFAULT_ICON_PICKER_LABELS, labels);\n const common = useKitLabels(\"common\", DEFAULT_COMMON_LABELS);\n const locale = useKitLocale();\n const mixedId = useId();\n const labelId = useId();\n const labelled = hasContent(label);\n // The label line's \"?\", or the caption under the grid (DUP-2). Only with a label, the\n // help's \"?\" too (0.36).\n const messages = useFieldMessages({\n hint: labelled ? hint : undefined,\n help: labelled ? help : undefined,\n name: label,\n describedBy: ariaDescribedBy,\n reasonId: mixed && mixedId,\n });\n const [query, setQuery] = useState(\"\");\n // Case- and accent-insensitive, so \"cafe\" finds \"Café\" — every kit list's fold (DUP-5).\n const needle = foldForSearch(query.trim(), locale);\n\n const visible = useMemo(\n () =>\n needle\n ? options.filter((o) =>\n [o.label, ...(o.keywords ?? [])].some((w) => foldForSearch(w, locale).includes(needle)),\n )\n : options,\n [options, needle, locale],\n );\n const byValue = useMemo(() => new Map(options.map((o) => [o.value, o])), [options]);\n const items: TileItem<T>[] = [\n // \"None\" stays through a search: it is the way back to no icon, and it matches\n // no word anybody would type for it.\n ...(allowNone ? [{ key: null, label: text.none }] : []),\n ...visible.map((o) => ({\n key: o.value,\n label: o.label,\n note: o.note,\n disabled: o.disabled,\n disabledReason: o.disabledReason,\n })),\n ];\n\n const picker = (\n <div {...rest} className={cn(\"space-y-2\", !labelled && className)}>\n {searchable && (\n <SearchField\n value={query}\n onChange={setQuery}\n aria-label={text.search}\n clearLabel={common.clear}\n disabled={disabled}\n />\n )}\n <div\n role=\"radiogroup\"\n aria-label={ariaLabel}\n // The strip's label names the GRID, unless the caller named it already.\n aria-labelledby={labelled && ariaLabel === undefined && ariaLabelledBy === undefined ? labelId : ariaLabelledBy}\n aria-describedby={messages.describedBy}\n aria-disabled={disabled || undefined}\n className=\"flex flex-wrap items-center gap-1.5\"\n >\n <TileRadioGroup\n items={items}\n checked={mixed ? undefined : value}\n // A locked tile is refused by the tiles themselves; asked again here so a\n // reason can never become a change, whatever the tiles do.\n onSelect={(next) => {\n if (!hasContent(next === null ? undefined : byValue.get(next)?.disabledReason)) onChange(next);\n }}\n activation={activation}\n disabled={disabled}\n size={size}\n renderTile={(item) => {\n const Icon = item.key === null ? undefined : byValue.get(item.key)?.icon;\n return Icon ? <Icon aria-hidden className={TILE_SIZE[size].glyph} /> : null;\n }}\n tileClassName={(_item, selected) =>\n cn(\n selected ? \"text-primary\" : \"text-secondary\",\n tileClassName,\n )\n }\n />\n </div>\n {needle && visible.length === 0 && (\n <p className=\"text-xs text-muted\">{text.noResults}</p>\n )}\n {mixed && (\n <span id={mixedId} hidden>\n {text.mixed}\n </span>\n )}\n {searchable && (\n // Typing moves no focus and the grid changes silently, so the count is said.\n // `sr-only-fixed`: this sits inside a consumer's markup and must not extend\n // the document height (see tokens.css).\n <span role=\"status\" aria-live=\"polite\" aria-atomic className=\"sr-only-fixed\">\n {needle ? text.resultCount(visible.length) : \"\"}\n </span>\n )}\n </div>\n );\n if (!labelled) return picker;\n return (\n <LabelStrip labelId={labelId} label={label} hint={messages.labelHint} disabled={disabled} className={className}>\n {picker}\n {messages.below}\n </LabelStrip>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/components/icon-picker.tsx"],"sourcesContent":["import { useId, useMemo, useState } from \"react\";\nimport type { ComponentPropsWithRef, ComponentType, ReactNode } from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { DEFAULT_COMMON_LABELS, useKitLabels, useKitLocale, type LabelOverride } from \"../i18n/kit-labels\";\nimport { SearchField } from \"./search-field\";\nimport { TILE_SIZE, TileRadioGroup } from \"./tile-radio\";\nimport type { TileItem, TileSize } from \"./tile-radio\";\nimport { hasContent } from \"../lib/presence\";\nimport { matchesSearch, searchWords } from \"../lib/text-match\";\nimport { LabelStrip, useFieldMessages, type FieldHelpProps } from \"./field-parts\";\nimport { DEFAULT_ICON_PICKER_LABELS } from \"./icon-picker-labels\";\n\nexport interface IconPickerLabels {\n /** The \"no icon\" tile's name, when `allowNone` is set. */\n none: string;\n /** Describes the group while `mixed` is set — a bulk edit over rows that disagree. */\n mixed: string;\n /** The search box's accessible name and placeholder, when `searchable` is set. */\n search: string;\n /** Shown in place of the tiles when the search matches nothing. */\n noResults: string;\n /** Announced as the search narrows the grid. Receives the number of matches. */\n resultCount: (count: number) => string;\n}\n\n// Defined in ./icon-picker-labels, which holds no component code (0.34, KS U8).\nexport { DEFAULT_ICON_PICKER_LABELS };\n\nexport interface IconOption<T extends string> {\n value: T;\n /** A Lucide icon, or any component that takes a `className`. Rendered `aria-hidden`\n * — the tile's name is `label`. */\n icon: ComponentType<{ className?: string; \"aria-hidden\"?: boolean }>;\n /** What the symbol IS, in the user's language (\"Game controller\") — not the icon\n * library's identifier (`Gamepad2`), which is nobody's word for anything. */\n label: string;\n /** More words the search matches (\"games\", \"play\"), besides `label`. */\n keywords?: string[];\n /** A remark read after the name and shown under it in the bubble (\"used by\n * Leisure\"). The tile wears a dot while it has one. */\n note?: string;\n /** Out of the choice and out of reach — skipped by the arrow keys and Tab. With a\n * `disabledReason` the tile stays reachable instead and says why. */\n disabled?: boolean;\n /**\n * Why THIS symbol cannot be chosen right now — {@link SwatchOption.disabledReason}'s\n * twin: the tile is `aria-disabled` but reachable by Tab and the arrow keys, the reason\n * shows in its bubble under its name and describes it, and choosing it does nothing.\n * Wins over `disabled`. Kept through a search like any other tile.\n */\n disabledReason?: ReactNode;\n}\n\nexport interface IconPickerProps<T extends string>\n extends Omit<ComponentPropsWithRef<\"div\">, \"onChange\" | \"children\" | \"defaultValue\">, FieldHelpProps {\n options: IconOption<T>[];\n /** The chosen icon, or `null` for none. */\n value: T | null;\n onChange: (value: T | null) => void;\n /** Lead with a \"no icon\" tile whose value is `null`. */\n allowNone?: boolean;\n /** See {@link SwatchPickerProps.mixed}: nothing is checked, and the group is\n * described as mixed. */\n mixed?: boolean;\n /**\n * \"automatic\" (default): an arrow key moves AND chooses, the APG radio group.\n * \"manual\": arrows move focus only; Space or Enter chooses. For a picker whose every\n * change is expensive — a bulk edit that saves as it goes.\n */\n activation?: \"automatic\" | \"manual\";\n /** A search box above the grid, matching `label` and `keywords`. For a set large\n * enough that scanning it is slower than typing. */\n searchable?: boolean;\n /** 28 / 32 / 44px tiles. Default \"md\"; \"lg\" is the touch-target size. */\n size?: TileSize;\n /**\n * Classes for every tile's frame, merged after the kit's own — so a caller's\n * `size-*` beats `size`'s. For a tile that has to follow a container query or a\n * breakpoint (keksdose grows its tiles to 40px in a narrow card:\n * `tileClassName=\"@max-md:size-10\"`) without reaching into the kit's markup with an\n * `[&_[role=radio]]` selector. The swatch dot / glyph inside keeps `size`'s\n * dimensions; the tile grows round it.\n */\n tileClassName?: string;\n disabled?: boolean;\n labels?: LabelOverride<IconPickerLabels>;\n /**\n * An 11px static label in a strip over the picker, naming the grid — keksdose K2,\n * {@link SwatchPickerProps.label}'s twin: a symbol picker standing in a form row\n * beside labelled fields, with its label on their line. With `searchable` the strip\n * is over the search box too, which keeps its own name. With a label, `className`\n * styles the wrapper.\n */\n label?: ReactNode;\n /** A {@link FieldHint} beside the label. Only with `label`. {@link Select}'s rule since\n * 0.34 (DUP-2, refactor audit 2026-10): plain TEXT, and a FieldHint at Large and on a\n * touch screen (§4), is a caption under the grid, describing it. */\n hint?: ReactNode;\n}\n\n/**\n * A grid of symbols that is one radio group, with an optional search.\n *\n * Name the group with `aria-label` or `aria-labelledby`. The name lands on the\n * radiogroup itself, not on the outer box: with `searchable` the outer box also holds\n * the search field, which has a name of its own.\n *\n * Keyboard is {@link SwatchPicker}'s: one tab stop, arrows to move (flipped in a\n * right-to-left page), Home and End.\n */\nexport function IconPicker<T extends string>({\n options,\n value,\n onChange,\n allowNone = false,\n mixed = false,\n activation = \"automatic\",\n searchable = false,\n size = \"md\",\n tileClassName,\n disabled = false,\n labels,\n label,\n hint,\n help,\n className,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledBy,\n \"aria-describedby\": ariaDescribedBy,\n ...rest\n}: IconPickerProps<T>) {\n const text = useKitLabels(\"iconPicker\", DEFAULT_ICON_PICKER_LABELS, labels);\n const common = useKitLabels(\"common\", DEFAULT_COMMON_LABELS);\n const locale = useKitLocale();\n const mixedId = useId();\n const labelId = useId();\n const labelled = hasContent(label);\n // The label line's \"?\", or the caption under the grid (DUP-2). Only with a label, the\n // help's \"?\" too (0.36).\n const messages = useFieldMessages({\n hint: labelled ? hint : undefined,\n help: labelled ? help : undefined,\n name: label,\n describedBy: ariaDescribedBy,\n reasonId: mixed && mixedId,\n });\n const [query, setQuery] = useState(\"\");\n // Case- and accent-insensitive, so \"cafe\" finds \"Café\" — every kit list's fold (DUP-5).\n // Every word of the query, in the label or a keyword (0.51.4).\n const words = useMemo(() => searchWords(query, locale), [query, locale]);\n const needle = words.length > 0;\n\n const visible = useMemo(\n () => (needle ? options.filter((o) => matchesSearch(words, [o.label, ...(o.keywords ?? [])], locale)) : options),\n [options, needle, words, locale],\n );\n const byValue = useMemo(() => new Map(options.map((o) => [o.value, o])), [options]);\n const items: TileItem<T>[] = [\n // \"None\" stays through a search: it is the way back to no icon, and it matches\n // no word anybody would type for it.\n ...(allowNone ? [{ key: null, label: text.none }] : []),\n ...visible.map((o) => ({\n key: o.value,\n label: o.label,\n note: o.note,\n disabled: o.disabled,\n disabledReason: o.disabledReason,\n })),\n ];\n\n const picker = (\n <div {...rest} className={cn(\"space-y-2\", !labelled && className)}>\n {searchable && (\n <SearchField\n value={query}\n onChange={setQuery}\n aria-label={text.search}\n clearLabel={common.clear}\n disabled={disabled}\n />\n )}\n <div\n role=\"radiogroup\"\n aria-label={ariaLabel}\n // The strip's label names the GRID, unless the caller named it already.\n aria-labelledby={labelled && ariaLabel === undefined && ariaLabelledBy === undefined ? labelId : ariaLabelledBy}\n aria-describedby={messages.describedBy}\n aria-disabled={disabled || undefined}\n className=\"flex flex-wrap items-center gap-1.5\"\n >\n <TileRadioGroup\n items={items}\n checked={mixed ? undefined : value}\n // A locked tile is refused by the tiles themselves; asked again here so a\n // reason can never become a change, whatever the tiles do.\n onSelect={(next) => {\n if (!hasContent(next === null ? undefined : byValue.get(next)?.disabledReason)) onChange(next);\n }}\n activation={activation}\n disabled={disabled}\n size={size}\n renderTile={(item) => {\n const Icon = item.key === null ? undefined : byValue.get(item.key)?.icon;\n return Icon ? <Icon aria-hidden className={TILE_SIZE[size].glyph} /> : null;\n }}\n tileClassName={(_item, selected) => cn(selected ? \"text-primary\" : \"text-secondary\", tileClassName)}\n />\n </div>\n {needle && visible.length === 0 && <p className=\"text-xs text-muted\">{text.noResults}</p>}\n {mixed && (\n <span id={mixedId} hidden>\n {text.mixed}\n </span>\n )}\n {searchable && (\n // Typing moves no focus and the grid changes silently, so the count is said.\n // `sr-only-fixed`: this sits inside a consumer's markup and must not extend\n // the document height (see tokens.css).\n <span role=\"status\" aria-live=\"polite\" aria-atomic className=\"sr-only-fixed\">\n {needle ? text.resultCount(visible.length) : \"\"}\n </span>\n )}\n </div>\n );\n if (!labelled) return picker;\n return (\n <LabelStrip labelId={labelId} label={label} hint={messages.labelHint} disabled={disabled} className={className}>\n {picker}\n {messages.below}\n </LabelStrip>\n );\n}\n"],"mappings":"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","names":[]}
@@ -39,59 +39,50 @@ function ImageGrid({
39
39
  ...style
40
40
  };
41
41
  return /* @__PURE__ */ jsxs(Fragment, { children: [
42
- /* @__PURE__ */ jsx(
43
- "ul",
44
- {
45
- "aria-label": labels.list,
46
- ...rest,
47
- className: cn("grid gap-2", className),
48
- style: gridStyle,
49
- children: items.map((item, i) => {
50
- const name = item.alt || labels.item(i + 1, count);
51
- const kind = imageItemKind(item);
52
- const actions = renderActions?.(item, i);
53
- return /* @__PURE__ */ jsxs("li", { className: "relative min-w-0", children: [
54
- /* @__PURE__ */ jsx(
55
- "button",
56
- {
57
- type: "button",
58
- "aria-label": name,
59
- onClick: () => onOpen ? onOpen(i) : setOpenIndex(i),
60
- className: cn(
61
- "block w-full overflow-hidden rounded-md border border-subtle bg-hover",
62
- "focus-visible:outline-focus focus-visible:outline-offset-2 focus-visible:outline-brand",
63
- ASPECT_CLASS[aspect]
64
- ),
65
- children: kind === "file" ? /* @__PURE__ */ jsxs("span", { className: "flex h-full w-full flex-col items-center justify-center gap-1 p-2 text-center text-xs text-secondary", children: [
66
- /* @__PURE__ */ jsx(FileText, { "aria-hidden": true, className: "size-6 shrink-0 text-muted" }),
67
- /* @__PURE__ */ jsx(FileNameLabel, { name: item.fileName ?? item.alt ?? "" })
68
- ] }) : /* @__PURE__ */ jsx(
69
- AuthedImage,
70
- {
71
- src: item.thumbnailSrc ?? item.src,
72
- alt: "",
73
- fetcher,
74
- loading: "lazy",
75
- wrapperClassName: "h-full w-full",
76
- className: cn("h-full w-full", aspect === "auto" ? "object-contain" : "object-cover")
77
- }
78
- )
79
- }
42
+ /* @__PURE__ */ jsx("ul", { "aria-label": labels.list, ...rest, className: cn("grid gap-2", className), style: gridStyle, children: items.map((item, i) => {
43
+ const name = item.alt || labels.item(i + 1, count);
44
+ const kind = imageItemKind(item);
45
+ const actions = renderActions?.(item, i);
46
+ return /* @__PURE__ */ jsxs("li", { className: "relative min-w-0", children: [
47
+ /* @__PURE__ */ jsx(
48
+ "button",
49
+ {
50
+ type: "button",
51
+ "aria-label": name,
52
+ onClick: () => onOpen ? onOpen(i) : setOpenIndex(i),
53
+ className: cn(
54
+ "block w-full overflow-hidden rounded-md border border-subtle bg-hover",
55
+ "focus-visible:outline-focus focus-visible:outline-offset-2 focus-visible:outline-brand",
56
+ ASPECT_CLASS[aspect]
80
57
  ),
81
- actions !== void 0 && actions !== null && actions !== false && /* @__PURE__ */ jsx(
82
- "div",
58
+ children: kind === "file" ? /* @__PURE__ */ jsxs("span", { className: "flex h-full w-full flex-col items-center justify-center gap-1 p-2 text-center text-xs text-secondary", children: [
59
+ /* @__PURE__ */ jsx(FileText, { "aria-hidden": true, className: "size-6 shrink-0 text-muted" }),
60
+ /* @__PURE__ */ jsx(FileNameLabel, { name: item.fileName ?? item.alt ?? "" })
61
+ ] }) : /* @__PURE__ */ jsx(
62
+ AuthedImage,
83
63
  {
84
- role: "group",
85
- "aria-label": labels.actions(name),
86
- className: "absolute end-1 top-1 flex gap-1 rounded-full bg-[color-mix(in_srgb,var(--bg-inverse)_40%,transparent)] p-0.5 backdrop-blur-sm",
87
- children: actions
64
+ src: item.thumbnailSrc ?? item.src,
65
+ alt: "",
66
+ fetcher,
67
+ loading: "lazy",
68
+ wrapperClassName: "h-full w-full",
69
+ className: cn("h-full w-full", aspect === "auto" ? "object-contain" : "object-cover")
88
70
  }
89
- ),
90
- item.caption !== void 0 && item.caption !== null && item.caption !== false && /* @__PURE__ */ jsx("div", { className: "mt-1 line-clamp-2 text-xs text-secondary", children: item.caption })
91
- ] }, item.key ?? item.src);
92
- })
93
- }
94
- ),
71
+ )
72
+ }
73
+ ),
74
+ actions !== void 0 && actions !== null && actions !== false && /* @__PURE__ */ jsx(
75
+ "div",
76
+ {
77
+ role: "group",
78
+ "aria-label": labels.actions(name),
79
+ className: "absolute end-1 top-1 flex gap-1 rounded-full bg-[color-mix(in_srgb,var(--bg-inverse)_40%,transparent)] p-0.5 backdrop-blur-sm",
80
+ children: actions
81
+ }
82
+ ),
83
+ item.caption !== void 0 && item.caption !== null && item.caption !== false && /* @__PURE__ */ jsx("div", { className: "mt-1 line-clamp-2 text-xs text-secondary", children: item.caption })
84
+ ] }, item.key ?? item.src);
85
+ }) }),
95
86
  !onOpen && /* @__PURE__ */ jsx(
96
87
  Lightbox,
97
88
  {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/image-grid.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode } from \"react\";\nimport { FileText } from \"lucide-react\";\n\nimport { cn } from \"../lib/cn\";\nimport { useKitLabels, type LabelOverride } from \"../i18n/kit-labels\";\nimport type { AuthedFetcher } from \"../hooks/use-authed-src\";\nimport { AuthedImage } from \"./authed-image\";\nimport { Lightbox, imageItemKind } from \"./lightbox\";\nimport type { ImageItem, LightboxLabels } from \"./lightbox\";\nimport { DEFAULT_IMAGE_GRID_LABELS } from \"./image-grid-labels\";\n\nexport interface ImageGridLabels {\n /** The list's accessible name. */\n list: string;\n /** An open button's name when an item's `alt` is empty: \"Image 3 of 7\". */\n item: (index: number, count: number) => string;\n /** The name of the group of per-image actions, given the image's name. */\n actions: (name: string) => string;\n}\n\n// Defined in ./image-grid-labels, which holds no component code (0.34, KS U8).\nexport { DEFAULT_IMAGE_GRID_LABELS };\n\nexport interface ImageGridProps extends Omit<ComponentPropsWithRef<\"ul\">, \"children\"> {\n items: readonly ImageItem[];\n /** The app's authenticated GET — thumbnails and the lightbox fetch through it. Only\n * images are fetched for the grid; a file (PDF) tile shows its icon and name. */\n fetcher?: AuthedFetcher | null;\n /**\n * Per-image actions — a delete, an edit. Rendered BESIDE the open button (on top of\n * the thumbnail's corner), never inside it: a button inside a button is invalid HTML\n * and a screen reader flattens it into the outer one's name, where it cannot be\n * reached. Use `IconButton variant=\"overlay\"` for controls that sit on a photo.\n */\n renderActions?: (item: ImageItem, index: number) => ReactNode;\n /**\n * What a tile's click does. Left out, the grid opens its own {@link Lightbox} on that\n * image. Given, the grid opens nothing — for a caller that keeps the lightbox's\n * index in the URL, or that opens something else.\n */\n onOpen?: (index: number) => void;\n /**\n * A fixed column count. Left out, the grid is responsive: as many columns of at\n * least {@link minTileSize} px as fit, so the same grid is three across in a card on\n * a phone and eight across on a desktop page — no breakpoints to choose.\n */\n columns?: number;\n /** The smallest a tile may be in the responsive layout. Default 96 (px). */\n minTileSize?: number;\n /** `square` (default) crops to a square; `video` to 16:9; `auto` keeps each image's\n * own proportions (a floor plan), within a square-ish box. */\n aspect?: \"square\" | \"video\" | \"auto\";\n /** Props for the built-in lightbox (ignored with {@link onOpen}). */\n lightbox?: {\n loop?: boolean;\n zoom?: boolean;\n download?: boolean;\n onDownload?: (item: ImageItem, index: number) => void;\n labels?: LabelOverride<LightboxLabels>;\n };\n labels?: LabelOverride<ImageGridLabels>;\n}\n\nconst ASPECT_CLASS = {\n square: \"aspect-square\",\n video: \"aspect-video\",\n auto: \"aspect-[4/3]\",\n} as const;\n\n/**\n * A file name as stem and extension, so a tile can cut the stem and keep the\n * extension whole: \"floor-plan-level-2.p / df\" (a break inside the extension) told the\n * reader less than \"floor-plan-le….pdf\" does. A leading dot (\".env\") or an\n * \"extension\" longer than five characters is not treated as one.\n */\nexport function splitFileName(name: string): [stem: string, ext: string] {\n const dot = name.lastIndexOf(\".\");\n if (dot <= 0 || name.length - dot > 6) return [name, \"\"];\n return [name.slice(0, dot), name.slice(dot)];\n}\n\n/**\n * A grid of thumbnails that open in a {@link Lightbox} — kastlan's room and defect\n * photos (room-inspector, unit-rooms-tab) and its floor plans (unit-floor-plan), each\n * of which drew its own `grid grid-cols-N` of `<img>` that nothing could open, with no\n * loading state and nowhere to put a delete.\n *\n * Each tile is a real `<button>` named by the image's alt, with the actions beside it\n * and the caption below it — outside the button, so the name stays the alt and the\n * caption is read once. Placeholders come from {@link AuthedImage}: a skeleton while\n * loading, a \"couldn't load\" tile on failure.\n */\nexport function ImageGrid({\n items,\n fetcher,\n renderActions,\n onOpen,\n columns,\n minTileSize = 96,\n aspect = \"square\",\n lightbox,\n labels: labelsProp,\n className,\n style,\n ...rest\n}: ImageGridProps) {\n const labels = useKitLabels(\"imageGrid\", DEFAULT_IMAGE_GRID_LABELS, labelsProp);\n const [openIndex, setOpenIndex] = useState<number | null>(null);\n const count = items.length;\n\n const gridStyle: CSSProperties = {\n gridTemplateColumns:\n columns !== undefined\n ? `repeat(${columns}, minmax(0, 1fr))`\n : `repeat(auto-fill, minmax(min(${minTileSize}px, 100%), 1fr))`,\n ...style,\n };\n\n return (\n <>\n <ul\n aria-label={labels.list}\n {...rest}\n className={cn(\"grid gap-2\", className)}\n style={gridStyle}\n >\n {items.map((item, i) => {\n const name = item.alt || labels.item(i + 1, count);\n const kind = imageItemKind(item);\n const actions = renderActions?.(item, i);\n return (\n <li key={item.key ?? item.src} className=\"relative min-w-0\">\n <button\n type=\"button\"\n aria-label={name}\n onClick={() => (onOpen ? onOpen(i) : setOpenIndex(i))}\n className={cn(\n \"block w-full overflow-hidden rounded-md border border-subtle bg-hover\",\n \"focus-visible:outline-focus focus-visible:outline-offset-2 focus-visible:outline-brand\",\n ASPECT_CLASS[aspect],\n )}\n >\n {kind === \"file\" ? (\n <span className=\"flex h-full w-full flex-col items-center justify-center gap-1 p-2 text-center text-xs text-secondary\">\n <FileText aria-hidden className=\"size-6 shrink-0 text-muted\" />\n <FileNameLabel name={item.fileName ?? item.alt ?? \"\"} />\n </span>\n ) : (\n <AuthedImage\n src={item.thumbnailSrc ?? item.src}\n // The button is named already; the picture inside it is decoration\n // to a reader, or the name would be read twice.\n alt=\"\"\n fetcher={fetcher}\n loading=\"lazy\"\n wrapperClassName=\"h-full w-full\"\n className={cn(\"h-full w-full\", aspect === \"auto\" ? \"object-contain\" : \"object-cover\")}\n />\n )}\n </button>\n {actions !== undefined && actions !== null && actions !== false && (\n <div\n role=\"group\"\n aria-label={labels.actions(name)}\n // A backdrop behind the whole group, in the inverse token the overlay\n // buttons use: a translucent disc alone took most of its colour from\n // the picture, and on a pale photo or the light file tile the glyphs\n // washed out. Two layers of the inverse keep them readable over any\n // picture, in both themes.\n className=\"absolute end-1 top-1 flex gap-1 rounded-full bg-[color-mix(in_srgb,var(--bg-inverse)_40%,transparent)] p-0.5 backdrop-blur-sm\"\n >\n {actions}\n </div>\n )}\n {item.caption !== undefined && item.caption !== null && item.caption !== false && (\n <div className=\"mt-1 line-clamp-2 text-xs text-secondary\">{item.caption}</div>\n )}\n </li>\n );\n })}\n </ul>\n {!onOpen && (\n <Lightbox\n open={openIndex !== null}\n onClose={() => setOpenIndex(null)}\n items={items}\n index={openIndex ?? 0}\n onIndexChange={setOpenIndex}\n fetcher={fetcher}\n {...lightbox}\n />\n )}\n </>\n );\n}\n\n/** One line: the stem truncates, the extension never does. */\nfunction FileNameLabel({ name }: { name: string }) {\n const [stem, ext] = splitFileName(name);\n return (\n <span aria-hidden className=\"flex max-w-full min-w-0\">\n <span className=\"min-w-0 truncate\">{stem}</span>\n {ext && <span className=\"shrink-0\">{ext}</span>}\n </span>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/components/image-grid.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode } from \"react\";\nimport { FileText } from \"lucide-react\";\n\nimport { cn } from \"../lib/cn\";\nimport { useKitLabels, type LabelOverride } from \"../i18n/kit-labels\";\nimport type { AuthedFetcher } from \"../hooks/use-authed-src\";\nimport { AuthedImage } from \"./authed-image\";\nimport { Lightbox, imageItemKind } from \"./lightbox\";\nimport type { ImageItem, LightboxLabels } from \"./lightbox\";\nimport { DEFAULT_IMAGE_GRID_LABELS } from \"./image-grid-labels\";\n\nexport interface ImageGridLabels {\n /** The list's accessible name. */\n list: string;\n /** An open button's name when an item's `alt` is empty: \"Image 3 of 7\". */\n item: (index: number, count: number) => string;\n /** The name of the group of per-image actions, given the image's name. */\n actions: (name: string) => string;\n}\n\n// Defined in ./image-grid-labels, which holds no component code (0.34, KS U8).\nexport { DEFAULT_IMAGE_GRID_LABELS };\n\nexport interface ImageGridProps extends Omit<ComponentPropsWithRef<\"ul\">, \"children\"> {\n items: readonly ImageItem[];\n /** The app's authenticated GET — thumbnails and the lightbox fetch through it. Only\n * images are fetched for the grid; a file (PDF) tile shows its icon and name. */\n fetcher?: AuthedFetcher | null;\n /**\n * Per-image actions — a delete, an edit. Rendered BESIDE the open button (on top of\n * the thumbnail's corner), never inside it: a button inside a button is invalid HTML\n * and a screen reader flattens it into the outer one's name, where it cannot be\n * reached. Use `IconButton variant=\"overlay\"` for controls that sit on a photo.\n */\n renderActions?: (item: ImageItem, index: number) => ReactNode;\n /**\n * What a tile's click does. Left out, the grid opens its own {@link Lightbox} on that\n * image. Given, the grid opens nothing — for a caller that keeps the lightbox's\n * index in the URL, or that opens something else.\n */\n onOpen?: (index: number) => void;\n /**\n * A fixed column count. Left out, the grid is responsive: as many columns of at\n * least {@link minTileSize} px as fit, so the same grid is three across in a card on\n * a phone and eight across on a desktop page — no breakpoints to choose.\n */\n columns?: number;\n /** The smallest a tile may be in the responsive layout. Default 96 (px). */\n minTileSize?: number;\n /** `square` (default) crops to a square; `video` to 16:9; `auto` keeps each image's\n * own proportions (a floor plan), within a square-ish box. */\n aspect?: \"square\" | \"video\" | \"auto\";\n /** Props for the built-in lightbox (ignored with {@link onOpen}). */\n lightbox?: {\n loop?: boolean;\n zoom?: boolean;\n download?: boolean;\n onDownload?: (item: ImageItem, index: number) => void;\n labels?: LabelOverride<LightboxLabels>;\n };\n labels?: LabelOverride<ImageGridLabels>;\n}\n\nconst ASPECT_CLASS = {\n square: \"aspect-square\",\n video: \"aspect-video\",\n auto: \"aspect-[4/3]\",\n} as const;\n\n/**\n * A file name as stem and extension, so a tile can cut the stem and keep the\n * extension whole: \"floor-plan-level-2.p / df\" (a break inside the extension) told the\n * reader less than \"floor-plan-le….pdf\" does. A leading dot (\".env\") or an\n * \"extension\" longer than five characters is not treated as one.\n */\nexport function splitFileName(name: string): [stem: string, ext: string] {\n const dot = name.lastIndexOf(\".\");\n if (dot <= 0 || name.length - dot > 6) return [name, \"\"];\n return [name.slice(0, dot), name.slice(dot)];\n}\n\n/**\n * A grid of thumbnails that open in a {@link Lightbox} — kastlan's room and defect\n * photos (room-inspector, unit-rooms-tab) and its floor plans (unit-floor-plan), each\n * of which drew its own `grid grid-cols-N` of `<img>` that nothing could open, with no\n * loading state and nowhere to put a delete.\n *\n * Each tile is a real `<button>` named by the image's alt, with the actions beside it\n * and the caption below it — outside the button, so the name stays the alt and the\n * caption is read once. Placeholders come from {@link AuthedImage}: a skeleton while\n * loading, a \"couldn't load\" tile on failure.\n */\nexport function ImageGrid({\n items,\n fetcher,\n renderActions,\n onOpen,\n columns,\n minTileSize = 96,\n aspect = \"square\",\n lightbox,\n labels: labelsProp,\n className,\n style,\n ...rest\n}: ImageGridProps) {\n const labels = useKitLabels(\"imageGrid\", DEFAULT_IMAGE_GRID_LABELS, labelsProp);\n const [openIndex, setOpenIndex] = useState<number | null>(null);\n const count = items.length;\n\n const gridStyle: CSSProperties = {\n gridTemplateColumns:\n columns !== undefined\n ? `repeat(${columns}, minmax(0, 1fr))`\n : `repeat(auto-fill, minmax(min(${minTileSize}px, 100%), 1fr))`,\n ...style,\n };\n\n return (\n <>\n <ul aria-label={labels.list} {...rest} className={cn(\"grid gap-2\", className)} style={gridStyle}>\n {items.map((item, i) => {\n const name = item.alt || labels.item(i + 1, count);\n const kind = imageItemKind(item);\n const actions = renderActions?.(item, i);\n return (\n <li key={item.key ?? item.src} className=\"relative min-w-0\">\n <button\n type=\"button\"\n aria-label={name}\n onClick={() => (onOpen ? onOpen(i) : setOpenIndex(i))}\n className={cn(\n \"block w-full overflow-hidden rounded-md border border-subtle bg-hover\",\n \"focus-visible:outline-focus focus-visible:outline-offset-2 focus-visible:outline-brand\",\n ASPECT_CLASS[aspect],\n )}\n >\n {kind === \"file\" ? (\n <span className=\"flex h-full w-full flex-col items-center justify-center gap-1 p-2 text-center text-xs text-secondary\">\n <FileText aria-hidden className=\"size-6 shrink-0 text-muted\" />\n <FileNameLabel name={item.fileName ?? item.alt ?? \"\"} />\n </span>\n ) : (\n <AuthedImage\n src={item.thumbnailSrc ?? item.src}\n // The button is named already; the picture inside it is decoration\n // to a reader, or the name would be read twice.\n alt=\"\"\n fetcher={fetcher}\n loading=\"lazy\"\n wrapperClassName=\"h-full w-full\"\n className={cn(\"h-full w-full\", aspect === \"auto\" ? \"object-contain\" : \"object-cover\")}\n />\n )}\n </button>\n {actions !== undefined && actions !== null && actions !== false && (\n <div\n role=\"group\"\n aria-label={labels.actions(name)}\n // A backdrop behind the whole group, in the inverse token the overlay\n // buttons use: a translucent disc alone took most of its colour from\n // the picture, and on a pale photo or the light file tile the glyphs\n // washed out. Two layers of the inverse keep them readable over any\n // picture, in both themes.\n className=\"absolute end-1 top-1 flex gap-1 rounded-full bg-[color-mix(in_srgb,var(--bg-inverse)_40%,transparent)] p-0.5 backdrop-blur-sm\"\n >\n {actions}\n </div>\n )}\n {item.caption !== undefined && item.caption !== null && item.caption !== false && (\n <div className=\"mt-1 line-clamp-2 text-xs text-secondary\">{item.caption}</div>\n )}\n </li>\n );\n })}\n </ul>\n {!onOpen && (\n <Lightbox\n open={openIndex !== null}\n onClose={() => setOpenIndex(null)}\n items={items}\n index={openIndex ?? 0}\n onIndexChange={setOpenIndex}\n fetcher={fetcher}\n {...lightbox}\n />\n )}\n </>\n );\n}\n\n/** One line: the stem truncates, the extension never does. */\nfunction FileNameLabel({ name }: { name: string }) {\n const [stem, ext] = splitFileName(name);\n return (\n <span aria-hidden className=\"flex max-w-full min-w-0\">\n <span className=\"min-w-0 truncate\">{stem}</span>\n {ext && <span className=\"shrink-0\">{ext}</span>}\n </span>\n );\n}\n"],"mappings":"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","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/inline-edit-field.tsx"],"sourcesContent":["import { useEffect, useId, useRef, useState } from \"react\";\nimport type { FocusEvent, InputHTMLAttributes, KeyboardEvent, ReactNode } from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { hasContent } from \"../lib/presence\";\nimport { useAnnounce } from \"../hooks/use-announce\";\nimport { claimEscape } from \"../hooks/use-dismiss\";\nimport { isImeEnter } from \"../lib/ime\";\nimport { useKitLabels, type LabelOverride } from \"../i18n/kit-labels\";\nimport { Input } from \"./input\";\nimport { Spinner } from \"./spinner\";\nimport { FIELD_ERROR_CLASS } from \"./field-parts\";\nimport { FOCUS_RING } from \"./focus-ring\";\nimport { Tooltip } from \"./tooltip\";\nimport { useCommitReason, type CommitScope } from \"./write-lock\";\nimport { DEFAULT_INLINE_EDIT_LABELS } from \"./inline-edit-field-labels\";\n\n/**\n * Every string {@link InlineEditField} says on its own behalf — the `inlineEdit`\n * namespace of `<UiKitProvider labels>`, overridable per instance through `labels`.\n */\nexport interface InlineEditLabels {\n /** The display button's tooltip and description — what pressing it does. Receives\n * the field's `label` (\"Budget name\" → \"Edit Budget name\"). */\n edit: (label: string) => string;\n /** Shown (and spoken) when `onCommit` rejects and no `formatError` says better. */\n failed: string;\n /** What the display shows for an empty value, when no `placeholder` is given. */\n empty: string;\n}\n\n// Defined in ./inline-edit-field-labels, which holds no component code (0.34, KS U8).\nexport { DEFAULT_INLINE_EDIT_LABELS };\n\n/** What an `editor` render prop is handed: the draft and the means to end the edit. */\nexport interface InlineEditorProps<T> {\n /** The draft — seeded from `value` each time the editor opens. */\n value: T;\n /** Replace the draft (every keystroke). */\n onChange: (next: T) => void;\n /**\n * End the edit and save. Without an argument it saves the draft; WITH one it saves\n * that — for an editor whose own commit hands over a resolved value (`NumberInput`'s\n * `onCommit` gives \"1050\" for a typed \"1100-50\"). Called twice (the editor's own\n * Enter, then the field's), the second call does nothing.\n */\n commit: (next?: T) => void;\n /** End the edit and drop the draft — what Escape does. */\n cancel: () => void;\n /** The save is in flight: make the control read-only (not disabled — it has focus). */\n pending: boolean;\n /** The field's `label`, for the control's accessible name. */\n label: string;\n /** The last save failed and the draft has not been touched since. */\n invalid: boolean;\n /** The id of the failure message while there is one, for `aria-describedby`. */\n describedBy: string | undefined;\n /** The four above as attributes — spread them on a kit field that takes the DOM\n * spelling: `<AmountInput {...p.inputProps} … />`. */\n inputProps: {\n \"aria-label\": string;\n \"aria-invalid\": true | undefined;\n \"aria-describedby\": string | undefined;\n readOnly: boolean;\n };\n}\n\ninterface InlineEditFieldBaseProps<T> {\n /** The saved value — what the display shows, and what the editor opens on. */\n value: T;\n /**\n * Save `next`. Return a promise and the field waits: the editor stays open and\n * read-only with a spinner until it settles, closes when it resolves, and STAYS OPEN\n * with the failure under it when it rejects (or throws) — the typed value is kept for\n * a retry. Not called when the draft equals `value` (see `isEqual`): opening the\n * editor and leaving it unchanged is not a write.\n */\n onCommit: (next: T) => void | Promise<unknown>;\n /** What the value is (\"Budget name\", \"Assigned\") — the editor's accessible name and\n * the subject of the display's \"Edit …\" description. Not drawn: inline, the row or\n * the column header is the visible label. */\n label: string;\n /** The value as the display shows it — a formatted amount, a date. Default: the\n * value as text. */\n display?: (value: T) => ReactNode;\n /** Whether the draft is unchanged, so leaving the editor is not a write. Default\n * `Object.is`. keksdose compares assigned amounts as NUMBERS — \"1100\" is the\n * server's \"1100.0000\". */\n isEqual?: (a: T, b: T) => boolean;\n /** What the display shows for an empty value (`\"\"`, `null`, an empty `display`).\n * Default `labels.empty`. */\n placeholder?: ReactNode;\n /** `\"end\"` for a figure in a right-aligned money column. Default `\"start\"`. */\n align?: \"start\" | \"end\";\n /** Look, do not touch — a guest's view: the value as plain text, no button. */\n readOnly?: boolean;\n /**\n * Why it cannot be edited now — the write lock's sentence, a closed period. The\n * value stays a BUTTON (focusable, `aria-disabled`) with the reason in the kit\n * Tooltip and its description; it opens no editor. An editor that is open when the\n * reason arrives closes, its draft dropped: a field you can type into must be one\n * whose value can be saved (keksdose dev#496).\n */\n disabledReason?: ReactNode;\n /** This field COMMITS — under a locked {@link WriteLockProvider} it takes the\n * `disabledReason` path with the lock's reason (which wins over its own). No\n * provider, or an unlocked one: no effect. */\n commit?: CommitScope;\n /** The value is the user's own data: `data-private` on the display and the editor,\n * so the app's demo-mode blur covers both. */\n redact?: boolean;\n /** Turns a rejection into words. Default `labels.failed`. */\n formatError?: (error: unknown) => ReactNode;\n /** Controlled open state — a row's swipe action that opens the editor from outside\n * (keksdose's assigned cell). Left out, the field keeps its own. */\n editing?: boolean;\n /** Called with the next open state: on open, on a save that resolved, on cancel. */\n onEditingChange?: (editing: boolean) => void;\n /** Attributes for the DEFAULT text editor's `<input>` — `maxLength`, `inputMode`,\n * `autoComplete`. Ignored with `editor`. */\n inputProps?: Omit<InputHTMLAttributes<HTMLInputElement>, \"value\" | \"defaultValue\" | \"onChange\" | \"type\">;\n /** Classes for the root. */\n className?: string;\n labels?: LabelOverride<InlineEditLabels>;\n}\n\ntype EditorRender<T> = (props: InlineEditorProps<T>) => ReactNode;\n\n/**\n * The editor. Optional for a string value — the kit's `Input`, compact — and\n * REQUIRED for anything else, since only the caller knows how to edit a number or an\n * amount. Render a kit field from the props it is handed:\n *\n * ```tsx\n * editor={(p) => (\n * <NumberInput calculator={false} aria-label={p.label} value={p.value} onChange={p.onChange}\n * onCommit={p.commit} aria-invalid={p.invalid || undefined} aria-describedby={p.describedBy} />\n * )}\n * ```\n *\n * The field itself handles Enter (save), Escape (cancel) and focus leaving the editor\n * (save) around whatever is rendered — a key the editor has already handled\n * (`preventDefault`, as a listbox does for its Enter) is left to it. Focus counts as\n * \"inside\" across a portal, so a picker or a phone keypad the editor opens does not\n * end the edit.\n */\nexport type InlineEditFieldProps<T = string> = InlineEditFieldBaseProps<T> &\n ([T] extends [string] ? { editor?: EditorRender<T> } : { editor: EditorRender<T> });\n\n/** Calls the `editor` render prop as a component of its own: the props it is handed\n * close over the field's refs, and a render prop called in the field's own render\n * would read like a ref read during render. Module-level, so the type is stable and\n * the editor never remounts. */\nfunction RenderEditor<T>({ render, ...props }: InlineEditorProps<T> & { render: EditorRender<T> }) {\n return render(props);\n}\n\n/**\n * The value at Large and Extra large (0.32.1, keksdose's 0.32 report;\n * docs/text-size-harmonization.md §4 \"nothing truncates\"): the display button, the\n * locked one and the read-only text wrap instead of ending in \"…\", and break a word too\n * long for the cell, so an IBAN or an e-mail address shows to its last character. The\n * value IS the button's accessible name and the thing being edited: a reader who needs\n * big type cannot recover a cut-off end by squinting. A `large:` class, so a table of\n * these keeps its one-line cells at Normal.\n *\n * 0.33 (§10.17): the kit's one `truncate-until-large` (tokens.css) in place of this\n * file's own `truncate` + `large:whitespace-normal large:break-words`. The utility\n * breaks with `overflow-wrap: anywhere`, which lowers the value's min-content width: in\n * an auto-sized table column (keksdose's budget and invoice lines) give the column a\n * width, or an amount can be squeezed to a digit per line.\n */\nconst VALUE_TRUNCATE = \"truncate-until-large\";\n\n/** `onCommit`'s result, if it is a promise. */\nfunction asPromise(result: unknown): Promise<unknown> | null {\n return result && typeof (result as Promise<unknown>).then === \"function\" ? (result as Promise<unknown>) : null;\n}\n\n/**\n * A value that edits in place: a quiet button showing the value; click it (or Enter\n * on it) and it becomes a field; Enter or leaving the field saves, Escape cancels\n * (keksdose K9).\n *\n * keksdose has three of them by hand — the budget's assigned cell (budget-cells.tsx),\n * the invoice line's value cells (invoice-lines-table.tsx `EditableCell`) and the\n * budget rename (budgets-page.tsx, an `Input` + Save + Cancel with no Escape) — and\n * each had to learn the same rules:\n *\n * - **The display's accessible name is the VALUE.** A cell named \"Edit\" reads a table\n * of \"edit, edit, edit\"; the action is the description instead (\"Edit Assigned\"),\n * in the kit Tooltip on hover and focus.\n * - **An unchanged draft is not a write.** Opening the editor and leaving it was a PUT\n * in keksdose until `isEqual` compared numbers, and on a sealed budget an outbox row\n * for a change nobody made.\n * - **A failed save keeps the editor.** The typed value is the user's work; the\n * failure is shown under it (attached with `aria-describedby`, and spoken), and the\n * next keystroke clears it. Escape still drops the draft.\n * - **Locked means a disabled BUTTON, not a disabled field** (keksdose dev#496): at\n * rest a real row shows this button, so a locked page shows the same, with the\n * reason — never a field that takes keys it cannot save.\n *\n * Inside a {@link DataTable} cell it needs nothing extra: the table already leaves a\n * click or a key on a control of the cell to that control. Focus returns to the value\n * after Enter or Escape; after a save that came from focus leaving, it stays where the\n * user put it.\n *\n * ```tsx\n * <InlineEditField label={t(\"budgets.name\")} value={budget.name}\n * onCommit={(name) => rename.mutateAsync({ id: budget.id, name })} inputProps={{ maxLength: 120 }} />\n * ```\n */\nexport function InlineEditField<T = string>(props: InlineEditFieldProps<T>) {\n const {\n value,\n onCommit,\n label,\n display,\n isEqual = Object.is,\n placeholder,\n align = \"start\",\n readOnly = false,\n disabledReason: ownDisabledReason,\n commit,\n redact = false,\n formatError,\n editing: editingProp,\n onEditingChange,\n inputProps,\n className,\n labels: labelsProp,\n } = props;\n const editor = props.editor as EditorRender<T> | undefined;\n const labels = useKitLabels(\"inlineEdit\", DEFAULT_INLINE_EDIT_LABELS, labelsProp);\n const disabledReason = useCommitReason(commit, ownDisabledReason);\n const locked = hasContent(disabledReason);\n\n const [openState, setOpenState] = useState(false);\n const open = editingProp ?? openState;\n // Locked (or read-only) wins over an open flag: a stale `editing` must never bring a\n // live commit-on-blur field back on a page that cannot save it.\n const editing = open && !locked && !readOnly;\n const [draft, setDraft] = useState<T>(value);\n const [pending, setPending] = useState(false);\n const [failure, setFailure] = useState<ReactNode>(null);\n const failed = hasContent(failure);\n\n const errorId = useId();\n const reasonId = useId();\n const displayRef = useRef<HTMLButtonElement>(null);\n const editorRef = useRef<HTMLDivElement>(null);\n const alert = useAnnounce({ politeness: \"assertive\" });\n\n // One edit ends once: Enter in a NumberInput commits through its own `onCommit` and\n // then reaches the field's Enter; a save that closes the editor unmounts the focused\n // control, which some browsers answer with a blur. The second ending is a no-op.\n const ended = useRef(false);\n const blurTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n const mounted = useRef(true);\n // The latest draft for the deferred blur check, which runs after a render.\n const draftRef = useRef(draft);\n useEffect(() => {\n draftRef.current = draft;\n });\n useEffect(() => {\n mounted.current = true;\n return () => {\n mounted.current = false;\n if (blurTimer.current) clearTimeout(blurTimer.current);\n };\n }, []);\n\n // Seed the draft when the editor opens — from wherever the open came (the button, a\n // controlled `editing`). During render, like DangerConfirm's wipe, so no frame shows\n // the editor holding the previous edit's text.\n const [wasEditing, setWasEditing] = useState(editing);\n if (wasEditing !== editing) {\n setWasEditing(editing);\n if (editing) {\n setDraft(value);\n setFailure(null);\n setPending(false);\n }\n }\n // An open editor that a lock (or `readOnly`) took away is closed for good, not\n // merely hidden until the lock lifts.\n if (openState && !editing && editingProp === undefined) setOpenState(false);\n\n // Focus follows the edit: into the editor when it opens, back to the value when it\n // closes — but only if focus went down with the editor (Enter, Escape, a save that\n // resolved under it). A save that came from focus LEAVING keeps focus where it went.\n const prevEditing = useRef(editing);\n useEffect(() => {\n const changed = prevEditing.current !== editing;\n prevEditing.current = editing;\n if (!changed) return;\n if (editing) {\n // A fresh edit, which may end once. Here rather than in the seed above: a ref is\n // not written during render, and no event can end the edit before this runs.\n ended.current = false;\n const control = editorRef.current?.querySelector<HTMLElement>(\n \"input:not([type=hidden]), textarea, select, [contenteditable='true'], [tabindex]:not([tabindex='-1']), button\",\n );\n control?.focus();\n if (control instanceof HTMLInputElement || control instanceof HTMLTextAreaElement) control.select();\n return;\n }\n // However it closed — a save, Escape, a lock taking it away — a blur check still\n // queued from the editor has nothing left to save.\n ended.current = true;\n if (blurTimer.current) clearTimeout(blurTimer.current);\n blurTimer.current = null;\n const lost = document.activeElement === null || document.activeElement === document.body;\n if (lost) displayRef.current?.focus();\n }, [editing]);\n\n const setOpen = (next: boolean) => {\n if (editingProp === undefined) setOpenState(next);\n onEditingChange?.(next);\n };\n\n const fail = (caught: unknown) => {\n ended.current = false;\n const message = formatError ? formatError(caught) : labels.failed;\n setFailure(hasContent(message) ? message : labels.failed);\n alert.announce(typeof message === \"string\" && message !== \"\" ? message : labels.failed);\n // Focus back into the editor if the failure left it nowhere (a pending field that\n // the browser let go of); never pulled back from somewhere the user went.\n const active = document.activeElement;\n if (active === null || active === document.body) {\n editorRef.current?.querySelector<HTMLElement>(\"input, textarea, select, [tabindex]\")?.focus();\n }\n };\n\n const save = (next: T = draftRef.current) => {\n if (!editing || ended.current || pending) return;\n ended.current = true;\n if (next !== draftRef.current) setDraft(next);\n if (isEqual(next, value)) {\n setOpen(false);\n return;\n }\n let result: unknown;\n try {\n result = onCommit(next);\n } catch (caught) {\n fail(caught);\n return;\n }\n const promise = asPromise(result);\n if (!promise) {\n setOpen(false);\n return;\n }\n setPending(true);\n setFailure(null);\n promise.then(\n () => {\n if (!mounted.current) return;\n setPending(false);\n setOpen(false);\n },\n (caught: unknown) => {\n if (!mounted.current) return;\n setPending(false);\n fail(caught);\n },\n );\n };\n\n const cancel = () => {\n // A request that has left cannot be called back; Escape waits for it.\n if (!editing || pending) return;\n ended.current = true;\n setOpen(false);\n };\n\n const change = (next: T) => {\n if (pending) return;\n setDraft(next);\n // Now, not after the render: a blur check scheduled before that render reads it.\n draftRef.current = next;\n // The failure described the draft that was sent; once edited it describes nothing\n // on screen, and the next save (or blur) is a fresh attempt.\n setFailure(null);\n };\n\n const onKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {\n if (e.defaultPrevented) return;\n if (e.key === \"Escape\") {\n // Ours, not the dialog's or the popover's around the field: claimed on the kit's\n // Escape stack (Wave 2, DUP-1), and stopped for anything else listening.\n claimEscape(e);\n e.stopPropagation();\n cancel();\n return;\n }\n // Not mid-composition: Enter there picks the IME's candidate (Japanese, Chinese) —\n // and Safari reports the Enter that ended one as keyCode 229 (`isImeEnter`).\n if (e.key === \"Enter\" && !isImeEnter(e) && !e.shiftKey) {\n e.preventDefault();\n save();\n }\n };\n\n // Leaving the editor saves. Deferred one task, and cancelled by any focus that lands\n // back inside: React reports focus moves through PORTALS to the component tree, so a\n // calculator popover or a phone keypad the editor opened counts as inside, which a\n // DOM `contains` check would get wrong. A failure on screen holds the editor: the\n // user is looking at why, and moving away is not a retry.\n const onBlur = (e: FocusEvent<HTMLDivElement>) => {\n if (e.currentTarget.contains(e.relatedTarget as Node | null)) return;\n if (blurTimer.current) clearTimeout(blurTimer.current);\n blurTimer.current = setTimeout(() => {\n blurTimer.current = null;\n if (!mounted.current || failed) return;\n save();\n }, 0);\n };\n const onFocus = () => {\n if (blurTimer.current) clearTimeout(blurTimer.current);\n blurTimer.current = null;\n };\n\n const private_ = redact ? { \"data-private\": true } : undefined;\n const shown = display ? display(value) : value === null || value === undefined ? \"\" : String(value);\n const content = hasContent(shown) ? (\n shown\n ) : (\n <span className=\"text-muted\">{placeholder ?? labels.empty}</span>\n );\n const alignClass = align === \"end\" ? \"text-end\" : \"text-start\";\n\n let body: ReactNode;\n if (readOnly) {\n body = (\n <span {...private_} className={cn(\"block min-w-0\", VALUE_TRUNCATE, alignClass)}>\n {content}\n </span>\n );\n } else if (editing) {\n const describedBy = failed ? errorId : undefined;\n const editorProps: InlineEditorProps<T> = {\n value: draft,\n onChange: change,\n commit: save,\n cancel,\n pending,\n label,\n invalid: failed,\n describedBy,\n inputProps: {\n \"aria-label\": label,\n \"aria-invalid\": failed || undefined,\n \"aria-describedby\": describedBy,\n readOnly: pending,\n },\n };\n body = (\n // A wrapper for the keys and the focus, not a control: the keys belong to the\n // field inside it, which is what has focus.\n // eslint-disable-next-line jsx-a11y/no-static-element-interactions -- see above\n <div\n ref={editorRef}\n {...private_}\n onKeyDown={onKeyDown}\n onBlur={onBlur}\n onFocus={onFocus}\n aria-busy={pending || undefined}\n className=\"min-w-0\"\n >\n <div className=\"flex min-w-0 items-center gap-1.5\">\n <div className=\"min-w-0 flex-1\">\n {editor ? (\n <RenderEditor render={editor} {...editorProps} />\n ) : (\n <Input\n {...inputProps}\n {...editorProps.inputProps}\n value={String(draft ?? \"\")}\n onChange={(e) => change(e.target.value as unknown as T)}\n className={cn(\"px-2 py-1\", alignClass, inputProps?.className)}\n />\n )}\n </div>\n {/* Decorative: `aria-busy` on the wrapper says it. */}\n {pending && <Spinner label={null} className=\"size-4 shrink-0\" />}\n </div>\n {failed && (\n <p id={errorId} className={FIELD_ERROR_CLASS}>\n {failure}\n </p>\n )}\n </div>\n );\n } else if (locked) {\n // Button's `disabledReason` path, spelled out for a button that must look like\n // the value rather than like a Button: focusable, `aria-disabled`, every\n // activation swallowed, the reason in the kit Tooltip and in a `hidden` copy that\n // describes it. The button in a FRAGMENT so the Tooltip does not add the bubble as\n // a second description of the same sentence.\n body = (\n <Tooltip label={disabledReason} className=\"flex w-full min-w-0\">\n <>\n <button\n ref={displayRef}\n type=\"button\"\n {...private_}\n aria-disabled\n aria-describedby={reasonId}\n onClick={(e) => e.preventDefault()}\n className={cn(\n \"block w-full min-w-0 cursor-not-allowed rounded-sm focus:outline-none\",\n VALUE_TRUNCATE,\n FOCUS_RING,\n alignClass,\n )}\n >\n {content}\n </button>\n <span id={reasonId} hidden>\n {disabledReason}\n </span>\n </>\n </Tooltip>\n );\n } else {\n body = (\n // Lazy: a table of these mounts no bubble until one is hovered or focused, and\n // the closed bubble's text is in no ancestor's accessible name.\n <Tooltip label={labels.edit(label)} lazy className=\"flex w-full min-w-0\">\n <button\n ref={displayRef}\n type=\"button\"\n {...private_}\n onClick={() => setOpen(true)}\n className={cn(\n \"block w-full min-w-0 rounded-sm underline-offset-2 hover:underline focus:outline-none\",\n VALUE_TRUNCATE,\n FOCUS_RING,\n alignClass,\n )}\n >\n {content}\n </button>\n </Tooltip>\n );\n }\n\n return (\n <div className={cn(\"min-w-0\", className)} data-editing={editing || undefined}>\n {body}\n {/* Always mounted: a live region that mounts with its message is usually missed. */}\n <span {...alert.regionProps} />\n </div>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/components/inline-edit-field.tsx"],"sourcesContent":["import { useEffect, useId, useRef, useState } from \"react\";\nimport type { FocusEvent, InputHTMLAttributes, KeyboardEvent, ReactNode } from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { hasContent } from \"../lib/presence\";\nimport { useAnnounce } from \"../hooks/use-announce\";\nimport { claimEscape } from \"../hooks/use-dismiss\";\nimport { isImeEnter } from \"../lib/ime\";\nimport { useKitLabels, type LabelOverride } from \"../i18n/kit-labels\";\nimport { Input } from \"./input\";\nimport { Spinner } from \"./spinner\";\nimport { FIELD_ERROR_CLASS } from \"./field-parts\";\nimport { FOCUS_RING } from \"./focus-ring\";\nimport { Tooltip } from \"./tooltip\";\nimport { useCommitReason, type CommitScope } from \"./write-lock\";\nimport { DEFAULT_INLINE_EDIT_LABELS } from \"./inline-edit-field-labels\";\n\n/**\n * Every string {@link InlineEditField} says on its own behalf — the `inlineEdit`\n * namespace of `<UiKitProvider labels>`, overridable per instance through `labels`.\n */\nexport interface InlineEditLabels {\n /** The display button's tooltip and description — what pressing it does. Receives\n * the field's `label` (\"Budget name\" → \"Edit Budget name\"). */\n edit: (label: string) => string;\n /** Shown (and spoken) when `onCommit` rejects and no `formatError` says better. */\n failed: string;\n /** What the display shows for an empty value, when no `placeholder` is given. */\n empty: string;\n}\n\n// Defined in ./inline-edit-field-labels, which holds no component code (0.34, KS U8).\nexport { DEFAULT_INLINE_EDIT_LABELS };\n\n/** What an `editor` render prop is handed: the draft and the means to end the edit. */\nexport interface InlineEditorProps<T> {\n /** The draft — seeded from `value` each time the editor opens. */\n value: T;\n /** Replace the draft (every keystroke). */\n onChange: (next: T) => void;\n /**\n * End the edit and save. Without an argument it saves the draft; WITH one it saves\n * that — for an editor whose own commit hands over a resolved value (`NumberInput`'s\n * `onCommit` gives \"1050\" for a typed \"1100-50\"). Called twice (the editor's own\n * Enter, then the field's), the second call does nothing.\n */\n commit: (next?: T) => void;\n /** End the edit and drop the draft — what Escape does. */\n cancel: () => void;\n /** The save is in flight: make the control read-only (not disabled — it has focus). */\n pending: boolean;\n /** The field's `label`, for the control's accessible name. */\n label: string;\n /** The last save failed and the draft has not been touched since. */\n invalid: boolean;\n /** The id of the failure message while there is one, for `aria-describedby`. */\n describedBy: string | undefined;\n /** The four above as attributes — spread them on a kit field that takes the DOM\n * spelling: `<AmountInput {...p.inputProps} … />`. */\n inputProps: {\n \"aria-label\": string;\n \"aria-invalid\": true | undefined;\n \"aria-describedby\": string | undefined;\n readOnly: boolean;\n };\n}\n\ninterface InlineEditFieldBaseProps<T> {\n /** The saved value — what the display shows, and what the editor opens on. */\n value: T;\n /**\n * Save `next`. Return a promise and the field waits: the editor stays open and\n * read-only with a spinner until it settles, closes when it resolves, and STAYS OPEN\n * with the failure under it when it rejects (or throws) — the typed value is kept for\n * a retry. Not called when the draft equals `value` (see `isEqual`): opening the\n * editor and leaving it unchanged is not a write.\n */\n onCommit: (next: T) => void | Promise<unknown>;\n /** What the value is (\"Budget name\", \"Assigned\") — the editor's accessible name and\n * the subject of the display's \"Edit …\" description. Not drawn: inline, the row or\n * the column header is the visible label. */\n label: string;\n /** The value as the display shows it — a formatted amount, a date. Default: the\n * value as text. */\n display?: (value: T) => ReactNode;\n /** Whether the draft is unchanged, so leaving the editor is not a write. Default\n * `Object.is`. keksdose compares assigned amounts as NUMBERS — \"1100\" is the\n * server's \"1100.0000\". */\n isEqual?: (a: T, b: T) => boolean;\n /** What the display shows for an empty value (`\"\"`, `null`, an empty `display`).\n * Default `labels.empty`. */\n placeholder?: ReactNode;\n /** `\"end\"` for a figure in a right-aligned money column. Default `\"start\"`. */\n align?: \"start\" | \"end\";\n /** Look, do not touch — a guest's view: the value as plain text, no button. */\n readOnly?: boolean;\n /**\n * Why it cannot be edited now — the write lock's sentence, a closed period. The\n * value stays a BUTTON (focusable, `aria-disabled`) with the reason in the kit\n * Tooltip and its description; it opens no editor. An editor that is open when the\n * reason arrives closes, its draft dropped: a field you can type into must be one\n * whose value can be saved (keksdose dev#496).\n */\n disabledReason?: ReactNode;\n /** This field COMMITS — under a locked {@link WriteLockProvider} it takes the\n * `disabledReason` path with the lock's reason (which wins over its own). No\n * provider, or an unlocked one: no effect. */\n commit?: CommitScope;\n /** The value is the user's own data: `data-private` on the display and the editor,\n * so the app's demo-mode blur covers both. */\n redact?: boolean;\n /** Turns a rejection into words. Default `labels.failed`. */\n formatError?: (error: unknown) => ReactNode;\n /** Controlled open state — a row's swipe action that opens the editor from outside\n * (keksdose's assigned cell). Left out, the field keeps its own. */\n editing?: boolean;\n /** Called with the next open state: on open, on a save that resolved, on cancel. */\n onEditingChange?: (editing: boolean) => void;\n /** Attributes for the DEFAULT text editor's `<input>` — `maxLength`, `inputMode`,\n * `autoComplete`. Ignored with `editor`. */\n inputProps?: Omit<InputHTMLAttributes<HTMLInputElement>, \"value\" | \"defaultValue\" | \"onChange\" | \"type\">;\n /** Classes for the root. */\n className?: string;\n labels?: LabelOverride<InlineEditLabels>;\n}\n\ntype EditorRender<T> = (props: InlineEditorProps<T>) => ReactNode;\n\n/**\n * The editor. Optional for a string value — the kit's `Input`, compact — and\n * REQUIRED for anything else, since only the caller knows how to edit a number or an\n * amount. Render a kit field from the props it is handed:\n *\n * ```tsx\n * editor={(p) => (\n * <NumberInput calculator={false} aria-label={p.label} value={p.value} onChange={p.onChange}\n * onCommit={p.commit} aria-invalid={p.invalid || undefined} aria-describedby={p.describedBy} />\n * )}\n * ```\n *\n * The field itself handles Enter (save), Escape (cancel) and focus leaving the editor\n * (save) around whatever is rendered — a key the editor has already handled\n * (`preventDefault`, as a listbox does for its Enter) is left to it. Focus counts as\n * \"inside\" across a portal, so a picker or a phone keypad the editor opens does not\n * end the edit.\n */\nexport type InlineEditFieldProps<T = string> = InlineEditFieldBaseProps<T> &\n ([T] extends [string] ? { editor?: EditorRender<T> } : { editor: EditorRender<T> });\n\n/** Calls the `editor` render prop as a component of its own: the props it is handed\n * close over the field's refs, and a render prop called in the field's own render\n * would read like a ref read during render. Module-level, so the type is stable and\n * the editor never remounts. */\nfunction RenderEditor<T>({ render, ...props }: InlineEditorProps<T> & { render: EditorRender<T> }) {\n return render(props);\n}\n\n/**\n * The value at Large and Extra large (0.32.1, keksdose's 0.32 report;\n * docs/text-size-harmonization.md §4 \"nothing truncates\"): the display button, the\n * locked one and the read-only text wrap instead of ending in \"…\", and break a word too\n * long for the cell, so an IBAN or an e-mail address shows to its last character. The\n * value IS the button's accessible name and the thing being edited: a reader who needs\n * big type cannot recover a cut-off end by squinting. A `large:` class, so a table of\n * these keeps its one-line cells at Normal.\n *\n * 0.33 (§10.17): the kit's one `truncate-until-large` (tokens.css) in place of this\n * file's own `truncate` + `large:whitespace-normal large:break-words`. The utility\n * breaks with `overflow-wrap: anywhere`, which lowers the value's min-content width: in\n * an auto-sized table column (keksdose's budget and invoice lines) give the column a\n * width, or an amount can be squeezed to a digit per line.\n */\nconst VALUE_TRUNCATE = \"truncate-until-large\";\n\n/** `onCommit`'s result, if it is a promise. */\nfunction asPromise(result: unknown): Promise<unknown> | null {\n return result && typeof (result as Promise<unknown>).then === \"function\" ? (result as Promise<unknown>) : null;\n}\n\n/**\n * A value that edits in place: a quiet button showing the value; click it (or Enter\n * on it) and it becomes a field; Enter or leaving the field saves, Escape cancels\n * (keksdose K9).\n *\n * keksdose has three of them by hand — the budget's assigned cell (budget-cells.tsx),\n * the invoice line's value cells (invoice-lines-table.tsx `EditableCell`) and the\n * budget rename (budgets-page.tsx, an `Input` + Save + Cancel with no Escape) — and\n * each had to learn the same rules:\n *\n * - **The display's accessible name is the VALUE.** A cell named \"Edit\" reads a table\n * of \"edit, edit, edit\"; the action is the description instead (\"Edit Assigned\"),\n * in the kit Tooltip on hover and focus.\n * - **An unchanged draft is not a write.** Opening the editor and leaving it was a PUT\n * in keksdose until `isEqual` compared numbers, and on a sealed budget an outbox row\n * for a change nobody made.\n * - **A failed save keeps the editor.** The typed value is the user's work; the\n * failure is shown under it (attached with `aria-describedby`, and spoken), and the\n * next keystroke clears it. Escape still drops the draft.\n * - **Locked means a disabled BUTTON, not a disabled field** (keksdose dev#496): at\n * rest a real row shows this button, so a locked page shows the same, with the\n * reason — never a field that takes keys it cannot save.\n *\n * Inside a {@link DataTable} cell it needs nothing extra: the table already leaves a\n * click or a key on a control of the cell to that control. Focus returns to the value\n * after Enter or Escape; after a save that came from focus leaving, it stays where the\n * user put it.\n *\n * ```tsx\n * <InlineEditField label={t(\"budgets.name\")} value={budget.name}\n * onCommit={(name) => rename.mutateAsync({ id: budget.id, name })} inputProps={{ maxLength: 120 }} />\n * ```\n */\nexport function InlineEditField<T = string>(props: InlineEditFieldProps<T>) {\n const {\n value,\n onCommit,\n label,\n display,\n isEqual = Object.is,\n placeholder,\n align = \"start\",\n readOnly = false,\n disabledReason: ownDisabledReason,\n commit,\n redact = false,\n formatError,\n editing: editingProp,\n onEditingChange,\n inputProps,\n className,\n labels: labelsProp,\n } = props;\n const editor = props.editor as EditorRender<T> | undefined;\n const labels = useKitLabels(\"inlineEdit\", DEFAULT_INLINE_EDIT_LABELS, labelsProp);\n const disabledReason = useCommitReason(commit, ownDisabledReason);\n const locked = hasContent(disabledReason);\n\n const [openState, setOpenState] = useState(false);\n const open = editingProp ?? openState;\n // Locked (or read-only) wins over an open flag: a stale `editing` must never bring a\n // live commit-on-blur field back on a page that cannot save it.\n const editing = open && !locked && !readOnly;\n const [draft, setDraft] = useState<T>(value);\n const [pending, setPending] = useState(false);\n const [failure, setFailure] = useState<ReactNode>(null);\n const failed = hasContent(failure);\n\n const errorId = useId();\n const reasonId = useId();\n const displayRef = useRef<HTMLButtonElement>(null);\n const editorRef = useRef<HTMLDivElement>(null);\n const alert = useAnnounce({ politeness: \"assertive\" });\n\n // One edit ends once: Enter in a NumberInput commits through its own `onCommit` and\n // then reaches the field's Enter; a save that closes the editor unmounts the focused\n // control, which some browsers answer with a blur. The second ending is a no-op.\n const ended = useRef(false);\n const blurTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n const mounted = useRef(true);\n // The latest draft for the deferred blur check, which runs after a render.\n const draftRef = useRef(draft);\n useEffect(() => {\n draftRef.current = draft;\n });\n useEffect(() => {\n mounted.current = true;\n return () => {\n mounted.current = false;\n if (blurTimer.current) clearTimeout(blurTimer.current);\n };\n }, []);\n\n // Seed the draft when the editor opens — from wherever the open came (the button, a\n // controlled `editing`). During render, like DangerConfirm's wipe, so no frame shows\n // the editor holding the previous edit's text.\n const [wasEditing, setWasEditing] = useState(editing);\n if (wasEditing !== editing) {\n setWasEditing(editing);\n if (editing) {\n setDraft(value);\n setFailure(null);\n setPending(false);\n }\n }\n // An open editor that a lock (or `readOnly`) took away is closed for good, not\n // merely hidden until the lock lifts.\n if (openState && !editing && editingProp === undefined) setOpenState(false);\n\n // Focus follows the edit: into the editor when it opens, back to the value when it\n // closes — but only if focus went down with the editor (Enter, Escape, a save that\n // resolved under it). A save that came from focus LEAVING keeps focus where it went.\n const prevEditing = useRef(editing);\n useEffect(() => {\n const changed = prevEditing.current !== editing;\n prevEditing.current = editing;\n if (!changed) return;\n if (editing) {\n // A fresh edit, which may end once. Here rather than in the seed above: a ref is\n // not written during render, and no event can end the edit before this runs.\n ended.current = false;\n const control = editorRef.current?.querySelector<HTMLElement>(\n \"input:not([type=hidden]), textarea, select, [contenteditable='true'], [tabindex]:not([tabindex='-1']), button\",\n );\n control?.focus();\n if (control instanceof HTMLInputElement || control instanceof HTMLTextAreaElement) control.select();\n return;\n }\n // However it closed — a save, Escape, a lock taking it away — a blur check still\n // queued from the editor has nothing left to save.\n ended.current = true;\n if (blurTimer.current) clearTimeout(blurTimer.current);\n blurTimer.current = null;\n const lost = document.activeElement === null || document.activeElement === document.body;\n if (lost) displayRef.current?.focus();\n }, [editing]);\n\n const setOpen = (next: boolean) => {\n if (editingProp === undefined) setOpenState(next);\n onEditingChange?.(next);\n };\n\n const fail = (caught: unknown) => {\n ended.current = false;\n const message = formatError ? formatError(caught) : labels.failed;\n setFailure(hasContent(message) ? message : labels.failed);\n alert.announce(typeof message === \"string\" && message !== \"\" ? message : labels.failed);\n // Focus back into the editor if the failure left it nowhere (a pending field that\n // the browser let go of); never pulled back from somewhere the user went.\n const active = document.activeElement;\n if (active === null || active === document.body) {\n editorRef.current?.querySelector<HTMLElement>(\"input, textarea, select, [tabindex]\")?.focus();\n }\n };\n\n const save = (next: T = draftRef.current) => {\n if (!editing || ended.current || pending) return;\n ended.current = true;\n if (next !== draftRef.current) setDraft(next);\n if (isEqual(next, value)) {\n setOpen(false);\n return;\n }\n let result: unknown;\n try {\n result = onCommit(next);\n } catch (caught) {\n fail(caught);\n return;\n }\n const promise = asPromise(result);\n if (!promise) {\n setOpen(false);\n return;\n }\n setPending(true);\n setFailure(null);\n promise.then(\n () => {\n if (!mounted.current) return;\n setPending(false);\n setOpen(false);\n },\n (caught: unknown) => {\n if (!mounted.current) return;\n setPending(false);\n fail(caught);\n },\n );\n };\n\n const cancel = () => {\n // A request that has left cannot be called back; Escape waits for it.\n if (!editing || pending) return;\n ended.current = true;\n setOpen(false);\n };\n\n const change = (next: T) => {\n if (pending) return;\n setDraft(next);\n // Now, not after the render: a blur check scheduled before that render reads it.\n draftRef.current = next;\n // The failure described the draft that was sent; once edited it describes nothing\n // on screen, and the next save (or blur) is a fresh attempt.\n setFailure(null);\n };\n\n const onKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {\n if (e.defaultPrevented) return;\n if (e.key === \"Escape\") {\n // Ours, not the dialog's or the popover's around the field: claimed on the kit's\n // Escape stack (Wave 2, DUP-1), and stopped for anything else listening.\n claimEscape(e);\n e.stopPropagation();\n cancel();\n return;\n }\n // Not mid-composition: Enter there picks the IME's candidate (Japanese, Chinese) —\n // and Safari reports the Enter that ended one as keyCode 229 (`isImeEnter`).\n if (e.key === \"Enter\" && !isImeEnter(e) && !e.shiftKey) {\n e.preventDefault();\n save();\n }\n };\n\n // Leaving the editor saves. Deferred one task, and cancelled by any focus that lands\n // back inside: React reports focus moves through PORTALS to the component tree, so a\n // calculator popover or a phone keypad the editor opened counts as inside, which a\n // DOM `contains` check would get wrong. A failure on screen holds the editor: the\n // user is looking at why, and moving away is not a retry.\n const onBlur = (e: FocusEvent<HTMLDivElement>) => {\n if (e.currentTarget.contains(e.relatedTarget as Node | null)) return;\n if (blurTimer.current) clearTimeout(blurTimer.current);\n blurTimer.current = setTimeout(() => {\n blurTimer.current = null;\n if (!mounted.current || failed) return;\n save();\n }, 0);\n };\n const onFocus = () => {\n if (blurTimer.current) clearTimeout(blurTimer.current);\n blurTimer.current = null;\n };\n\n const private_ = redact ? { \"data-private\": true } : undefined;\n const shown = display ? display(value) : value === null || value === undefined ? \"\" : String(value);\n const content = hasContent(shown) ? shown : <span className=\"text-muted\">{placeholder ?? labels.empty}</span>;\n const alignClass = align === \"end\" ? \"text-end\" : \"text-start\";\n\n let body: ReactNode;\n if (readOnly) {\n body = (\n <span {...private_} className={cn(\"block min-w-0\", VALUE_TRUNCATE, alignClass)}>\n {content}\n </span>\n );\n } else if (editing) {\n const describedBy = failed ? errorId : undefined;\n const editorProps: InlineEditorProps<T> = {\n value: draft,\n onChange: change,\n commit: save,\n cancel,\n pending,\n label,\n invalid: failed,\n describedBy,\n inputProps: {\n \"aria-label\": label,\n \"aria-invalid\": failed || undefined,\n \"aria-describedby\": describedBy,\n readOnly: pending,\n },\n };\n body = (\n // A wrapper for the keys and the focus, not a control: the keys belong to the\n // field inside it, which is what has focus.\n // eslint-disable-next-line jsx-a11y/no-static-element-interactions -- see above\n <div\n ref={editorRef}\n {...private_}\n onKeyDown={onKeyDown}\n onBlur={onBlur}\n onFocus={onFocus}\n aria-busy={pending || undefined}\n className=\"min-w-0\"\n >\n <div className=\"flex min-w-0 items-center gap-1.5\">\n <div className=\"min-w-0 flex-1\">\n {editor ? (\n <RenderEditor render={editor} {...editorProps} />\n ) : (\n <Input\n {...inputProps}\n {...editorProps.inputProps}\n value={String(draft ?? \"\")}\n onChange={(e) => change(e.target.value as unknown as T)}\n className={cn(\"px-2 py-1\", alignClass, inputProps?.className)}\n />\n )}\n </div>\n {/* Decorative: `aria-busy` on the wrapper says it. */}\n {pending && <Spinner label={null} className=\"size-4 shrink-0\" />}\n </div>\n {failed && (\n <p id={errorId} className={FIELD_ERROR_CLASS}>\n {failure}\n </p>\n )}\n </div>\n );\n } else if (locked) {\n // Button's `disabledReason` path, spelled out for a button that must look like\n // the value rather than like a Button: focusable, `aria-disabled`, every\n // activation swallowed, the reason in the kit Tooltip and in a `hidden` copy that\n // describes it. The button in a FRAGMENT so the Tooltip does not add the bubble as\n // a second description of the same sentence.\n body = (\n <Tooltip label={disabledReason} className=\"flex w-full min-w-0\">\n <>\n <button\n ref={displayRef}\n type=\"button\"\n {...private_}\n aria-disabled\n aria-describedby={reasonId}\n onClick={(e) => e.preventDefault()}\n className={cn(\n \"block w-full min-w-0 cursor-not-allowed rounded-sm focus:outline-none\",\n VALUE_TRUNCATE,\n FOCUS_RING,\n alignClass,\n )}\n >\n {content}\n </button>\n <span id={reasonId} hidden>\n {disabledReason}\n </span>\n </>\n </Tooltip>\n );\n } else {\n body = (\n // Lazy: a table of these mounts no bubble until one is hovered or focused, and\n // the closed bubble's text is in no ancestor's accessible name.\n <Tooltip label={labels.edit(label)} lazy className=\"flex w-full min-w-0\">\n <button\n ref={displayRef}\n type=\"button\"\n {...private_}\n onClick={() => setOpen(true)}\n className={cn(\n \"block w-full min-w-0 rounded-sm underline-offset-2 hover:underline focus:outline-none\",\n VALUE_TRUNCATE,\n FOCUS_RING,\n alignClass,\n )}\n >\n {content}\n </button>\n </Tooltip>\n );\n }\n\n return (\n <div className={cn(\"min-w-0\", className)} data-editing={editing || undefined}>\n {body}\n {/* Always mounted: a live region that mounts with its message is usually missed. */}\n <span {...alert.regionProps} />\n </div>\n );\n}\n"],"mappings":"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","names":[]}
@@ -21,7 +21,7 @@ import {
21
21
  useActiveOptionScroll
22
22
  } from "./combobox-core.js";
23
23
  import { DEFAULT_COMBOBOX_LABELS, useKitLabels, useKitLocale } from "../i18n/kit-labels.js";
24
- import { foldForSearch, rankMatch } from "../lib/text-match.js";
24
+ import { matchesSearch, rankMatch, searchWords } from "../lib/text-match.js";
25
25
  import {
26
26
  EndHintRow,
27
27
  FieldCaption,
@@ -119,14 +119,14 @@ function InlineEntityCombobox(props) {
119
119
  const matches = useMemo(() => {
120
120
  let ranked = options;
121
121
  if (query) {
122
- const q = foldForSearch(query, locale);
123
- const hit = (s) => s !== void 0 && foldForSearch(s, locale).includes(q);
122
+ const words = searchWords(query, locale);
123
+ const hit = (o) => matchesSearch(words, [o.label, o.sublabel], locale);
124
124
  const leads = (o) => {
125
125
  const rank = rankMatch(o.label, query, { locale });
126
126
  return rank === 0 || rank === 1;
127
127
  };
128
128
  const starts = options.filter(leads);
129
- const rest2 = options.filter((o) => !leads(o) && (hit(o.label) || hit(o.sublabel)));
129
+ const rest2 = options.filter((o) => !leads(o) && hit(o));
130
130
  ranked = [...starts, ...rest2];
131
131
  }
132
132
  if (!ranked.some((o) => o.group)) return ranked;
@@ -178,9 +178,7 @@ function InlineEntityCombobox(props) {
178
178
  if (!q) {
179
179
  if (value != null) onChange(clearValue);
180
180
  } else {
181
- const hits = options.filter(
182
- (o) => !o.disabled && o.label.toLowerCase() === q.toLowerCase()
183
- );
181
+ const hits = options.filter((o) => !o.disabled && o.label.toLowerCase() === q.toLowerCase());
184
182
  const ids = new Set(hits.map((h) => h.value));
185
183
  if (ids.size === 1 && hits[0].value !== value) onChange(hits[0].value);
186
184
  }
@@ -389,11 +387,7 @@ function InlineEntityCombobox(props) {
389
387
  "aria-selected": false,
390
388
  "aria-disabled": createLocked || void 0,
391
389
  onClick: create,
392
- className: cn(
393
- SHEET_ROW_CLASS,
394
- CREATE_ROW_CLASS,
395
- createLocked && DISABLED_ROW_CLASS
396
- ),
390
+ className: cn(SHEET_ROW_CLASS, CREATE_ROW_CLASS, createLocked && DISABLED_ROW_CLASS),
397
391
  children: createRowContent
398
392
  }
399
393
  ) }),