@eifi1/ui-kit 0.51.4 → 0.51.6

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 (759) hide show
  1. package/README.md +5 -5
  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 +1 -4
  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 +10 -8
  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.map +1 -1
  106. package/dist/components/data-table-groups.js +3 -1
  107. package/dist/components/data-table-groups.js.map +1 -1
  108. package/dist/components/data-table-head.js +44 -56
  109. package/dist/components/data-table-head.js.map +1 -1
  110. package/dist/components/data-table-mobile.js +31 -44
  111. package/dist/components/data-table-mobile.js.map +1 -1
  112. package/dist/components/data-table-pagination.js.map +1 -1
  113. package/dist/components/data-table-row-actions.js +19 -16
  114. package/dist/components/data-table-row-actions.js.map +1 -1
  115. package/dist/components/data-table-row.js.map +1 -1
  116. package/dist/components/data-table-shared.js.map +1 -1
  117. package/dist/components/data-table-sort.js.map +1 -1
  118. package/dist/components/data-table-virtual.js +4 -1
  119. package/dist/components/data-table-virtual.js.map +1 -1
  120. package/dist/components/data-table.js +30 -39
  121. package/dist/components/data-table.js.map +1 -1
  122. package/dist/components/date-picker.js +1 -6
  123. package/dist/components/date-picker.js.map +1 -1
  124. package/dist/components/date-range-picker.js.map +1 -1
  125. package/dist/components/description-list.js +10 -10
  126. package/dist/components/description-list.js.map +1 -1
  127. package/dist/components/dialog-frame.js +9 -2
  128. package/dist/components/dialog-frame.js.map +1 -1
  129. package/dist/components/disclosure.js +17 -27
  130. package/dist/components/disclosure.js.map +1 -1
  131. package/dist/components/dropdown.js +25 -35
  132. package/dist/components/dropdown.js.map +1 -1
  133. package/dist/components/empty-state.js.map +1 -1
  134. package/dist/components/entity-combobox.js.map +1 -1
  135. package/dist/components/error-boundary.js.map +1 -1
  136. package/dist/components/field-base.js +5 -5
  137. package/dist/components/field-base.js.map +1 -1
  138. package/dist/components/field-parts.d.ts +1 -1
  139. package/dist/components/field-parts.js +10 -14
  140. package/dist/components/field-parts.js.map +1 -1
  141. package/dist/components/field-sync.js +8 -10
  142. package/dist/components/field-sync.js.map +1 -1
  143. package/dist/components/file-button.js.map +1 -1
  144. package/dist/components/file-dropzone.js.map +1 -1
  145. package/dist/components/floating-panel.js +4 -1
  146. package/dist/components/floating-panel.js.map +1 -1
  147. package/dist/components/focus-ring.js.map +1 -1
  148. package/dist/components/form-actions.js.map +1 -1
  149. package/dist/components/grouped-picker.js +6 -8
  150. package/dist/components/grouped-picker.js.map +1 -1
  151. package/dist/components/hover-menu.js +1 -4
  152. package/dist/components/hover-menu.js.map +1 -1
  153. package/dist/components/iban-input-labels.js.map +1 -1
  154. package/dist/components/icon-button.js +4 -1
  155. package/dist/components/icon-button.js.map +1 -1
  156. package/dist/components/icon-picker.js +2 -7
  157. package/dist/components/icon-picker.js.map +1 -1
  158. package/dist/components/image-grid.js +41 -50
  159. package/dist/components/image-grid.js.map +1 -1
  160. package/dist/components/inline-edit-field.js.map +1 -1
  161. package/dist/components/inline-entity-combobox.js +2 -8
  162. package/dist/components/inline-entity-combobox.js.map +1 -1
  163. package/dist/components/input.js +14 -2
  164. package/dist/components/input.js.map +1 -1
  165. package/dist/components/jump-calendar.js +1 -7
  166. package/dist/components/jump-calendar.js.map +1 -1
  167. package/dist/components/legal-labels.js.map +1 -1
  168. package/dist/components/legal-page.js +14 -2
  169. package/dist/components/legal-page.js.map +1 -1
  170. package/dist/components/legal.js +12 -2
  171. package/dist/components/legal.js.map +1 -1
  172. package/dist/components/lightbox.js.map +1 -1
  173. package/dist/components/line-items.js +26 -36
  174. package/dist/components/line-items.js.map +1 -1
  175. package/dist/components/list.js +22 -10
  176. package/dist/components/list.js.map +1 -1
  177. package/dist/components/measured-grid.js +29 -53
  178. package/dist/components/measured-grid.js.map +1 -1
  179. package/dist/components/menu-item.js.map +1 -1
  180. package/dist/components/mini-calendar.js +13 -31
  181. package/dist/components/mini-calendar.js.map +1 -1
  182. package/dist/components/money-field.js.map +1 -1
  183. package/dist/components/month-picker.js +10 -21
  184. package/dist/components/month-picker.js.map +1 -1
  185. package/dist/components/multi-entity-combobox.js +3 -16
  186. package/dist/components/multi-entity-combobox.js.map +1 -1
  187. package/dist/components/multi-select.js.map +1 -1
  188. package/dist/components/nav-pills.js +10 -1
  189. package/dist/components/nav-pills.js.map +1 -1
  190. package/dist/components/number-field.js.map +1 -1
  191. package/dist/components/number-input.js +1 -7
  192. package/dist/components/number-input.js.map +1 -1
  193. package/dist/components/numpad-sheet.js +1 -4
  194. package/dist/components/numpad-sheet.js.map +1 -1
  195. package/dist/components/one-time-code-input.js +1 -7
  196. package/dist/components/one-time-code-input.js.map +1 -1
  197. package/dist/components/page-contents.js +13 -22
  198. package/dist/components/page-contents.js.map +1 -1
  199. package/dist/components/page-header.js +1 -11
  200. package/dist/components/page-header.js.map +1 -1
  201. package/dist/components/password-strength-labels.js.map +1 -1
  202. package/dist/components/password-strength.js +2 -8
  203. package/dist/components/password-strength.js.map +1 -1
  204. package/dist/components/phone-input.js +42 -57
  205. package/dist/components/phone-input.js.map +1 -1
  206. package/dist/components/picker-sheet.js.map +1 -1
  207. package/dist/components/pie-chart.js +1 -8
  208. package/dist/components/pie-chart.js.map +1 -1
  209. package/dist/components/popover.js.map +1 -1
  210. package/dist/components/progress-bar.js +16 -9
  211. package/dist/components/progress-bar.js.map +1 -1
  212. package/dist/components/qr-code.js +1 -8
  213. package/dist/components/qr-code.js.map +1 -1
  214. package/dist/components/row-actions.js +5 -1
  215. package/dist/components/row-actions.js.map +1 -1
  216. package/dist/components/search-field.js.map +1 -1
  217. package/dist/components/select.js +28 -38
  218. package/dist/components/select.js.map +1 -1
  219. package/dist/components/separator.js +1 -5
  220. package/dist/components/separator.js.map +1 -1
  221. package/dist/components/series-chart-axes.js +4 -1
  222. package/dist/components/series-chart-axes.js.map +1 -1
  223. package/dist/components/series-chart-budget.js.map +1 -1
  224. package/dist/components/series-chart-ticks.js.map +1 -1
  225. package/dist/components/series-chart.js +29 -45
  226. package/dist/components/series-chart.js.map +1 -1
  227. package/dist/components/settings-fields.js +21 -17
  228. package/dist/components/settings-fields.js.map +1 -1
  229. package/dist/components/share-card.js +4 -9
  230. package/dist/components/share-card.js.map +1 -1
  231. package/dist/components/sign-chip.js.map +1 -1
  232. package/dist/components/signature-pad.js +4 -13
  233. package/dist/components/signature-pad.js.map +1 -1
  234. package/dist/components/signed-amount.js +12 -5
  235. package/dist/components/signed-amount.js.map +1 -1
  236. package/dist/components/skeleton.js +2 -17
  237. package/dist/components/skeleton.js.map +1 -1
  238. package/dist/components/slider.js +13 -36
  239. package/dist/components/slider.js.map +1 -1
  240. package/dist/components/sparkline.js.map +1 -1
  241. package/dist/components/stat-tile.js +14 -10
  242. package/dist/components/stat-tile.js.map +1 -1
  243. package/dist/components/swatch-picker.js +14 -4
  244. package/dist/components/swatch-picker.js.map +1 -1
  245. package/dist/components/swipeable-row.d.ts +1 -1
  246. package/dist/components/swipeable-row.js +1 -8
  247. package/dist/components/swipeable-row.js.map +1 -1
  248. package/dist/components/table-skeleton.js +4 -1
  249. package/dist/components/table-skeleton.js.map +1 -1
  250. package/dist/components/table.js +12 -9
  251. package/dist/components/table.js.map +1 -1
  252. package/dist/components/tabs.js +5 -24
  253. package/dist/components/tabs.js.map +1 -1
  254. package/dist/components/text-link.js.map +1 -1
  255. package/dist/components/text.js.map +1 -1
  256. package/dist/components/tile-radio.js +1 -7
  257. package/dist/components/tile-radio.js.map +1 -1
  258. package/dist/components/time-input.js.map +1 -1
  259. package/dist/components/toast.js.map +1 -1
  260. package/dist/components/toggle-group.js +6 -1
  261. package/dist/components/toggle-group.js.map +1 -1
  262. package/dist/components/toggle-legend.js +2 -11
  263. package/dist/components/toggle-legend.js.map +1 -1
  264. package/dist/components/tooltip.js +2 -22
  265. package/dist/components/tooltip.js.map +1 -1
  266. package/dist/components/translation-review-editor.js +1 -4
  267. package/dist/components/translation-review-editor.js.map +1 -1
  268. package/dist/components/translation-review-parts.d.ts +1 -1
  269. package/dist/components/translation-review-parts.js +7 -1
  270. package/dist/components/translation-review-parts.js.map +1 -1
  271. package/dist/components/translation-review.js +18 -7
  272. package/dist/components/translation-review.js.map +1 -1
  273. package/dist/components/tree-view.js +14 -10
  274. package/dist/components/tree-view.js.map +1 -1
  275. package/dist/components/treemap.d.ts +24 -2
  276. package/dist/components/treemap.js +28 -4
  277. package/dist/components/treemap.js.map +1 -1
  278. package/dist/components/trigger-aria.js.map +1 -1
  279. package/dist/components/use-table-state.js +14 -11
  280. package/dist/components/use-table-state.js.map +1 -1
  281. package/dist/components/user-avatar.js +3 -26
  282. package/dist/components/user-avatar.js.map +1 -1
  283. package/dist/demo/demo-start.js +8 -4
  284. package/dist/demo/demo-start.js.map +1 -1
  285. package/dist/eslint/index.js +1 -1
  286. package/dist/feedback/feedback-attachment-labels.js.map +1 -1
  287. package/dist/feedback/feedback-attachment.js +1 -8
  288. package/dist/feedback/feedback-attachment.js.map +1 -1
  289. package/dist/feedback/feedback-capture.d.ts +1 -1
  290. package/dist/feedback/feedback-capture.js +3 -1
  291. package/dist/feedback/feedback-capture.js.map +1 -1
  292. package/dist/feedback/feedback-comment-thread.js +27 -13
  293. package/dist/feedback/feedback-comment-thread.js.map +1 -1
  294. package/dist/feedback/feedback-context.js +1 -4
  295. package/dist/feedback/feedback-context.js.map +1 -1
  296. package/dist/feedback/feedback-dialog.js.map +1 -1
  297. package/dist/feedback/feedback-inbox.js.map +1 -1
  298. package/dist/feedback/feedback-public-list.js +7 -1
  299. package/dist/feedback/feedback-public-list.js.map +1 -1
  300. package/dist/feedback/feedback-record.js +1 -5
  301. package/dist/feedback/feedback-record.js.map +1 -1
  302. package/dist/feedback/feedback-replies.js +25 -10
  303. package/dist/feedback/feedback-replies.js.map +1 -1
  304. package/dist/feedback/feedback-row-detail.js.map +1 -1
  305. package/dist/feedback/feedback-status-undo.js.map +1 -1
  306. package/dist/feedback/feedback-submit-dialog.js.map +1 -1
  307. package/dist/feedback/feedback-table.js +4 -1
  308. package/dist/feedback/feedback-table.js.map +1 -1
  309. package/dist/feedback/feedback-thread.js +2 -13
  310. package/dist/feedback/feedback-thread.js.map +1 -1
  311. package/dist/feedback/feedback-vocabulary.js.map +1 -1
  312. package/dist/feedback/feedback-vote-button.js.map +1 -1
  313. package/dist/hooks/use-anchored-rect.js.map +1 -1
  314. package/dist/hooks/use-document-title.js.map +1 -1
  315. package/dist/hooks/use-file-drop.js.map +1 -1
  316. package/dist/hooks/use-file-picker-labels.js.map +1 -1
  317. package/dist/hooks/use-file-picker.js.map +1 -1
  318. package/dist/hooks/use-focus-trap.js.map +1 -1
  319. package/dist/hooks/use-overlay-history.js +10 -5
  320. package/dist/hooks/use-overlay-history.js.map +1 -1
  321. package/dist/hooks/use-row-swipe.d.ts +1 -1
  322. package/dist/hooks/use-row-swipe.js +1 -6
  323. package/dist/hooks/use-row-swipe.js.map +1 -1
  324. package/dist/i18n/defaults.js +1 -4
  325. package/dist/i18n/defaults.js.map +1 -1
  326. package/dist/i18n/german.js +4 -1
  327. package/dist/i18n/german.js.map +1 -1
  328. package/dist/i18n/kit-labels.js +2 -8
  329. package/dist/i18n/kit-labels.js.map +1 -1
  330. package/dist/i18n/languages.js.map +1 -1
  331. package/dist/i18n/locales/en.js +4 -1
  332. package/dist/i18n/locales/en.js.map +2 -2
  333. package/dist/i18n/locales/es.js +4 -1
  334. package/dist/i18n/locales/es.js.map +1 -1
  335. package/dist/i18n/locales/fr.js +4 -1
  336. package/dist/i18n/locales/fr.js.map +1 -1
  337. package/dist/i18n/locales/hu.js +4 -1
  338. package/dist/i18n/locales/hu.js.map +1 -1
  339. package/dist/i18n/locales/it.js +4 -1
  340. package/dist/i18n/locales/it.js.map +1 -1
  341. package/dist/i18n/locales/zh.js.map +1 -1
  342. package/dist/i18n/plurals.js.map +1 -1
  343. package/dist/i18n/review.js +13 -7
  344. package/dist/i18n/review.js.map +1 -1
  345. package/dist/i18n/shared-labels.js.map +1 -1
  346. package/dist/index.d.ts +16 -16
  347. package/dist/index.js +40 -23
  348. package/dist/index.js.map +1 -1
  349. package/dist/landing/access.js +1 -4
  350. package/dist/landing/access.js.map +1 -1
  351. package/dist/landing/landing-sections.js +35 -38
  352. package/dist/landing/landing-sections.js.map +1 -1
  353. package/dist/landing/page-seo.js.map +1 -1
  354. package/dist/lib/calc.js.map +1 -1
  355. package/dist/lib/clipboard-minus.js.map +1 -1
  356. package/dist/lib/dates.js.map +1 -1
  357. package/dist/lib/decimal-marks.js.map +1 -1
  358. package/dist/lib/device-settings.js +2 -1
  359. package/dist/lib/device-settings.js.map +1 -1
  360. package/dist/lib/direction.js.map +1 -1
  361. package/dist/lib/iban.js.map +1 -1
  362. package/dist/lib/logger.js +1 -5
  363. package/dist/lib/logger.js.map +1 -1
  364. package/dist/lib/person-name.js.map +1 -1
  365. package/dist/lib/phone.js.map +1 -1
  366. package/dist/lib/qr-encode.js +344 -8
  367. package/dist/lib/qr-encode.js.map +1 -1
  368. package/dist/lib/session-refresh.js.map +1 -1
  369. package/dist/lib/table-text.js +15 -3
  370. package/dist/lib/table-text.js.map +1 -1
  371. package/dist/lib/text-match.d.ts +4 -0
  372. package/dist/lib/text-match.js.map +1 -1
  373. package/dist/lib/translation-review.js.map +1 -1
  374. package/dist/notifications/notification-inbox.js +63 -44
  375. package/dist/notifications/notification-inbox.js.map +1 -1
  376. package/dist/notifications/notification-parts.js.map +1 -1
  377. package/dist/notifications/notification-types-setting.js.map +1 -1
  378. package/dist/notifications/notifications-labels.js.map +1 -1
  379. package/dist/notifications/push-device-setting.js +6 -1
  380. package/dist/notifications/push-device-setting.js.map +1 -1
  381. package/dist/notifications/push-devices-setting.js.map +1 -1
  382. package/dist/notifications/push-worker.js.map +1 -1
  383. package/dist/notifications/quiet-hours-setting.js.map +1 -1
  384. package/dist/notifications/use-push-device.js +4 -1
  385. package/dist/notifications/use-push-device.js.map +1 -1
  386. package/dist/offline/pwa-update-prompt.d.ts +1 -1
  387. package/dist/offline/pwa-update-prompt.js +7 -1
  388. package/dist/offline/pwa-update-prompt.js.map +1 -1
  389. package/dist/offline/query-persistence.js.map +1 -1
  390. package/dist/offline.d.ts +1 -1
  391. package/dist/offline.js +6 -1
  392. package/dist/offline.js.map +1 -1
  393. package/dist/pay/pay.css +8 -1
  394. package/dist/rhf/fields.d.ts +1 -2
  395. package/dist/rhf/fields.js +1 -10
  396. package/dist/rhf/fields.js.map +1 -1
  397. package/dist/rhf/form-core.js +1 -4
  398. package/dist/rhf/form-core.js.map +1 -1
  399. package/dist/rhf/line-items.js.map +1 -1
  400. package/dist/rhf/use-rhf-wizard-step.js.map +1 -1
  401. package/dist/search/command-palette.js +71 -62
  402. package/dist/search/command-palette.js.map +1 -1
  403. package/dist/search/global-search.js +34 -5
  404. package/dist/search/global-search.js.map +1 -1
  405. package/dist/search/search-index.js.map +1 -1
  406. package/dist/settings/device-reset-setting.js +1 -6
  407. package/dist/settings/device-reset-setting.js.map +1 -1
  408. package/dist/settings/settings-heading.js.map +1 -1
  409. package/dist/settings/settings-layout.js +9 -20
  410. package/dist/settings/settings-layout.js.map +1 -1
  411. package/dist/settings/use-settings-route.js.map +1 -1
  412. package/dist/shell/app-shell.js +62 -31
  413. package/dist/shell/app-shell.js.map +1 -1
  414. package/dist/shell/auth-layout.js +1 -10
  415. package/dist/shell/auth-layout.js.map +1 -1
  416. package/dist/shell/dev-menu.js +12 -6
  417. package/dist/shell/dev-menu.js.map +1 -1
  418. package/dist/shell/option-switcher-menu.js.map +1 -1
  419. package/dist/shell/sync-status-indicator.js +7 -6
  420. package/dist/shell/sync-status-indicator.js.map +1 -1
  421. package/dist/shell/top-bar-parts-labels.js.map +1 -1
  422. package/dist/shell/topbar-action-menu.js.map +1 -1
  423. package/dist/shell/topbar-controls.js +1 -10
  424. package/dist/shell/topbar-controls.js.map +1 -1
  425. package/dist/shell/tour-menu.js +9 -1
  426. package/dist/shell/tour-menu.js.map +1 -1
  427. package/dist/sync/db.js.map +1 -1
  428. package/dist/sync/engine.js +24 -6
  429. package/dist/sync/engine.js.map +1 -1
  430. package/dist/sync.d.ts +2 -2
  431. package/dist/sync.js +7 -1
  432. package/dist/sync.js.map +1 -1
  433. package/dist/testing/css-variables.js.map +1 -1
  434. package/dist/testing/index.d.ts +1 -1
  435. package/dist/testing/index.js +1 -3
  436. package/dist/testing/index.js.map +1 -1
  437. package/dist/testing/react.js.map +1 -1
  438. package/dist/testing/viewport.js +7 -4
  439. package/dist/testing/viewport.js.map +1 -1
  440. package/dist/theme/contrast-tokens.js +13 -2
  441. package/dist/theme/contrast-tokens.js.map +1 -1
  442. package/dist/theme/contrast.d.ts +2 -2
  443. package/dist/theme/palette-derive.d.ts +1 -1
  444. package/dist/theme/palette-derive.js +18 -29
  445. package/dist/theme/palette-derive.js.map +1 -1
  446. package/dist/theme/palette-presets.js.map +1 -1
  447. package/dist/theme/palette-store.js.map +1 -1
  448. package/dist/theme/text-size.js.map +1 -1
  449. package/dist/theme/theme-store.js +1 -3
  450. package/dist/theme/theme-store.js.map +1 -1
  451. package/dist/tour/tour-catalogue.js.map +1 -1
  452. package/dist/tour/tour-dock.js +5 -1
  453. package/dist/tour/tour-dock.js.map +1 -1
  454. package/dist/tour/tour-engine.js +1 -4
  455. package/dist/tour/tour-engine.js.map +1 -1
  456. package/dist/tour/tour.js +21 -12
  457. package/dist/tour/tour.js.map +1 -1
  458. package/dist/tour/use-tour-progress.js.map +1 -1
  459. package/dist/tour/use-tour-url-sync.js.map +1 -1
  460. package/dist/wizard/stepper-nav.js +3 -24
  461. package/dist/wizard/stepper-nav.js.map +1 -1
  462. package/dist/wizard/types.js.map +1 -1
  463. package/dist/wizard/use-wizard.js +24 -28
  464. package/dist/wizard/use-wizard.js.map +1 -1
  465. package/dist/wizard/validation.js.map +1 -1
  466. package/dist/wizard/wizard-context.js.map +1 -1
  467. package/dist/wizard/wizard-step.d.ts +1 -1
  468. package/dist/wizard/wizard-step.js +1 -8
  469. package/dist/wizard/wizard-step.js.map +1 -1
  470. package/dist/wizard/wizard-summary.js.map +1 -1
  471. package/package.json +5 -2
  472. package/src/account/account-settings-labels.ts +1 -2
  473. package/src/account/account-settings.tsx +11 -17
  474. package/src/account/delete-account-setting.tsx +1 -6
  475. package/src/account/email-change-setting-labels.ts +1 -2
  476. package/src/account/email-change-setting.tsx +3 -1
  477. package/src/account/passkeys-setting.tsx +1 -3
  478. package/src/account/sessions-setting.tsx +4 -2
  479. package/src/admin/admin-action-confirm.tsx +4 -7
  480. package/src/admin/admin-action-log.tsx +29 -29
  481. package/src/admin/admin-area-labels.ts +4 -1
  482. package/src/admin/admin-overview.tsx +8 -2
  483. package/src/admin/admin-sections.tsx +3 -1
  484. package/src/admin/admin-tool-links.tsx +7 -1
  485. package/src/admin/invitations-panel.tsx +5 -11
  486. package/src/admin/mail-test-card.tsx +1 -2
  487. package/src/admin/reviewer-scope-editor.tsx +4 -9
  488. package/src/admin/role-select.tsx +4 -2
  489. package/src/admin/user-roster.tsx +2 -11
  490. package/src/auth/accept-invitation.tsx +1 -7
  491. package/src/auth/auth-errors.ts +1 -6
  492. package/src/auth/forgot-password-form.tsx +2 -7
  493. package/src/auth/register-form.tsx +7 -8
  494. package/src/auth/reset-password-form.tsx +2 -5
  495. package/src/auth/sign-in-form.tsx +15 -14
  496. package/src/auth/verify-email.tsx +2 -7
  497. package/src/billing/billing-banner.tsx +3 -7
  498. package/src/billing/billing-labels.ts +6 -4
  499. package/src/billing/checkout-processing.ts +0 -1
  500. package/src/billing/plan-card.tsx +9 -8
  501. package/src/billing/plan-limit-toast.tsx +1 -2
  502. package/src/billing/plan-limit.ts +1 -2
  503. package/src/billing/plan-price.ts +1 -4
  504. package/src/billing/plan-switch.tsx +1 -2
  505. package/src/billing/subscription-actions.tsx +5 -2
  506. package/src/components/account-chips.tsx +1 -7
  507. package/src/components/alert-banner.tsx +2 -8
  508. package/src/components/amount-input.tsx +206 -178
  509. package/src/components/authed-image.tsx +7 -4
  510. package/src/components/autocomplete.tsx +7 -9
  511. package/src/components/bulk-action-bar.tsx +2 -7
  512. package/src/components/button-group.tsx +1 -2
  513. package/src/components/button-look.ts +6 -17
  514. package/src/components/button.tsx +1 -4
  515. package/src/components/calculator.tsx +6 -9
  516. package/src/components/calendar-heatmap.tsx +5 -10
  517. package/src/components/card.tsx +9 -13
  518. package/src/components/chart-zoom-core.tsx +6 -33
  519. package/src/components/chart.tsx +8 -31
  520. package/src/components/checkbox-group.tsx +1 -4
  521. package/src/components/chip-input.tsx +6 -6
  522. package/src/components/chip.tsx +14 -26
  523. package/src/components/choice-card.tsx +4 -18
  524. package/src/components/column-mapper-labels.ts +3 -10
  525. package/src/components/column-mapper.tsx +33 -122
  526. package/src/components/combobox-core.tsx +12 -42
  527. package/src/components/combobox.tsx +2 -6
  528. package/src/components/confirm-dialog.tsx +1 -3
  529. package/src/components/copy-button.tsx +2 -4
  530. package/src/components/country-select.tsx +9 -21
  531. package/src/components/currency-option-list.tsx +13 -14
  532. package/src/components/currency-select.tsx +2 -7
  533. package/src/components/danger-confirm.tsx +8 -9
  534. package/src/components/data-table-cells.tsx +1 -2
  535. package/src/components/data-table-column-settings.tsx +1 -3
  536. package/src/components/data-table-filter-popover.tsx +5 -19
  537. package/src/components/data-table-filters.ts +1 -6
  538. package/src/components/data-table-fit.ts +1 -3
  539. package/src/components/data-table-groups.tsx +3 -1
  540. package/src/components/data-table-head.tsx +27 -45
  541. package/src/components/data-table-mobile.tsx +99 -108
  542. package/src/components/data-table-pagination.tsx +2 -6
  543. package/src/components/data-table-row-actions.tsx +25 -24
  544. package/src/components/data-table-row.tsx +16 -17
  545. package/src/components/data-table-shared.tsx +10 -16
  546. package/src/components/data-table-sort.ts +1 -2
  547. package/src/components/data-table-virtual.ts +9 -4
  548. package/src/components/data-table.tsx +489 -501
  549. package/src/components/date-picker.tsx +1 -7
  550. package/src/components/date-range-picker.tsx +3 -13
  551. package/src/components/description-list.tsx +7 -13
  552. package/src/components/dialog-frame.tsx +12 -3
  553. package/src/components/disclosure.tsx +8 -12
  554. package/src/components/dropdown.tsx +3 -11
  555. package/src/components/empty-state.tsx +1 -3
  556. package/src/components/entity-combobox.tsx +2 -6
  557. package/src/components/error-boundary.tsx +7 -7
  558. package/src/components/field-base.tsx +8 -14
  559. package/src/components/field-parts.tsx +12 -23
  560. package/src/components/field-sync.tsx +7 -11
  561. package/src/components/file-button.tsx +7 -3
  562. package/src/components/file-dropzone.tsx +7 -12
  563. package/src/components/floating-panel.tsx +44 -51
  564. package/src/components/focus-ring.ts +1 -2
  565. package/src/components/form-actions.tsx +1 -4
  566. package/src/components/grouped-picker.tsx +6 -8
  567. package/src/components/hover-menu.tsx +1 -4
  568. package/src/components/iban-input-labels.ts +1 -2
  569. package/src/components/icon-button.tsx +46 -48
  570. package/src/components/icon-picker.tsx +4 -17
  571. package/src/components/image-grid.tsx +1 -6
  572. package/src/components/inline-edit-field.tsx +1 -5
  573. package/src/components/inline-entity-combobox.tsx +6 -21
  574. package/src/components/input.tsx +14 -2
  575. package/src/components/jump-calendar.tsx +2 -6
  576. package/src/components/legal-labels.ts +1 -2
  577. package/src/components/legal-page.tsx +18 -7
  578. package/src/components/legal.tsx +14 -5
  579. package/src/components/lightbox.tsx +1 -3
  580. package/src/components/line-items.tsx +7 -15
  581. package/src/components/list.tsx +29 -25
  582. package/src/components/measured-grid.tsx +8 -30
  583. package/src/components/menu-item.tsx +1 -3
  584. package/src/components/mini-calendar.tsx +129 -159
  585. package/src/components/money-field.tsx +4 -2
  586. package/src/components/month-picker.tsx +14 -21
  587. package/src/components/multi-entity-combobox.tsx +6 -26
  588. package/src/components/multi-select.tsx +5 -9
  589. package/src/components/nav-pills.tsx +10 -2
  590. package/src/components/number-field.tsx +18 -15
  591. package/src/components/number-input.tsx +3 -19
  592. package/src/components/numpad-sheet.tsx +9 -13
  593. package/src/components/one-time-code-input.tsx +6 -11
  594. package/src/components/page-contents.tsx +2 -9
  595. package/src/components/page-header.tsx +5 -10
  596. package/src/components/password-strength-labels.ts +1 -2
  597. package/src/components/password-strength.tsx +6 -9
  598. package/src/components/phone-input.tsx +2 -13
  599. package/src/components/picker-sheet.tsx +2 -5
  600. package/src/components/pie-chart.tsx +6 -12
  601. package/src/components/popover.tsx +2 -1
  602. package/src/components/progress-bar.tsx +16 -25
  603. package/src/components/qr-code.tsx +2 -9
  604. package/src/components/row-actions.tsx +9 -1
  605. package/src/components/search-field.tsx +4 -5
  606. package/src/components/select.tsx +2 -10
  607. package/src/components/separator.tsx +1 -5
  608. package/src/components/series-chart-axes.ts +5 -5
  609. package/src/components/series-chart-budget.ts +1 -4
  610. package/src/components/series-chart-ticks.ts +1 -4
  611. package/src/components/series-chart.tsx +43 -83
  612. package/src/components/settings-fields.tsx +20 -15
  613. package/src/components/share-card.tsx +6 -14
  614. package/src/components/sign-chip.tsx +16 -16
  615. package/src/components/signature-pad.tsx +21 -13
  616. package/src/components/signed-amount.tsx +6 -14
  617. package/src/components/skeleton.tsx +2 -13
  618. package/src/components/slider.tsx +9 -20
  619. package/src/components/sparkline.tsx +2 -13
  620. package/src/components/stat-tile.tsx +13 -24
  621. package/src/components/swatch-picker.tsx +8 -3
  622. package/src/components/swipeable-row.tsx +2 -11
  623. package/src/components/table-skeleton.tsx +4 -1
  624. package/src/components/table.tsx +12 -8
  625. package/src/components/tabs.tsx +24 -39
  626. package/src/components/text-link.tsx +6 -6
  627. package/src/components/text.tsx +1 -2
  628. package/src/components/tile-radio.tsx +6 -19
  629. package/src/components/time-input.tsx +4 -5
  630. package/src/components/toast.tsx +3 -12
  631. package/src/components/toggle-group.tsx +9 -9
  632. package/src/components/toggle-legend.tsx +4 -16
  633. package/src/components/tooltip.tsx +9 -48
  634. package/src/components/translation-review-editor.tsx +4 -4
  635. package/src/components/translation-review-parts.tsx +7 -1
  636. package/src/components/translation-review.tsx +15 -16
  637. package/src/components/tree-view.tsx +17 -20
  638. package/src/components/treemap.tsx +59 -9
  639. package/src/components/trigger-aria.ts +1 -4
  640. package/src/components/use-table-state.ts +16 -17
  641. package/src/components/user-avatar.tsx +4 -21
  642. package/src/demo/demo-start.tsx +8 -4
  643. package/src/feedback/feedback-attachment-labels.ts +2 -1
  644. package/src/feedback/feedback-attachment.tsx +16 -29
  645. package/src/feedback/feedback-capture.ts +3 -1
  646. package/src/feedback/feedback-comment-thread.tsx +9 -6
  647. package/src/feedback/feedback-context.tsx +1 -4
  648. package/src/feedback/feedback-dialog.tsx +3 -4
  649. package/src/feedback/feedback-inbox.tsx +3 -9
  650. package/src/feedback/feedback-public-list.tsx +7 -1
  651. package/src/feedback/feedback-record.ts +6 -14
  652. package/src/feedback/feedback-replies.tsx +10 -5
  653. package/src/feedback/feedback-row-detail.tsx +2 -5
  654. package/src/feedback/feedback-status-undo.ts +1 -4
  655. package/src/feedback/feedback-submit-dialog.tsx +4 -4
  656. package/src/feedback/feedback-table.tsx +12 -8
  657. package/src/feedback/feedback-thread.tsx +4 -16
  658. package/src/feedback/feedback-vocabulary.ts +72 -82
  659. package/src/feedback/feedback-vote-button.tsx +9 -8
  660. package/src/hooks/use-anchored-rect.ts +1 -4
  661. package/src/hooks/use-document-title.ts +3 -1
  662. package/src/hooks/use-file-drop.ts +1 -3
  663. package/src/hooks/use-file-picker-labels.ts +1 -2
  664. package/src/hooks/use-file-picker.tsx +1 -5
  665. package/src/hooks/use-focus-trap.ts +1 -2
  666. package/src/hooks/use-overlay-history.ts +10 -5
  667. package/src/hooks/use-row-swipe.ts +1 -6
  668. package/src/i18n/defaults.ts +1 -4
  669. package/src/i18n/german.ts +33 -49
  670. package/src/i18n/kit-labels.tsx +5 -20
  671. package/src/i18n/languages.ts +2 -8
  672. package/src/i18n/locales/en.ts +11 -31
  673. package/src/i18n/locales/es.ts +35 -49
  674. package/src/i18n/locales/fr.ts +34 -51
  675. package/src/i18n/locales/hu.ts +26 -37
  676. package/src/i18n/locales/it.ts +35 -52
  677. package/src/i18n/locales/zh.ts +14 -32
  678. package/src/i18n/plurals.ts +4 -1
  679. package/src/i18n/review.ts +18 -7
  680. package/src/i18n/shared-labels.ts +1 -2
  681. package/src/index.ts +106 -32
  682. package/src/landing/access.ts +2 -7
  683. package/src/landing/landing-sections.tsx +14 -15
  684. package/src/landing/page-seo.ts +1 -3
  685. package/src/lib/calc.ts +4 -1
  686. package/src/lib/clipboard-minus.ts +4 -1
  687. package/src/lib/dates.ts +2 -10
  688. package/src/lib/decimal-marks.ts +1 -3
  689. package/src/lib/device-settings.ts +2 -1
  690. package/src/lib/direction.ts +1 -3
  691. package/src/lib/iban.ts +89 -9
  692. package/src/lib/logger.ts +1 -5
  693. package/src/lib/person-name.ts +1 -3
  694. package/src/lib/phone.ts +5 -8
  695. package/src/lib/qr-encode.ts +32 -8
  696. package/src/lib/session-refresh.ts +5 -6
  697. package/src/lib/table-text.ts +24 -6
  698. package/src/lib/text-match.ts +15 -3
  699. package/src/lib/translation-review.ts +1 -3
  700. package/src/notifications/notification-inbox.tsx +17 -4
  701. package/src/notifications/notification-parts.tsx +1 -2
  702. package/src/notifications/notification-types-setting.tsx +19 -18
  703. package/src/notifications/notifications-labels.ts +1 -2
  704. package/src/notifications/push-device-setting.tsx +7 -1
  705. package/src/notifications/push-devices-setting.tsx +7 -3
  706. package/src/notifications/push-worker.ts +10 -2
  707. package/src/notifications/quiet-hours-setting.tsx +4 -1
  708. package/src/notifications/use-push-device.ts +4 -1
  709. package/src/offline/pwa-update-prompt.tsx +7 -1
  710. package/src/offline/query-persistence.ts +9 -6
  711. package/src/offline.ts +6 -1
  712. package/src/rhf/fields.tsx +50 -19
  713. package/src/rhf/form-core.tsx +2 -6
  714. package/src/rhf/line-items.tsx +5 -7
  715. package/src/rhf/use-rhf-wizard-step.ts +3 -12
  716. package/src/search/command-palette.tsx +15 -23
  717. package/src/search/global-search.tsx +38 -8
  718. package/src/search/search-index.ts +5 -1
  719. package/src/settings/device-reset-setting.tsx +5 -8
  720. package/src/settings/settings-heading.tsx +3 -1
  721. package/src/settings/settings-layout.tsx +6 -13
  722. package/src/settings/use-settings-route.ts +4 -3
  723. package/src/shell/app-shell.tsx +48 -24
  724. package/src/shell/auth-layout.tsx +1 -6
  725. package/src/shell/dev-menu.tsx +12 -6
  726. package/src/shell/option-switcher-menu.tsx +1 -3
  727. package/src/shell/sync-status-indicator.tsx +14 -12
  728. package/src/shell/top-bar-parts-labels.ts +1 -3
  729. package/src/shell/topbar-action-menu.tsx +9 -8
  730. package/src/shell/topbar-controls.tsx +5 -19
  731. package/src/shell/tour-menu.tsx +9 -1
  732. package/src/sync/db.ts +5 -1
  733. package/src/sync/engine.ts +44 -12
  734. package/src/sync.ts +16 -2
  735. package/src/testing/css-variables.ts +1 -2
  736. package/src/testing/index.ts +1 -6
  737. package/src/testing/react.ts +1 -2
  738. package/src/testing/viewport.ts +9 -9
  739. package/src/theme/contrast-tokens.ts +13 -2
  740. package/src/theme/palette-derive.ts +24 -50
  741. package/src/theme/palette-presets.ts +0 -1
  742. package/src/theme/palette-store.ts +1 -4
  743. package/src/theme/text-size.ts +1 -1
  744. package/src/theme/theme-store.ts +2 -6
  745. package/src/tour/tour-catalogue.tsx +4 -2
  746. package/src/tour/tour-dock.ts +5 -1
  747. package/src/tour/tour-engine.ts +2 -6
  748. package/src/tour/tour.tsx +28 -12
  749. package/src/tour/use-tour-progress.ts +8 -2
  750. package/src/tour/use-tour-url-sync.ts +3 -1
  751. package/src/vite-env.d.ts +1 -4
  752. package/src/wizard/stepper-nav.tsx +10 -35
  753. package/src/wizard/types.ts +1 -4
  754. package/src/wizard/use-wizard.ts +28 -43
  755. package/src/wizard/validation.ts +1 -5
  756. package/src/wizard/wizard-context.tsx +2 -6
  757. package/src/wizard/wizard-step.tsx +1 -8
  758. package/src/wizard/wizard-summary.tsx +2 -6
  759. package/tokens.css +393 -152
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/hooks/use-file-picker.tsx"],"sourcesContent":["import { useRef } from \"react\";\nimport type { ReactElement } from \"react\";\nimport { useAnnounce } from \"./use-announce\";\nimport { useKitFileLabels, useKitLabels, type LabelOverride } from \"../i18n/kit-labels\";\nimport { DEFAULT_FILE_PICKER_LABELS } from \"./use-file-picker-labels\";\n\n/**\n * The file pickers' headless half: the hidden input, the screening of a pick and the\n * announcement of a refusal, which {@link FileButton}, `FileDropzone` and {@link useFileDrop}\n * are built on.\n *\n * In hooks/ since 0.34 (MOD-8, refactor audit 2026-10). It lived in\n * components/file-button.tsx, and `useFileDrop` — a hook — imported it from there, so\n * hooks/ reached up into components/. components/file-button re-exports all of it, so an\n * import from there keeps working.\n */\n\n/* ── Labels ──────────────────────────────────────────────────────────────── */\n\n/** Every string the file pickers ({@link FileButton}, `FileDropzone`) render or speak.\n * Messages are functions of the file name so a translation can put it anywhere. */\nexport interface FilePickerLabels {\n /** The file's type is not in `accept`, when no `accept` is known to name (see\n * `rejectedTypeOnly`). */\n rejectedType: (name: string) => string;\n /** The file's type is not in `accept`, naming what IS accepted: `accept`'s tokens,\n * lower-cased and joined with \", \" (`\".csv\"` => \"Only .csv files\"). Used whenever\n * `accept` is set — kastlan kept a custom `isValid` + `invalidMessage` per form only\n * to say this, and `accept` already knows it. */\n rejectedTypeOnly: (accept: string, name: string) => string;\n /** The file is larger than `maxSize`; `maxSize` arrives formatted (\"5 MB\"). */\n rejectedSize: (name: string, maxSize: string) => string;\n /** The file was one too many for `maxFiles`. */\n rejectedCount: (name: string, maxFiles: number) => string;\n /** `isValid` said no and the caller gave no `invalidMessage`. */\n rejectedInvalid: (name: string) => string;\n /** Spoken instead of the per-file message when more than one file was refused. */\n rejectedMany: (count: number) => string;\n /** Spoken (and shown, on a dropzone) when `onPick` refused the WHOLE pick.\n * Receives the number of files in it. */\n rejectedPick: (count: number) => string;\n /** Spoken after a pick the component itself echoes (the dropzone). */\n selected: (count: number, firstName: string) => string;\n /** The dropzone's remove button for one file. */\n remove: (name: string) => string;\n /** The dropzone's remove-everything button in `multiple` mode. */\n clearAll: string;\n /** Spoken after a remove / clear, since the button that was pressed is gone. */\n removed: (name: string) => string;\n cleared: string;\n /** The dropzone's accessible name, when the host passes no `dropLabel`. */\n dropzone: string;\n /** The dropzone's Browse button, when the host passes no `browseLabel`. */\n browse: string;\n /** What an empty single-file dropzone says (no `emptyLabel` given). */\n empty: string;\n /** …and an empty `multiple` one. */\n emptyMultiple: string;\n /** The dropzone's second line (no `hint` given). Receives `accept` as a person\n * reads it (`\".pdf, image/png\"`), or `\"\"` when anything goes. */\n hint: (accept: string) => string;\n /** What a `busy` dropzone says in place of its body (\"Uploading…\"). */\n busy: string;\n}\n\n/* ── Screening ───────────────────────────────────────────────────────────── */\n\nexport type FileRejectionReason = \"type\" | \"size\" | \"count\" | \"invalid\";\n\n/** One file the picker refused, and why. `message` is already translated (see\n * {@link FilePickerLabels}, or the caller's `invalidMessage`), so a host that just\n * wants to show it can render `rejections[0].message` as is. */\nexport interface FileRejection {\n file: File;\n reason: FileRejectionReason;\n message: string;\n}\n\n/**\n * Does `file` satisfy an `accept` string, the way the browser's dialog reads it?\n * Comma-separated tokens: `.ext` (case-insensitive suffix of the name), `type/*`\n * (a MIME family) or an exact MIME type. An empty or absent `accept` takes anything.\n *\n * A file with no `type` — common for `.step`, `.dat`, anything the OS has no MIME\n * mapping for — can only match by extension, which is why lenkbank lists `.stp` AND\n * `model/step`: that is how `accept` has to be written for the dialog anyway.\n */\nexport function matchesAccept(file: File, accept: string | undefined): boolean {\n const tokens = (accept ?? \"\")\n .split(\",\")\n .map((t) => t.trim().toLowerCase())\n .filter(Boolean);\n if (tokens.length === 0) return true;\n const name = file.name.toLowerCase();\n // An EMPTY type is the browser saying \"I don't know\", not \"it's something else\": HEIC\n // photos on Windows without the codec arrive with `type === \"\"`. Refusing them\n // (0.6.0) refused iPhone photos the picker itself had just offered (keksdose). So a\n // missing type is inferred from the extension where that is unambiguous, and a file\n // whose type cannot be known at all is given the benefit of the doubt — nothing here\n // proves it does not match, and the server validates what it receives anyway.\n const type = (file.type || typeFromExtension(name) || \"\").toLowerCase();\n if (!type && !tokens.every((t) => t.startsWith(\".\"))) return true;\n return tokens.some((token) => {\n if (token.startsWith(\".\")) return name.endsWith(token);\n if (!type) return false;\n if (token.endsWith(\"/*\")) return type.startsWith(token.slice(0, -1));\n return type === token;\n });\n}\n\n/** MIME types for the extensions a browser most often leaves untyped. */\nconst EXTENSION_TYPES: Record<string, string> = {\n heic: \"image/heic\",\n heif: \"image/heif\",\n avif: \"image/avif\",\n webp: \"image/webp\",\n jpg: \"image/jpeg\",\n jpeg: \"image/jpeg\",\n png: \"image/png\",\n gif: \"image/gif\",\n pdf: \"application/pdf\",\n csv: \"text/csv\",\n txt: \"text/plain\",\n};\n\nfunction typeFromExtension(name: string): string | undefined {\n const dot = name.lastIndexOf(\".\");\n return dot === -1 ? undefined : EXTENSION_TYPES[name.slice(dot + 1)];\n}\n\n/** @internal `\" .PDF, image/png ,\"` => `\".pdf, image/png\"` — `accept` as a person reads\n * it. Also the dropzone's default hint. */\nexport function formatAccept(accept: string): string {\n return accept\n .split(\",\")\n .map((t) => t.trim().toLowerCase())\n .filter(Boolean)\n .join(\", \");\n}\n\n/** What the pickers screen a pick with. All optional; nothing set accepts everything. */\nexport interface FileScreenOptions {\n accept?: string;\n /** Bytes. Larger files are refused with reason `\"size\"`. */\n maxSize?: number;\n /** How many files one pick may deliver. The rest are refused with reason `\"count\"`\n * — first come, first kept. For a running cap (\"at most 5 attachments\") pass what\n * is LEFT: `maxFiles={5 - attachments.length}`. */\n maxFiles?: number;\n /** The caller's own check, after `accept` and `maxSize`. */\n isValid?: (file: File) => boolean;\n /** The message for an `isValid` refusal; `labels.rejectedInvalid` otherwise. */\n invalidMessage?: string;\n}\n\n/** @internal Split a pick into the files that pass and the ones that do not. */\nexport function screenFiles(\n files: readonly File[],\n opts: FileScreenOptions,\n labels: FilePickerLabels,\n formatSize: (bytes: number) => string,\n): { accepted: File[]; rejected: FileRejection[] } {\n const accepted: File[] = [];\n const rejected: FileRejection[] = [];\n // Name what `accept` takes (\"Only .csv files\") — unless the host translated\n // `rejectedType` but not yet `rejectedTypeOnly`: its own sentence beats an English one.\n const acceptText = opts.accept ? formatAccept(opts.accept) : \"\";\n const useOnly =\n acceptText !== \"\" &&\n (labels.rejectedTypeOnly !== DEFAULT_FILE_PICKER_LABELS.rejectedTypeOnly ||\n labels.rejectedType === DEFAULT_FILE_PICKER_LABELS.rejectedType);\n const typeMessage = (name: string) =>\n useOnly ? labels.rejectedTypeOnly(acceptText, name) : labels.rejectedType(name);\n for (const file of files) {\n let reason: FileRejectionReason | null = null;\n if (!matchesAccept(file, opts.accept)) reason = \"type\";\n else if (opts.maxSize !== undefined && file.size > opts.maxSize) reason = \"size\";\n else if (opts.isValid && !opts.isValid(file)) reason = \"invalid\";\n // Count last, and only against files that passed everything else: a refused\n // file should not use up one of the slots a good one could have had.\n else if (opts.maxFiles !== undefined && accepted.length >= opts.maxFiles) reason = \"count\";\n if (reason === null) {\n accepted.push(file);\n continue;\n }\n const message =\n reason === \"type\"\n ? typeMessage(file.name)\n : reason === \"size\"\n ? labels.rejectedSize(file.name, formatSize(opts.maxSize ?? 0))\n : reason === \"count\"\n ? labels.rejectedCount(file.name, Math.max(0, opts.maxFiles ?? 0))\n : (opts.invalidMessage ?? labels.rejectedInvalid(file.name));\n rejected.push({ file, reason, message });\n }\n return { accepted, rejected };\n}\n\n/**\n * Judges a pick as a whole — keksdose's \"refuse the pick if any file is bad\", which\n * with `onFiles` + `onReject` alone had to be rebuilt from two calls in a microtask.\n *\n * Called ONCE per pick, after screening and before `onFiles` / `onReject`, with both\n * halves (either may be empty). Return `false` to refuse the whole pick: the accepted\n * files are not delivered, `onReject` still receives what screening refused, and one\n * sentence (`labels.rejectedPick`) is spoken for the pick instead of a per-file one.\n * Return nothing to let the pick through as usual.\n *\n * ```tsx\n * onPick={(ok, bad) => bad.length === 0 || false} // all or nothing\n * ```\n */\nexport type FilePickHandler = (accepted: File[], rejected: FileRejection[]) => boolean | void;\n\n/** @internal The shared tail of both pickers: run `onPick`, and say whether the pick\n * stands. A pick with nothing accepted has nothing to refuse, so `false` there is\n * treated as no verdict and the usual per-file messages are kept. */\nexport function judgePick(\n onPick: FilePickHandler | undefined,\n accepted: File[],\n rejected: FileRejection[],\n): boolean {\n if (!onPick) return true;\n return onPick(accepted, rejected) !== false || accepted.length === 0;\n}\n\n/** One sentence for a whole batch of refusals: the file's own message for one, a\n * count for several — reading out five sentences in a row helps nobody. */\nexport function summariseRejections(rejected: readonly FileRejection[], labels: FilePickerLabels): string {\n if (rejected.length === 0) return \"\";\n if (rejected.length === 1) return rejected[0].message;\n return labels.rejectedMany(rejected.length);\n}\n\n/* ── useFilePicker ───────────────────────────────────────────────────────── */\n\nexport interface UseFilePickerOptions extends FileScreenOptions {\n /** Let one pick deliver several files. Without it a drop of several keeps the first. */\n multiple?: boolean;\n /**\n * Ask a phone for its camera instead of the file chooser: `\"environment\"` is the\n * back camera, `\"user\"` the front. A HINT — desktop browsers ignore it, and Chrome\n * drops it when `multiple` is also set (a camera cannot deliver a list), which is why\n * keksdose's camera input has no `multiple`. Pass one or the other.\n */\n capture?: boolean | \"user\" | \"environment\";\n /** The files that passed, in pick order. Never called with an empty array.\n * Optional since 0.7 for a caller that takes the pick through `onPick` alone. */\n onFiles?: (files: File[]) => void;\n /** See {@link FilePickHandler}: the whole pick at once, with the power to refuse it. */\n onPick?: FilePickHandler;\n /** The files that did not, with a translated message each. The refusals are also\n * spoken through a live region, so this is for SHOWING them, not for a11y. */\n onReject?: (rejections: FileRejection[]) => void;\n /** `open()` and `take()` do nothing while set. */\n disabled?: boolean;\n /** Per-instance overrides of the `filePicker` label namespace. */\n labels?: LabelOverride<FilePickerLabels>;\n}\n\nexport interface UseFilePickerReturn {\n /** Open the system picker. Call it from a click handler — browsers only open a file\n * dialog in response to a user gesture. */\n open: () => void;\n /** Screen and deliver files that arrived some other way — a drop, a paste. Honours\n * `multiple` (only the first file without it) and every check. */\n take: (files: ArrayLike<File> | null | undefined) => void;\n /** The hidden input and the live region. Render it once, anywhere — it takes no\n * space and needs no positioned ancestor. */\n element: ReactElement;\n}\n\n/**\n * The headless half of {@link FileButton}: a hidden file input you can open from any\n * control, with the reset, the screening and the announcement built in.\n *\n * ```tsx\n * const picker = useFilePicker({ accept: \".pdf\", onFiles: ([f]) => upload(f) });\n * return <>{picker.element}<DetailTableCard onAdd={picker.open} … /></>;\n * ```\n */\nexport function useFilePicker({\n accept,\n multiple,\n capture,\n maxSize,\n maxFiles,\n isValid,\n invalidMessage,\n onFiles,\n onPick,\n onReject,\n disabled,\n labels: labelsProp,\n}: UseFilePickerOptions): UseFilePickerReturn {\n const inputRef = useRef<HTMLInputElement>(null);\n const labels = useKitLabels(\"filePicker\", DEFAULT_FILE_PICKER_LABELS, labelsProp);\n const fileText = useKitFileLabels();\n // Assertive: a refusal is a failure the user has to hear before they move on, and\n // it is the only thing this region ever says.\n const { announce, regionProps } = useAnnounce({ politeness: \"assertive\" });\n\n const take = (list: ArrayLike<File> | null | undefined) => {\n if (disabled || !list || list.length === 0) return;\n const all = Array.from(list);\n const files = multiple ? all : all.slice(0, 1);\n const { accepted, rejected } = screenFiles(\n files,\n // A single-file picker holds one file by definition; `maxFiles` is a multi-mode\n // cap and would only ever say \"0\" there if a caller passed it by mistake.\n { accept, maxSize, maxFiles: multiple ? maxFiles : undefined, isValid, invalidMessage },\n labels,\n fileText.size,\n );\n if (!judgePick(onPick, accepted, rejected)) {\n if (rejected.length > 0) onReject?.(rejected);\n announce(labels.rejectedPick(files.length));\n return;\n }\n if (accepted.length > 0) onFiles?.(accepted);\n if (rejected.length > 0) {\n onReject?.(rejected);\n announce(summariseRejections(rejected, labels));\n }\n };\n\n const element = (\n <>\n <input\n ref={inputRef}\n type=\"file\"\n accept={accept}\n multiple={multiple}\n capture={capture}\n disabled={disabled}\n // `hidden` (display: none), not `sr-only`: the control a user reaches is the\n // button, and a second, nameless tab stop for the same action is noise. A\n // display:none file input still opens on `.click()` in every current browser —\n // and, unlike `sr-only`, it cannot escape to the initial containing block and\n // stretch the page (see the note in `file-dropzone.tsx`).\n className=\"hidden\"\n tabIndex={-1}\n aria-hidden\n onChange={(e) => {\n // Copy BEFORE the reset: `files` is a live view of the input's value, and\n // clearing the value empties it.\n const picked = Array.from(e.currentTarget.files ?? []);\n e.currentTarget.value = \"\";\n take(picked);\n }}\n />\n <span {...regionProps} />\n </>\n );\n\n return {\n open: () => {\n if (!disabled) inputRef.current?.click();\n },\n take,\n element,\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/hooks/use-file-picker.tsx"],"sourcesContent":["import { useRef } from \"react\";\nimport type { ReactElement } from \"react\";\nimport { useAnnounce } from \"./use-announce\";\nimport { useKitFileLabels, useKitLabels, type LabelOverride } from \"../i18n/kit-labels\";\nimport { DEFAULT_FILE_PICKER_LABELS } from \"./use-file-picker-labels\";\n\n/**\n * The file pickers' headless half: the hidden input, the screening of a pick and the\n * announcement of a refusal, which {@link FileButton}, `FileDropzone` and {@link useFileDrop}\n * are built on.\n *\n * In hooks/ since 0.34 (MOD-8, refactor audit 2026-10). It lived in\n * components/file-button.tsx, and `useFileDrop` — a hook — imported it from there, so\n * hooks/ reached up into components/. components/file-button re-exports all of it, so an\n * import from there keeps working.\n */\n\n/* ── Labels ──────────────────────────────────────────────────────────────── */\n\n/** Every string the file pickers ({@link FileButton}, `FileDropzone`) render or speak.\n * Messages are functions of the file name so a translation can put it anywhere. */\nexport interface FilePickerLabels {\n /** The file's type is not in `accept`, when no `accept` is known to name (see\n * `rejectedTypeOnly`). */\n rejectedType: (name: string) => string;\n /** The file's type is not in `accept`, naming what IS accepted: `accept`'s tokens,\n * lower-cased and joined with \", \" (`\".csv\"` => \"Only .csv files\"). Used whenever\n * `accept` is set — kastlan kept a custom `isValid` + `invalidMessage` per form only\n * to say this, and `accept` already knows it. */\n rejectedTypeOnly: (accept: string, name: string) => string;\n /** The file is larger than `maxSize`; `maxSize` arrives formatted (\"5 MB\"). */\n rejectedSize: (name: string, maxSize: string) => string;\n /** The file was one too many for `maxFiles`. */\n rejectedCount: (name: string, maxFiles: number) => string;\n /** `isValid` said no and the caller gave no `invalidMessage`. */\n rejectedInvalid: (name: string) => string;\n /** Spoken instead of the per-file message when more than one file was refused. */\n rejectedMany: (count: number) => string;\n /** Spoken (and shown, on a dropzone) when `onPick` refused the WHOLE pick.\n * Receives the number of files in it. */\n rejectedPick: (count: number) => string;\n /** Spoken after a pick the component itself echoes (the dropzone). */\n selected: (count: number, firstName: string) => string;\n /** The dropzone's remove button for one file. */\n remove: (name: string) => string;\n /** The dropzone's remove-everything button in `multiple` mode. */\n clearAll: string;\n /** Spoken after a remove / clear, since the button that was pressed is gone. */\n removed: (name: string) => string;\n cleared: string;\n /** The dropzone's accessible name, when the host passes no `dropLabel`. */\n dropzone: string;\n /** The dropzone's Browse button, when the host passes no `browseLabel`. */\n browse: string;\n /** What an empty single-file dropzone says (no `emptyLabel` given). */\n empty: string;\n /** …and an empty `multiple` one. */\n emptyMultiple: string;\n /** The dropzone's second line (no `hint` given). Receives `accept` as a person\n * reads it (`\".pdf, image/png\"`), or `\"\"` when anything goes. */\n hint: (accept: string) => string;\n /** What a `busy` dropzone says in place of its body (\"Uploading…\"). */\n busy: string;\n}\n\n/* ── Screening ───────────────────────────────────────────────────────────── */\n\nexport type FileRejectionReason = \"type\" | \"size\" | \"count\" | \"invalid\";\n\n/** One file the picker refused, and why. `message` is already translated (see\n * {@link FilePickerLabels}, or the caller's `invalidMessage`), so a host that just\n * wants to show it can render `rejections[0].message` as is. */\nexport interface FileRejection {\n file: File;\n reason: FileRejectionReason;\n message: string;\n}\n\n/**\n * Does `file` satisfy an `accept` string, the way the browser's dialog reads it?\n * Comma-separated tokens: `.ext` (case-insensitive suffix of the name), `type/*`\n * (a MIME family) or an exact MIME type. An empty or absent `accept` takes anything.\n *\n * A file with no `type` — common for `.step`, `.dat`, anything the OS has no MIME\n * mapping for — can only match by extension, which is why lenkbank lists `.stp` AND\n * `model/step`: that is how `accept` has to be written for the dialog anyway.\n */\nexport function matchesAccept(file: File, accept: string | undefined): boolean {\n const tokens = (accept ?? \"\")\n .split(\",\")\n .map((t) => t.trim().toLowerCase())\n .filter(Boolean);\n if (tokens.length === 0) return true;\n const name = file.name.toLowerCase();\n // An EMPTY type is the browser saying \"I don't know\", not \"it's something else\": HEIC\n // photos on Windows without the codec arrive with `type === \"\"`. Refusing them\n // (0.6.0) refused iPhone photos the picker itself had just offered (keksdose). So a\n // missing type is inferred from the extension where that is unambiguous, and a file\n // whose type cannot be known at all is given the benefit of the doubt — nothing here\n // proves it does not match, and the server validates what it receives anyway.\n const type = (file.type || typeFromExtension(name) || \"\").toLowerCase();\n if (!type && !tokens.every((t) => t.startsWith(\".\"))) return true;\n return tokens.some((token) => {\n if (token.startsWith(\".\")) return name.endsWith(token);\n if (!type) return false;\n if (token.endsWith(\"/*\")) return type.startsWith(token.slice(0, -1));\n return type === token;\n });\n}\n\n/** MIME types for the extensions a browser most often leaves untyped. */\nconst EXTENSION_TYPES: Record<string, string> = {\n heic: \"image/heic\",\n heif: \"image/heif\",\n avif: \"image/avif\",\n webp: \"image/webp\",\n jpg: \"image/jpeg\",\n jpeg: \"image/jpeg\",\n png: \"image/png\",\n gif: \"image/gif\",\n pdf: \"application/pdf\",\n csv: \"text/csv\",\n txt: \"text/plain\",\n};\n\nfunction typeFromExtension(name: string): string | undefined {\n const dot = name.lastIndexOf(\".\");\n return dot === -1 ? undefined : EXTENSION_TYPES[name.slice(dot + 1)];\n}\n\n/** @internal `\" .PDF, image/png ,\"` => `\".pdf, image/png\"` — `accept` as a person reads\n * it. Also the dropzone's default hint. */\nexport function formatAccept(accept: string): string {\n return accept\n .split(\",\")\n .map((t) => t.trim().toLowerCase())\n .filter(Boolean)\n .join(\", \");\n}\n\n/** What the pickers screen a pick with. All optional; nothing set accepts everything. */\nexport interface FileScreenOptions {\n accept?: string;\n /** Bytes. Larger files are refused with reason `\"size\"`. */\n maxSize?: number;\n /** How many files one pick may deliver. The rest are refused with reason `\"count\"`\n * — first come, first kept. For a running cap (\"at most 5 attachments\") pass what\n * is LEFT: `maxFiles={5 - attachments.length}`. */\n maxFiles?: number;\n /** The caller's own check, after `accept` and `maxSize`. */\n isValid?: (file: File) => boolean;\n /** The message for an `isValid` refusal; `labels.rejectedInvalid` otherwise. */\n invalidMessage?: string;\n}\n\n/** @internal Split a pick into the files that pass and the ones that do not. */\nexport function screenFiles(\n files: readonly File[],\n opts: FileScreenOptions,\n labels: FilePickerLabels,\n formatSize: (bytes: number) => string,\n): { accepted: File[]; rejected: FileRejection[] } {\n const accepted: File[] = [];\n const rejected: FileRejection[] = [];\n // Name what `accept` takes (\"Only .csv files\") — unless the host translated\n // `rejectedType` but not yet `rejectedTypeOnly`: its own sentence beats an English one.\n const acceptText = opts.accept ? formatAccept(opts.accept) : \"\";\n const useOnly =\n acceptText !== \"\" &&\n (labels.rejectedTypeOnly !== DEFAULT_FILE_PICKER_LABELS.rejectedTypeOnly ||\n labels.rejectedType === DEFAULT_FILE_PICKER_LABELS.rejectedType);\n const typeMessage = (name: string) =>\n useOnly ? labels.rejectedTypeOnly(acceptText, name) : labels.rejectedType(name);\n for (const file of files) {\n let reason: FileRejectionReason | null = null;\n if (!matchesAccept(file, opts.accept)) reason = \"type\";\n else if (opts.maxSize !== undefined && file.size > opts.maxSize) reason = \"size\";\n else if (opts.isValid && !opts.isValid(file)) reason = \"invalid\";\n // Count last, and only against files that passed everything else: a refused\n // file should not use up one of the slots a good one could have had.\n else if (opts.maxFiles !== undefined && accepted.length >= opts.maxFiles) reason = \"count\";\n if (reason === null) {\n accepted.push(file);\n continue;\n }\n const message =\n reason === \"type\"\n ? typeMessage(file.name)\n : reason === \"size\"\n ? labels.rejectedSize(file.name, formatSize(opts.maxSize ?? 0))\n : reason === \"count\"\n ? labels.rejectedCount(file.name, Math.max(0, opts.maxFiles ?? 0))\n : (opts.invalidMessage ?? labels.rejectedInvalid(file.name));\n rejected.push({ file, reason, message });\n }\n return { accepted, rejected };\n}\n\n/**\n * Judges a pick as a whole — keksdose's \"refuse the pick if any file is bad\", which\n * with `onFiles` + `onReject` alone had to be rebuilt from two calls in a microtask.\n *\n * Called ONCE per pick, after screening and before `onFiles` / `onReject`, with both\n * halves (either may be empty). Return `false` to refuse the whole pick: the accepted\n * files are not delivered, `onReject` still receives what screening refused, and one\n * sentence (`labels.rejectedPick`) is spoken for the pick instead of a per-file one.\n * Return nothing to let the pick through as usual.\n *\n * ```tsx\n * onPick={(ok, bad) => bad.length === 0 || false} // all or nothing\n * ```\n */\nexport type FilePickHandler = (accepted: File[], rejected: FileRejection[]) => boolean | void;\n\n/** @internal The shared tail of both pickers: run `onPick`, and say whether the pick\n * stands. A pick with nothing accepted has nothing to refuse, so `false` there is\n * treated as no verdict and the usual per-file messages are kept. */\nexport function judgePick(onPick: FilePickHandler | undefined, accepted: File[], rejected: FileRejection[]): boolean {\n if (!onPick) return true;\n return onPick(accepted, rejected) !== false || accepted.length === 0;\n}\n\n/** One sentence for a whole batch of refusals: the file's own message for one, a\n * count for several — reading out five sentences in a row helps nobody. */\nexport function summariseRejections(rejected: readonly FileRejection[], labels: FilePickerLabels): string {\n if (rejected.length === 0) return \"\";\n if (rejected.length === 1) return rejected[0].message;\n return labels.rejectedMany(rejected.length);\n}\n\n/* ── useFilePicker ───────────────────────────────────────────────────────── */\n\nexport interface UseFilePickerOptions extends FileScreenOptions {\n /** Let one pick deliver several files. Without it a drop of several keeps the first. */\n multiple?: boolean;\n /**\n * Ask a phone for its camera instead of the file chooser: `\"environment\"` is the\n * back camera, `\"user\"` the front. A HINT — desktop browsers ignore it, and Chrome\n * drops it when `multiple` is also set (a camera cannot deliver a list), which is why\n * keksdose's camera input has no `multiple`. Pass one or the other.\n */\n capture?: boolean | \"user\" | \"environment\";\n /** The files that passed, in pick order. Never called with an empty array.\n * Optional since 0.7 for a caller that takes the pick through `onPick` alone. */\n onFiles?: (files: File[]) => void;\n /** See {@link FilePickHandler}: the whole pick at once, with the power to refuse it. */\n onPick?: FilePickHandler;\n /** The files that did not, with a translated message each. The refusals are also\n * spoken through a live region, so this is for SHOWING them, not for a11y. */\n onReject?: (rejections: FileRejection[]) => void;\n /** `open()` and `take()` do nothing while set. */\n disabled?: boolean;\n /** Per-instance overrides of the `filePicker` label namespace. */\n labels?: LabelOverride<FilePickerLabels>;\n}\n\nexport interface UseFilePickerReturn {\n /** Open the system picker. Call it from a click handler — browsers only open a file\n * dialog in response to a user gesture. */\n open: () => void;\n /** Screen and deliver files that arrived some other way — a drop, a paste. Honours\n * `multiple` (only the first file without it) and every check. */\n take: (files: ArrayLike<File> | null | undefined) => void;\n /** The hidden input and the live region. Render it once, anywhere — it takes no\n * space and needs no positioned ancestor. */\n element: ReactElement;\n}\n\n/**\n * The headless half of {@link FileButton}: a hidden file input you can open from any\n * control, with the reset, the screening and the announcement built in.\n *\n * ```tsx\n * const picker = useFilePicker({ accept: \".pdf\", onFiles: ([f]) => upload(f) });\n * return <>{picker.element}<DetailTableCard onAdd={picker.open} … /></>;\n * ```\n */\nexport function useFilePicker({\n accept,\n multiple,\n capture,\n maxSize,\n maxFiles,\n isValid,\n invalidMessage,\n onFiles,\n onPick,\n onReject,\n disabled,\n labels: labelsProp,\n}: UseFilePickerOptions): UseFilePickerReturn {\n const inputRef = useRef<HTMLInputElement>(null);\n const labels = useKitLabels(\"filePicker\", DEFAULT_FILE_PICKER_LABELS, labelsProp);\n const fileText = useKitFileLabels();\n // Assertive: a refusal is a failure the user has to hear before they move on, and\n // it is the only thing this region ever says.\n const { announce, regionProps } = useAnnounce({ politeness: \"assertive\" });\n\n const take = (list: ArrayLike<File> | null | undefined) => {\n if (disabled || !list || list.length === 0) return;\n const all = Array.from(list);\n const files = multiple ? all : all.slice(0, 1);\n const { accepted, rejected } = screenFiles(\n files,\n // A single-file picker holds one file by definition; `maxFiles` is a multi-mode\n // cap and would only ever say \"0\" there if a caller passed it by mistake.\n { accept, maxSize, maxFiles: multiple ? maxFiles : undefined, isValid, invalidMessage },\n labels,\n fileText.size,\n );\n if (!judgePick(onPick, accepted, rejected)) {\n if (rejected.length > 0) onReject?.(rejected);\n announce(labels.rejectedPick(files.length));\n return;\n }\n if (accepted.length > 0) onFiles?.(accepted);\n if (rejected.length > 0) {\n onReject?.(rejected);\n announce(summariseRejections(rejected, labels));\n }\n };\n\n const element = (\n <>\n <input\n ref={inputRef}\n type=\"file\"\n accept={accept}\n multiple={multiple}\n capture={capture}\n disabled={disabled}\n // `hidden` (display: none), not `sr-only`: the control a user reaches is the\n // button, and a second, nameless tab stop for the same action is noise. A\n // display:none file input still opens on `.click()` in every current browser —\n // and, unlike `sr-only`, it cannot escape to the initial containing block and\n // stretch the page (see the note in `file-dropzone.tsx`).\n className=\"hidden\"\n tabIndex={-1}\n aria-hidden\n onChange={(e) => {\n // Copy BEFORE the reset: `files` is a live view of the input's value, and\n // clearing the value empties it.\n const picked = Array.from(e.currentTarget.files ?? []);\n e.currentTarget.value = \"\";\n take(picked);\n }}\n />\n <span {...regionProps} />\n </>\n );\n\n return {\n open: () => {\n if (!disabled) inputRef.current?.click();\n },\n take,\n element,\n };\n}\n"],"mappings":"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","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/hooks/use-focus-trap.ts"],"sourcesContent":["import { useEffect, useLayoutEffect, useRef, useState } from \"react\";\n\n/**\n * Keep keyboard focus inside an open overlay, and give it back when it closes.\n *\n * Extracted from `Modal`, which was the ONLY component in this package that did any of\n * this. Every other overlay — the full-bleed dialog a phone's row editor opens, the\n * picker sheet, the numpad, the tour card — declared `role=\"dialog\" aria-modal=\"true\"`\n * and then left focus on the page behind it. That is the worst of the two possible\n * bugs: assistive technology hides the page because of `aria-modal`, while the user's\n * focus is still down there in the part that is now hidden, so Tab walks through\n * controls the screen reader refuses to describe.\n *\n * What this does beyond Modal's original copy:\n *\n * - **Recomputes the tabbable list on every Tab**, not once when the overlay opens.\n * These panels have conditionally-rendered contents — a search box that appears\n * once options load, a \"clear\" button that exists only while a filter is set — and\n * a list captured at open time traps focus against elements that have since gone.\n * - **Guards the restore target.** The element focused before opening may have left\n * the DOM by the time the overlay closes (the row that owned the button was\n * filtered away). Calling `.focus()` on a detached node silently drops focus to\n * `<body>`, which for a keyboard user means losing their place in the page\n * entirely. When the target is gone, focus falls back to the nearest thing that\n * still exists rather than to nothing.\n * - **Leaves an app's own autofocus alone** (0.33.1). React honours a child's\n * `autoFocus` while it commits, and this hook's effect runs AFTER that commit, so\n * focusing the container there took focus straight back off the field the app had\n * asked for. See `initialFocus`.\n * - **Nests.** An overlay can open another — a picker inside a dialog. Only the\n * innermost trap handles a keystroke, and closing it hands control back to the one\n * underneath. Innermost is decided by DOM containment rather than by mount order;\n * see the note on `TRAP_ATTR` for why the obvious stack is wrong.\n * `use-overlay-history.ts` reasons about the same nesting problem for the back\n * button; this is the focus half of it.\n */\n\n/**\n * Candidate tab stops, in DOM order — narrowed by {@link isTabStop} to the ones a Tab\n * really reaches.\n *\n * The selector is the one `Modal` used. On its own it was not enough (round 0.34\n * BUG-4): only the bare `[tabindex]` clause excluded `-1`, so a `button`, an `input` or\n * a link taken out of the tab order still counted. A panel that ends in a roving grid —\n * the calendar's days, the month grid, the picker sheet's rows, every one of them `-1`\n * but the active cell — then had a \"last\" stop no Tab ever reaches: Tab from the real\n * last stop was not wrapped and walked out of the open panel, and Shift+Tab from the\n * first landed on a cell that is not a stop at all.\n */\nconst FOCUSABLE =\n 'a[href],button:not([disabled]),textarea:not([disabled]),input:not([disabled]),select:not([disabled]),[tabindex]:not([tabindex=\"-1\"])';\n\n/**\n * A stop the browser's own Tab would land on: in the tab order (`tabIndex` ≥ 0) and not\n * inside something `hidden` or `inert`. Removes only elements a native Tab never\n * reaches, so no correct order changes. `visibility: hidden` and closed `<details>` are\n * still not walked — they need layout, which this list has never read.\n */\nconst isTabStop = (el: HTMLElement): boolean =>\n el.tabIndex >= 0 && !el.closest(\"[hidden],[inert]\");\n\n/**\n * Marks a container whose trap is currently active.\n *\n * \"Which trap is innermost?\" is answered by DOM CONTAINMENT, not by mount order. The\n * obvious implementation — a module-level stack pushed in the effect — is wrong, and\n * wrong in a way that passes a casual reading: React runs CHILD effects before PARENT\n * effects, so a nested overlay pushes first and the outer one ends up on top of the\n * stack, claiming to be innermost. The outer trap then handles the keystroke and wraps\n * focus to its own first element, jumping the user out of the picker they had open.\n *\n * An attribute on the live DOM cannot disagree with the DOM.\n */\nconst TRAP_ATTR = \"data-focus-trap\";\n\nexport interface FocusTrapOptions {\n /** Trap only while this is true. Mount-time `true` is the common case. */\n active?: boolean;\n /** Return focus to whatever had it before the trap engaged. Default `true`. */\n restoreFocus?: boolean;\n /**\n * What to focus when the trap engages. Default is the container itself, which is\n * what `Modal` does and why: focusing the first FIELD pops the software keyboard on\n * a phone the moment a dialog opens, before the user has asked to type.\n * The container needs `tabIndex={-1}` for this to work.\n *\n * `\"container\"` and `\"first\"` both step aside when focus is ALREADY inside the\n * container, on something other than the container itself. That is a child the app\n * focused on purpose — an `autoFocus`, which React honours before this hook's effect\n * runs — and the keyboard it brings up is the one the user was promised. Moving focus\n * off it lost keksdose's create card its amount field the instant it got it, and on a\n * phone the numpad sheet with it, which shows only while the field is focused (live\n * #402). A function is the caller's explicit choice and is always followed.\n */\n initialFocus?: \"container\" | \"first\" | (() => HTMLElement | null);\n}\n\n/** Whatever has focus right now; nothing on the server, where there is no document. */\nfunction focusedElement(): Element | null {\n return typeof document === \"undefined\" ? null : document.activeElement;\n}\n\nexport function useFocusTrap(\n containerRef: React.RefObject<HTMLElement | null>,\n { active = true, restoreFocus = true, initialFocus = \"container\" }: FocusTrapOptions = {},\n): void {\n // Latest-ref so a caller passing an inline `initialFocus` thunk — which every caller\n // will — does not tear the trap down and re-engage it on each render, stealing focus\n // back to the top of the panel mid-interaction. Written in a LAYOUT effect so that\n // the trap's cleanup, which runs after the layout effects of the same commit, reads\n // this render's `active` rather than the last one's — see the cleanup for why.\n const optsRef = useRef({ active, restoreFocus, initialFocus });\n useLayoutEffect(() => {\n optsRef.current = { active, restoreFocus, initialFocus };\n });\n\n // Where focus was BEFORE the trap engaged: the element it is given back to on close.\n //\n // Read DURING the render that turns the trap on, and that timing is the point. React\n // honours a child's `autoFocus` while it commits that render, before any effect runs,\n // so the effect below asking `document.activeElement` would get the child, inside the\n // overlay. Restoring to that on close aimed focus at the dialog that was closing —\n // already out of the DOM, so focus fell to <body> rather than going back to the\n // button that opened it.\n //\n // State adjusted during render (the pattern `Modal` uses for `open`) rather than a\n // ref written there: set once per opening and cleared on release, so a re-render\n // while open cannot replace it with something inside, and StrictMode's second render\n // and second effect both see the same element. `undefined` means \"not captured\";\n // `null` is a capture that found nothing.\n const [opener, setOpener] = useState<Element | null | undefined>(undefined);\n if (active && opener === undefined) setOpener(focusedElement());\n else if (!active && opener !== undefined) setOpener(undefined);\n\n useEffect(() => {\n if (!active) return;\n const container = containerRef.current;\n if (!container) return;\n\n container.setAttribute(TRAP_ATTR, \"\");\n\n /** Is `node` inside a trap nested within this one? */\n const inNestedTrap = (node: Element | null) => {\n if (!node) return false;\n const owner = node.closest(`[${TRAP_ATTR}]`);\n return owner !== null && owner !== container;\n };\n\n const tabbables = () =>\n Array.from(container.querySelectorAll<HTMLElement>(FOCUSABLE)).filter(\n // A tabbable inside a NESTED open overlay belongs to that overlay's trap, not\n // to this one — otherwise the outer list spans both panels and Tab from the\n // inner panel's last control wraps to the OUTER panel's first.\n (el) => isTabStop(el) && !inNestedTrap(el),\n );\n\n const { initialFocus: how } = optsRef.current;\n const focused = document.activeElement;\n // A child that already has focus was given it on purpose — see `initialFocus`.\n const childHasFocus = focused !== container && container.contains(focused);\n if (typeof how === \"function\") {\n (how() ?? container).focus();\n } else if (!childHasFocus) {\n (how === \"first\" ? (tabbables()[0] ?? container) : container).focus();\n }\n\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key !== \"Tab\") return;\n // Only the innermost trap acts. The event bubbles through every ancestor\n // container, so without this an outer dialog and an inner picker both handle one\n // Tab and fight over where focus lands.\n if (inNestedTrap(document.activeElement)) return;\n\n // Recomputed per keystroke — see the note at the top of this file.\n const items = tabbables();\n if (items.length === 0) {\n e.preventDefault();\n container.focus();\n return;\n }\n const first = items[0];\n const last = items[items.length - 1];\n const activeEl = document.activeElement;\n if (e.shiftKey && (activeEl === first || activeEl === container)) {\n e.preventDefault();\n last.focus();\n } else if (!e.shiftKey && activeEl === last) {\n e.preventDefault();\n first.focus();\n }\n };\n\n // On the container, not the document: a portalled child panel (an open dropdown\n // rendered to <body>) is outside this subtree and manages its own focus, which is\n // the behaviour Modal's comment describes and consumers already depend on.\n container.addEventListener(\"keydown\", onKeyDown);\n\n return () => {\n container.removeEventListener(\"keydown\", onKeyDown);\n container.removeAttribute(TRAP_ATTR);\n\n // Only when the trap is really being released: the container has left the page,\n // or `active` has turned false. StrictMode runs this cleanup once more right after\n // mounting, with the overlay still up, and then the effect again — and React does\n // not re-apply `autoFocus` for that second run. Restoring here would pull focus\n // out of the autofocused field for good: live #402 again, in development.\n if (container.isConnected && optsRef.current.active) return;\n if (!optsRef.current.restoreFocus) return;\n // `isConnected` is the whole point: restoring to a node that has been removed\n // sends focus to <body> and the user loses their place entirely. When the\n // original is gone, leave focus where the browser put it rather than moving it\n // somewhere arbitrary — a wrong guess is worse than no move.\n if (opener?.isConnected) (opener as HTMLElement).focus?.();\n };\n }, [active, containerRef, opener]);\n}\n"],"mappings":";AAAA,SAAS,WAAW,iBAAiB,QAAQ,gBAAgB;AAiD7D,MAAM,YACJ;AAQF,MAAM,YAAY,CAAC,OACjB,GAAG,YAAY,KAAK,CAAC,GAAG,QAAQ,kBAAkB;AAcpD,MAAM,YAAY;AAyBlB,SAAS,iBAAiC;AACxC,SAAO,OAAO,aAAa,cAAc,OAAO,SAAS;AAC3D;AAEO,SAAS,aACd,cACA,EAAE,SAAS,MAAM,eAAe,MAAM,eAAe,YAAY,IAAsB,CAAC,GAClF;AAMN,QAAM,UAAU,OAAO,EAAE,QAAQ,cAAc,aAAa,CAAC;AAC7D,kBAAgB,MAAM;AACpB,YAAQ,UAAU,EAAE,QAAQ,cAAc,aAAa;AAAA,EACzD,CAAC;AAgBD,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAqC,MAAS;AAC1E,MAAI,UAAU,WAAW,OAAW,WAAU,eAAe,CAAC;AAAA,WACrD,CAAC,UAAU,WAAW,OAAW,WAAU,MAAS;AAE7D,YAAU,MAAM;AACd,QAAI,CAAC,OAAQ;AACb,UAAM,YAAY,aAAa;AAC/B,QAAI,CAAC,UAAW;AAEhB,cAAU,aAAa,WAAW,EAAE;AAGpC,UAAM,eAAe,CAAC,SAAyB;AAC7C,UAAI,CAAC,KAAM,QAAO;AAClB,YAAM,QAAQ,KAAK,QAAQ,IAAI,SAAS,GAAG;AAC3C,aAAO,UAAU,QAAQ,UAAU;AAAA,IACrC;AAEA,UAAM,YAAY,MAChB,MAAM,KAAK,UAAU,iBAA8B,SAAS,CAAC,EAAE;AAAA;AAAA;AAAA;AAAA,MAI7D,CAAC,OAAO,UAAU,EAAE,KAAK,CAAC,aAAa,EAAE;AAAA,IAC3C;AAEF,UAAM,EAAE,cAAc,IAAI,IAAI,QAAQ;AACtC,UAAM,UAAU,SAAS;AAEzB,UAAM,gBAAgB,YAAY,aAAa,UAAU,SAAS,OAAO;AACzE,QAAI,OAAO,QAAQ,YAAY;AAC7B,OAAC,IAAI,KAAK,WAAW,MAAM;AAAA,IAC7B,WAAW,CAAC,eAAe;AACzB,OAAC,QAAQ,UAAW,UAAU,EAAE,CAAC,KAAK,YAAa,WAAW,MAAM;AAAA,IACtE;AAEA,UAAM,YAAY,CAAC,MAAqB;AACtC,UAAI,EAAE,QAAQ,MAAO;AAIrB,UAAI,aAAa,SAAS,aAAa,EAAG;AAG1C,YAAM,QAAQ,UAAU;AACxB,UAAI,MAAM,WAAW,GAAG;AACtB,UAAE,eAAe;AACjB,kBAAU,MAAM;AAChB;AAAA,MACF;AACA,YAAM,QAAQ,MAAM,CAAC;AACrB,YAAM,OAAO,MAAM,MAAM,SAAS,CAAC;AACnC,YAAM,WAAW,SAAS;AAC1B,UAAI,EAAE,aAAa,aAAa,SAAS,aAAa,YAAY;AAChE,UAAE,eAAe;AACjB,aAAK,MAAM;AAAA,MACb,WAAW,CAAC,EAAE,YAAY,aAAa,MAAM;AAC3C,UAAE,eAAe;AACjB,cAAM,MAAM;AAAA,MACd;AAAA,IACF;AAKA,cAAU,iBAAiB,WAAW,SAAS;AAE/C,WAAO,MAAM;AACX,gBAAU,oBAAoB,WAAW,SAAS;AAClD,gBAAU,gBAAgB,SAAS;AAOnC,UAAI,UAAU,eAAe,QAAQ,QAAQ,OAAQ;AACrD,UAAI,CAAC,QAAQ,QAAQ,aAAc;AAKnC,UAAI,QAAQ,YAAa,CAAC,OAAuB,QAAQ;AAAA,IAC3D;AAAA,EACF,GAAG,CAAC,QAAQ,cAAc,MAAM,CAAC;AACnC;","names":[]}
1
+ {"version":3,"sources":["../../src/hooks/use-focus-trap.ts"],"sourcesContent":["import { useEffect, useLayoutEffect, useRef, useState } from \"react\";\n\n/**\n * Keep keyboard focus inside an open overlay, and give it back when it closes.\n *\n * Extracted from `Modal`, which was the ONLY component in this package that did any of\n * this. Every other overlay — the full-bleed dialog a phone's row editor opens, the\n * picker sheet, the numpad, the tour card — declared `role=\"dialog\" aria-modal=\"true\"`\n * and then left focus on the page behind it. That is the worst of the two possible\n * bugs: assistive technology hides the page because of `aria-modal`, while the user's\n * focus is still down there in the part that is now hidden, so Tab walks through\n * controls the screen reader refuses to describe.\n *\n * What this does beyond Modal's original copy:\n *\n * - **Recomputes the tabbable list on every Tab**, not once when the overlay opens.\n * These panels have conditionally-rendered contents — a search box that appears\n * once options load, a \"clear\" button that exists only while a filter is set — and\n * a list captured at open time traps focus against elements that have since gone.\n * - **Guards the restore target.** The element focused before opening may have left\n * the DOM by the time the overlay closes (the row that owned the button was\n * filtered away). Calling `.focus()` on a detached node silently drops focus to\n * `<body>`, which for a keyboard user means losing their place in the page\n * entirely. When the target is gone, focus falls back to the nearest thing that\n * still exists rather than to nothing.\n * - **Leaves an app's own autofocus alone** (0.33.1). React honours a child's\n * `autoFocus` while it commits, and this hook's effect runs AFTER that commit, so\n * focusing the container there took focus straight back off the field the app had\n * asked for. See `initialFocus`.\n * - **Nests.** An overlay can open another — a picker inside a dialog. Only the\n * innermost trap handles a keystroke, and closing it hands control back to the one\n * underneath. Innermost is decided by DOM containment rather than by mount order;\n * see the note on `TRAP_ATTR` for why the obvious stack is wrong.\n * `use-overlay-history.ts` reasons about the same nesting problem for the back\n * button; this is the focus half of it.\n */\n\n/**\n * Candidate tab stops, in DOM order — narrowed by {@link isTabStop} to the ones a Tab\n * really reaches.\n *\n * The selector is the one `Modal` used. On its own it was not enough (round 0.34\n * BUG-4): only the bare `[tabindex]` clause excluded `-1`, so a `button`, an `input` or\n * a link taken out of the tab order still counted. A panel that ends in a roving grid —\n * the calendar's days, the month grid, the picker sheet's rows, every one of them `-1`\n * but the active cell — then had a \"last\" stop no Tab ever reaches: Tab from the real\n * last stop was not wrapped and walked out of the open panel, and Shift+Tab from the\n * first landed on a cell that is not a stop at all.\n */\nconst FOCUSABLE =\n 'a[href],button:not([disabled]),textarea:not([disabled]),input:not([disabled]),select:not([disabled]),[tabindex]:not([tabindex=\"-1\"])';\n\n/**\n * A stop the browser's own Tab would land on: in the tab order (`tabIndex` ≥ 0) and not\n * inside something `hidden` or `inert`. Removes only elements a native Tab never\n * reaches, so no correct order changes. `visibility: hidden` and closed `<details>` are\n * still not walked — they need layout, which this list has never read.\n */\nconst isTabStop = (el: HTMLElement): boolean => el.tabIndex >= 0 && !el.closest(\"[hidden],[inert]\");\n\n/**\n * Marks a container whose trap is currently active.\n *\n * \"Which trap is innermost?\" is answered by DOM CONTAINMENT, not by mount order. The\n * obvious implementation — a module-level stack pushed in the effect — is wrong, and\n * wrong in a way that passes a casual reading: React runs CHILD effects before PARENT\n * effects, so a nested overlay pushes first and the outer one ends up on top of the\n * stack, claiming to be innermost. The outer trap then handles the keystroke and wraps\n * focus to its own first element, jumping the user out of the picker they had open.\n *\n * An attribute on the live DOM cannot disagree with the DOM.\n */\nconst TRAP_ATTR = \"data-focus-trap\";\n\nexport interface FocusTrapOptions {\n /** Trap only while this is true. Mount-time `true` is the common case. */\n active?: boolean;\n /** Return focus to whatever had it before the trap engaged. Default `true`. */\n restoreFocus?: boolean;\n /**\n * What to focus when the trap engages. Default is the container itself, which is\n * what `Modal` does and why: focusing the first FIELD pops the software keyboard on\n * a phone the moment a dialog opens, before the user has asked to type.\n * The container needs `tabIndex={-1}` for this to work.\n *\n * `\"container\"` and `\"first\"` both step aside when focus is ALREADY inside the\n * container, on something other than the container itself. That is a child the app\n * focused on purpose — an `autoFocus`, which React honours before this hook's effect\n * runs — and the keyboard it brings up is the one the user was promised. Moving focus\n * off it lost keksdose's create card its amount field the instant it got it, and on a\n * phone the numpad sheet with it, which shows only while the field is focused (live\n * #402). A function is the caller's explicit choice and is always followed.\n */\n initialFocus?: \"container\" | \"first\" | (() => HTMLElement | null);\n}\n\n/** Whatever has focus right now; nothing on the server, where there is no document. */\nfunction focusedElement(): Element | null {\n return typeof document === \"undefined\" ? null : document.activeElement;\n}\n\nexport function useFocusTrap(\n containerRef: React.RefObject<HTMLElement | null>,\n { active = true, restoreFocus = true, initialFocus = \"container\" }: FocusTrapOptions = {},\n): void {\n // Latest-ref so a caller passing an inline `initialFocus` thunk — which every caller\n // will — does not tear the trap down and re-engage it on each render, stealing focus\n // back to the top of the panel mid-interaction. Written in a LAYOUT effect so that\n // the trap's cleanup, which runs after the layout effects of the same commit, reads\n // this render's `active` rather than the last one's — see the cleanup for why.\n const optsRef = useRef({ active, restoreFocus, initialFocus });\n useLayoutEffect(() => {\n optsRef.current = { active, restoreFocus, initialFocus };\n });\n\n // Where focus was BEFORE the trap engaged: the element it is given back to on close.\n //\n // Read DURING the render that turns the trap on, and that timing is the point. React\n // honours a child's `autoFocus` while it commits that render, before any effect runs,\n // so the effect below asking `document.activeElement` would get the child, inside the\n // overlay. Restoring to that on close aimed focus at the dialog that was closing —\n // already out of the DOM, so focus fell to <body> rather than going back to the\n // button that opened it.\n //\n // State adjusted during render (the pattern `Modal` uses for `open`) rather than a\n // ref written there: set once per opening and cleared on release, so a re-render\n // while open cannot replace it with something inside, and StrictMode's second render\n // and second effect both see the same element. `undefined` means \"not captured\";\n // `null` is a capture that found nothing.\n const [opener, setOpener] = useState<Element | null | undefined>(undefined);\n if (active && opener === undefined) setOpener(focusedElement());\n else if (!active && opener !== undefined) setOpener(undefined);\n\n useEffect(() => {\n if (!active) return;\n const container = containerRef.current;\n if (!container) return;\n\n container.setAttribute(TRAP_ATTR, \"\");\n\n /** Is `node` inside a trap nested within this one? */\n const inNestedTrap = (node: Element | null) => {\n if (!node) return false;\n const owner = node.closest(`[${TRAP_ATTR}]`);\n return owner !== null && owner !== container;\n };\n\n const tabbables = () =>\n Array.from(container.querySelectorAll<HTMLElement>(FOCUSABLE)).filter(\n // A tabbable inside a NESTED open overlay belongs to that overlay's trap, not\n // to this one — otherwise the outer list spans both panels and Tab from the\n // inner panel's last control wraps to the OUTER panel's first.\n (el) => isTabStop(el) && !inNestedTrap(el),\n );\n\n const { initialFocus: how } = optsRef.current;\n const focused = document.activeElement;\n // A child that already has focus was given it on purpose — see `initialFocus`.\n const childHasFocus = focused !== container && container.contains(focused);\n if (typeof how === \"function\") {\n (how() ?? container).focus();\n } else if (!childHasFocus) {\n (how === \"first\" ? (tabbables()[0] ?? container) : container).focus();\n }\n\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key !== \"Tab\") return;\n // Only the innermost trap acts. The event bubbles through every ancestor\n // container, so without this an outer dialog and an inner picker both handle one\n // Tab and fight over where focus lands.\n if (inNestedTrap(document.activeElement)) return;\n\n // Recomputed per keystroke — see the note at the top of this file.\n const items = tabbables();\n if (items.length === 0) {\n e.preventDefault();\n container.focus();\n return;\n }\n const first = items[0];\n const last = items[items.length - 1];\n const activeEl = document.activeElement;\n if (e.shiftKey && (activeEl === first || activeEl === container)) {\n e.preventDefault();\n last.focus();\n } else if (!e.shiftKey && activeEl === last) {\n e.preventDefault();\n first.focus();\n }\n };\n\n // On the container, not the document: a portalled child panel (an open dropdown\n // rendered to <body>) is outside this subtree and manages its own focus, which is\n // the behaviour Modal's comment describes and consumers already depend on.\n container.addEventListener(\"keydown\", onKeyDown);\n\n return () => {\n container.removeEventListener(\"keydown\", onKeyDown);\n container.removeAttribute(TRAP_ATTR);\n\n // Only when the trap is really being released: the container has left the page,\n // or `active` has turned false. StrictMode runs this cleanup once more right after\n // mounting, with the overlay still up, and then the effect again — and React does\n // not re-apply `autoFocus` for that second run. Restoring here would pull focus\n // out of the autofocused field for good: live #402 again, in development.\n if (container.isConnected && optsRef.current.active) return;\n if (!optsRef.current.restoreFocus) return;\n // `isConnected` is the whole point: restoring to a node that has been removed\n // sends focus to <body> and the user loses their place entirely. When the\n // original is gone, leave focus where the browser put it rather than moving it\n // somewhere arbitrary — a wrong guess is worse than no move.\n if (opener?.isConnected) (opener as HTMLElement).focus?.();\n };\n }, [active, containerRef, opener]);\n}\n"],"mappings":";AAAA,SAAS,WAAW,iBAAiB,QAAQ,gBAAgB;AAiD7D,MAAM,YACJ;AAQF,MAAM,YAAY,CAAC,OAA6B,GAAG,YAAY,KAAK,CAAC,GAAG,QAAQ,kBAAkB;AAclG,MAAM,YAAY;AAyBlB,SAAS,iBAAiC;AACxC,SAAO,OAAO,aAAa,cAAc,OAAO,SAAS;AAC3D;AAEO,SAAS,aACd,cACA,EAAE,SAAS,MAAM,eAAe,MAAM,eAAe,YAAY,IAAsB,CAAC,GAClF;AAMN,QAAM,UAAU,OAAO,EAAE,QAAQ,cAAc,aAAa,CAAC;AAC7D,kBAAgB,MAAM;AACpB,YAAQ,UAAU,EAAE,QAAQ,cAAc,aAAa;AAAA,EACzD,CAAC;AAgBD,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAqC,MAAS;AAC1E,MAAI,UAAU,WAAW,OAAW,WAAU,eAAe,CAAC;AAAA,WACrD,CAAC,UAAU,WAAW,OAAW,WAAU,MAAS;AAE7D,YAAU,MAAM;AACd,QAAI,CAAC,OAAQ;AACb,UAAM,YAAY,aAAa;AAC/B,QAAI,CAAC,UAAW;AAEhB,cAAU,aAAa,WAAW,EAAE;AAGpC,UAAM,eAAe,CAAC,SAAyB;AAC7C,UAAI,CAAC,KAAM,QAAO;AAClB,YAAM,QAAQ,KAAK,QAAQ,IAAI,SAAS,GAAG;AAC3C,aAAO,UAAU,QAAQ,UAAU;AAAA,IACrC;AAEA,UAAM,YAAY,MAChB,MAAM,KAAK,UAAU,iBAA8B,SAAS,CAAC,EAAE;AAAA;AAAA;AAAA;AAAA,MAI7D,CAAC,OAAO,UAAU,EAAE,KAAK,CAAC,aAAa,EAAE;AAAA,IAC3C;AAEF,UAAM,EAAE,cAAc,IAAI,IAAI,QAAQ;AACtC,UAAM,UAAU,SAAS;AAEzB,UAAM,gBAAgB,YAAY,aAAa,UAAU,SAAS,OAAO;AACzE,QAAI,OAAO,QAAQ,YAAY;AAC7B,OAAC,IAAI,KAAK,WAAW,MAAM;AAAA,IAC7B,WAAW,CAAC,eAAe;AACzB,OAAC,QAAQ,UAAW,UAAU,EAAE,CAAC,KAAK,YAAa,WAAW,MAAM;AAAA,IACtE;AAEA,UAAM,YAAY,CAAC,MAAqB;AACtC,UAAI,EAAE,QAAQ,MAAO;AAIrB,UAAI,aAAa,SAAS,aAAa,EAAG;AAG1C,YAAM,QAAQ,UAAU;AACxB,UAAI,MAAM,WAAW,GAAG;AACtB,UAAE,eAAe;AACjB,kBAAU,MAAM;AAChB;AAAA,MACF;AACA,YAAM,QAAQ,MAAM,CAAC;AACrB,YAAM,OAAO,MAAM,MAAM,SAAS,CAAC;AACnC,YAAM,WAAW,SAAS;AAC1B,UAAI,EAAE,aAAa,aAAa,SAAS,aAAa,YAAY;AAChE,UAAE,eAAe;AACjB,aAAK,MAAM;AAAA,MACb,WAAW,CAAC,EAAE,YAAY,aAAa,MAAM;AAC3C,UAAE,eAAe;AACjB,cAAM,MAAM;AAAA,MACd;AAAA,IACF;AAKA,cAAU,iBAAiB,WAAW,SAAS;AAE/C,WAAO,MAAM;AACX,gBAAU,oBAAoB,WAAW,SAAS;AAClD,gBAAU,gBAAgB,SAAS;AAOnC,UAAI,UAAU,eAAe,QAAQ,QAAQ,OAAQ;AACrD,UAAI,CAAC,QAAQ,QAAQ,aAAc;AAKnC,UAAI,QAAQ,YAAa,CAAC,OAAuB,QAAQ;AAAA,IAC3D;AAAA,EACF,GAAG,CAAC,QAAQ,cAAc,MAAM,CAAC;AACnC;","names":[]}
@@ -116,10 +116,7 @@ function taggedSentinel() {
116
116
  }
117
117
  function retag(id) {
118
118
  const prev = window.history.state;
119
- window.history.replaceState(
120
- { ...prev && typeof prev === "object" ? prev : {}, [SENTINEL_KEY]: id },
121
- ""
122
- );
119
+ window.history.replaceState({ ...prev && typeof prev === "object" ? prev : {}, [SENTINEL_KEY]: id }, "");
123
120
  }
124
121
  function currentSentinel() {
125
122
  const tag = taggedSentinel();
@@ -209,7 +206,15 @@ function useOverlayHistory(open, onClose) {
209
206
  window.history.replaceState(marked, "");
210
207
  for (const owed of inherited.slice(0, -1)) pushed.push({ ...owed, dead: true });
211
208
  const was = inherited[inherited.length - 1];
212
- pushed.push({ id, href: currentHref(), seen: currentHref(), dead: false, idx: was.idx, key: was.key, route: was.route });
209
+ pushed.push({
210
+ id,
211
+ href: currentHref(),
212
+ seen: currentHref(),
213
+ dead: false,
214
+ idx: was.idx,
215
+ key: was.key,
216
+ route: was.route
217
+ });
213
218
  standing = id;
214
219
  } else {
215
220
  const under = pushed.findIndex((p) => p.id === currentSentinel());
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/hooks/use-overlay-history.ts"],"sourcesContent":["import { useEffect, useRef } from \"react\";\n\n/**\n * Back closes the overlay, not the page (Keksdose feedback #172).\n *\n * On a phone the Back gesture is the universal \"dismiss this\" — so a dialog or a\n * full-screen search surface that ignores it doesn't merely fail to close: the\n * navigation lands somewhere else entirely, and on a route with nothing beneath it\n * that means leaving the app with the overlay still notionally open.\n *\n * The mechanism is one throwaway, SAME-URL history entry pushed while the overlay is\n * up. Back then pops a same-document entry — no route change, no re-render of the\n * page under it — and this hook turns that pop into `onClose()`. Same trick the\n * transactions page already uses for its unsaved-edit trap\n * (`use-soft-leave-guard.ts`); the two coexist because they use different marker\n * keys AND because `handlePop` only claims a pop that actually consumed OUR entry\n * — a foreign sentinel stacked on top of ours pops back ONTO us, and an overlay\n * that closed on that would vanish mid-edit (feedback #424/#426).\n *\n * Not a URL parameter, deliberately. Encoding \"a dialog is open\" in the address bar\n * makes it shareable and restorable, which is wrong for the overlays this covers —\n * nobody wants to send someone a link that opens a confirm dialog — and it would\n * fight the page-owned `?row=` / `f.*` params that ARE meaningful state.\n *\n * ## Ordering\n *\n * The stack is module-level because correctness here is about order ACROSS\n * instances: every mounted overlay hears the same `popstate`, but only the one whose\n * entry was actually popped may close. Overlays nest and unwind last-in-first-out —\n * a dialog opened from a search surface closes before the surface — so the top of\n * this stack is by construction the entry the browser just popped.\n *\n * ## Not undoing a real navigation\n *\n * The cleanup pops our entry when the overlay closes some other way (a button,\n * Escape, a backdrop click) so history doesn't silently accumulate dead entries. It\n * first checks that our marker is still the CURRENT entry: if the user followed a\n * link while the overlay was open, the router has pushed on top of us, and a blind\n * `history.back()` there would undo their navigation.\n *\n * ## Not undoing the owner's own close\n *\n * Nor when the address under the entry changed in the same breath as the close\n * (keksdose dev #584). A page that opens its editor by PUSHING `?row=x` and closes it\n * by REPLACING the clean URL over the top entry rewrites OUR entry — the overlay sits\n * above `?row=x` — and that rewrite is what unmounts the overlay. Going back from there\n * lands on `?row=x` and the router opens the editor that was just closed. See\n * {@link noteAddress}: an address the overlay LIVED THROUGH (a filter the table wrote\n * while its sheet was up) is still unwound, because the user has acted since.\n *\n * ## Not undoing the owner's replaces either\n *\n * An address the overlay lived through is unwound as an ENTRY, never as an address\n * (keksdose live #412). A report that writes each tick of its payee sheet into the query\n * with `setSearchParams(…, { replace: true })` rewrites the overlay's own entry, and the\n * page entry beneath still carries the address from before the sheet opened — so the\n * traversal that took our entry away also took every tick with it. See {@link carryOnto}:\n * whichever way the entry goes (the unwind after a press, or a Back press), the address it\n * last showed is carried onto the entry it lands on, and the router is told.\n *\n * ## The traversal is deferred\n *\n * And it is *scheduled* rather than issued, because `history.go()` lands a task\n * later while everything this module knows is written synchronously. See\n * {@link Unwind}: an overlay opening inside that window adopts the entry instead\n * of stacking a second one on it, which is what makes the hook survive React\n * StrictMode's mount → clean up → mount (steering-design feedback #49).\n */\nconst SENTINEL_KEY = \"__hbUiOverlayHistory\";\n\ninterface OverlayEntry {\n id: string;\n close: () => void;\n}\n\nconst stack: OverlayEntry[] = [];\n\n/**\n * The sentinels we have PUSHED and not yet unwound, oldest first.\n *\n * The stack above tracks live overlays; this tracks live history entries, and the\n * two come apart in exactly one case — which is the bug this list exists for.\n * Nested overlays that close in the same commit unwind parent-first, so the dialog's\n * cleanup runs while the sheet's entry is still on top of its own. It cannot call\n * `history.back()` there (that would consume the SHEET's entry, and the sheet is\n * about to consume it itself), so its entry was simply abandoned: a husk with no\n * live owner sitting between the user and the page.\n *\n * The user paid for it on the next Back press. It popped the husk, `handlePop` found\n * an empty stack and returned, and nothing visible happened — Back had to be pressed\n * twice to leave the page.\n *\n * So an abandoned entry is recorded as `dead` instead, and whichever overlay DOES\n * get to unwind pays the debt with a single `history.go(-n)`. `href` is what keeps\n * that safe: only dead entries pushed at the address we are still sitting at are\n * unwound, so a router navigation that landed between two sentinels is never\n * reversed on the user's behalf.\n */\ninterface PushedEntry {\n id: string;\n /** The full address of the entry (query and hash included) as of the last time the\n * user acted while it was current — see {@link noteAddress}. */\n href: string;\n /** Its overlay is gone but the entry is not — someone above owes this pop. */\n dead: boolean;\n /** The router's `idx` on this entry when the marker went on — see {@link locate}. */\n idx: number | undefined;\n /** The router's `key` copied from the page entry beneath ours. */\n key: string | undefined;\n /** {@link routeOf} the address the marker was placed at. */\n route: string;\n /** The entry's address as last OBSERVED, after the user's last gesture had run its\n * handlers — what a Back press must carry down (see {@link carryOnto}). `href` is\n * the snapshot BEFORE the gesture, which the owner's-close check needs instead. */\n seen: string;\n}\nconst pushed: PushedEntry[] = [];\n\n/**\n * Entries of ours that a ROUTER NAVIGATION made from inside the overlay has left\n * behind — ours, still in the history, but no longer on top and no longer anyone's.\n *\n * Nothing can take an entry out from under a newer one, so they cannot be unwound;\n * what can be done is to not let the user land on one. A traversal that comes to\n * rest on a buried entry is carried straight on over the whole run of them, in the\n * direction it was going (see `skipBuried`): Back from the new page lands on the page\n * the overlay was opened from, Forward from there lands on the new page, and no press\n * is spent on an entry with nothing on screen for it.\n *\n * `below` / `above` count the buried entries of the same run on either side. The\n * router's `idx` tells the direction apart — except after a REPLACE navigation, when\n * the page above the run shares the run's `idx`; the two keys decide there.\n */\ninterface BuriedEntry {\n idx: number;\n /** The `idx` of the router entry that buried the run: higher after a push, the\n * same after a replace. */\n aboveIdx: number;\n /** The key of the page entry directly beneath the run. */\n belowKey: string | undefined;\n /** The key of the router entry that buried the run, when it did. */\n aboveKey: string | undefined;\n below: number;\n above: number;\n}\nconst buried = new Map<string, BuriedEntry>();\n\n/** The router `{idx, key}` of the entry the last traversal (or burial) left us on —\n * what `skipBuried` compares against to know which way the user was going. */\nlet lastSeen: { idx: number | undefined; key: string | undefined } | null = null;\n\n/** Entries a real Back press already consumed. Their overlay's cleanup still has to\n * run, and it must not mistake \"my entry is not current\" for \"my entry is buried\"\n * — it has no entry left at all. */\nconst consumed = new Set<string>();\n\n/** Pops WE caused (a cleanup unwinding its own entries) — their `popstate` is\n * bookkeeping, not a user gesture, and must not close a second overlay. A\n * `history.go(-n)` traversal fires exactly ONE popstate however far it travels. */\nlet pendingProgrammatic = 0;\nlet listening = false;\nlet nextId = 0;\n\n/**\n * The unwind a cleanup has asked for and not yet issued.\n *\n * **`history.go()` is asynchronous and everything above is written\n * synchronously**, which is the seam the whole of this block exists to close.\n * The traversal lands a task later, so an overlay that opens in the *same* tick\n * as one that closed used to push its sentinel on top of the entry that was\n * about to be popped — and the pop then took the NEW entry. The overlay left on\n * screen had no entry of its own (so Back no longer closed it) and the old one\n * stayed behind as a husk between the user and their page.\n *\n * That is not an exotic race. **React StrictMode remounts every effect** — mount,\n * clean up, mount again, all in one commit — so in development *every* dialog\n * anyone opened left a husk behind, the position drifted one entry further from\n * the page on each open, and the Back press that was meant to leave the page was\n * spent on something invisible instead (steering-design feedback #49).\n *\n * So the traversal is deferred by a task, and an overlay mounting inside that\n * window **adopts** the entry instead of pushing a second one (see the effect).\n * Adoption is `replaceState` on an entry that is already there: no traversal, no\n * window in which the count can be wrong, and the StrictMode remount costs\n * exactly nothing.\n */\ninterface Unwind {\n timer: ReturnType<typeof setTimeout>;\n /** The sentinel on the entry to leave — the top of `entries`. */\n id: string;\n href: string;\n /** That entry plus the abandoned ones directly beneath it, oldest first. */\n entries: PushedEntry[];\n}\nlet unwind: Unwind | null = null;\n\n/** Issue the deferred traversal, if the ground has not moved under it.\n *\n * A real Back press or a router navigation during the deferred task leaves us\n * somewhere that is not the entry we meant to leave, and going back from there\n * would take something that is not ours. The entries are then simply forgotten\n * rather than handed back as `dead`: a forgotten entry costs one dead Back\n * press, while an over-counted one costs the user a navigation they did make. */\nfunction runUnwind(): void {\n const job = unwind;\n unwind = null;\n if (!job) return;\n if (currentSentinel() !== job.id || currentHref() !== job.href) {\n // A router navigation landed between the cleanup and this task: the entries\n // are under it (or were replaced by it) rather than simply forgotten.\n if (taggedSentinel() === null) buryUnder(job.entries);\n return;\n }\n const bottom = job.entries[0];\n carry = { href: job.href, idx: bottom.idx, key: bottom.key };\n pendingProgrammatic += 1;\n window.history.go(-job.entries.length);\n}\n\n/** The address an unwind in flight must carry onto the entry it lands on. */\nlet carry: { href: string; idx: number | undefined; key: string | undefined } | null = null;\n\n/**\n * Put `href` — the address the overlay's entry last showed — onto the entry a traversal\n * has just landed on, and tell the router (keksdose live #412).\n *\n * Only the query and the hash can differ: an address naming another page is a\n * navigation, never carried. And only onto the page entry the overlay was opened over,\n * which our push copied the router's `{idx, key}` from — a traversal that came to rest\n * anywhere else is not ours to rewrite.\n *\n * The router heard the traversal land on the OLD address in this same event, so a\n * `replaceState` alone would leave it showing the old query under the new one. A\n * synthetic `popstate` makes it read the address again; both reads happen in one event\n * dispatch, so React commits only the second. Ours absorbs it as programmatic.\n */\nfunction carryOnto(href: string, mark: { idx: number | undefined; key: string | undefined }): void {\n const here = currentHref();\n if (here === href || routeOf(here) !== routeOf(href)) return;\n const now = routerMark();\n if (mark.idx !== undefined && now.idx !== undefined && mark.idx !== now.idx) return;\n if (mark.key !== undefined && now.key !== undefined && mark.key !== now.key) return;\n const state: unknown = window.history.state;\n window.history.replaceState(state, \"\", href);\n pendingProgrammatic += 1;\n window.dispatchEvent(new PopStateEvent(\"popstate\", { state }));\n}\n\nfunction currentHref(): string {\n return typeof window === \"undefined\" ? \"\" : window.location.href;\n}\n\n/** The router's own `{idx, key}` on the current entry, where a router has put them. */\nfunction routerMark(): { idx: number | undefined; key: string | undefined } {\n const state = typeof window !== \"undefined\" ? (window.history.state as unknown) : null;\n if (!state || typeof state !== \"object\") return { idx: undefined, key: undefined };\n const { idx, key } = state as Record<string, unknown>;\n return { idx: typeof idx === \"number\" ? idx : undefined, key: typeof key === \"string\" ? key : undefined };\n}\n\n/**\n * The part of an address that names the PAGE — the query string left out.\n *\n * A `setSearchParams(…, { replace: true })` rewrites the query on the entry the\n * overlay is standing on; that is a wipe to repair, not a move. Anything else changing\n * is a navigation. Under a hash router the page lives in the hash (`#/table?f.q=x`), so\n * a hash that starts with `#/` counts up to its own `?`; an ordinary `#fragment` does\n * not name a page and is left out.\n */\nfunction routeOf(href: string): string {\n try {\n const url = new URL(href);\n const hashRoute = url.hash.startsWith(\"#/\") ? url.hash.split(\"?\")[0] : \"\";\n return url.origin + url.pathname + hashRoute;\n } catch {\n return href;\n }\n}\n\n/**\n * Is the untagged entry we are standing on still `p`'s — merely wiped by the router —\n * or an entry the router NAVIGATED to from inside the overlay?\n *\n * The router's `idx` is the one per-entry fact that separates the two, because it is\n * the one the router itself keeps straight: a replace leaves it as it was, a push adds\n * one. Our own push copies the page's state, so our entry carries the page's `idx` and\n * a router push on top of it carries one more. The key would not do — every replace\n * mints a new one, so the wipe this repair exists for would already fail it. Neither\n * would the address alone: a push to the address we are already at is still a push.\n *\n * `idx` does not see one move, the router REPLACING our entry with a different page\n * (same `idx`), so the page named by the address must also be unchanged. Where there is\n * no `idx` to compare (no router, or one that keeps none) the address is all there is.\n */\nfunction isStillOn(p: PushedEntry): boolean {\n if (routeOf(currentHref()) !== p.route) return false;\n const { idx } = routerMark();\n return idx === undefined || p.idx === undefined || idx === p.idx;\n}\n\n/**\n * The router has navigated while our entries were on top: record which of `records`\n * (ours, oldest first, ending with the top one) it buried and which it replaced, and\n * drop them from `pushed` — none of them can be unwound any more. Returns whether it\n * recognised a navigation at all; when it cannot tell (no router `idx` to go by), it\n * leaves everything as it was and the caller falls back to marking the entry dead.\n */\nfunction buryUnder(records: PushedEntry[]): boolean {\n const top = records[records.length - 1];\n const here = routerMark();\n if (!top || top.idx === undefined || here.idx === undefined) return false;\n let run: PushedEntry[];\n if (here.idx > top.idx) {\n run = [];\n for (let i = records.length - 1; i >= 0 && records[i].idx === top.idx; i -= 1) run.unshift(records[i]);\n } else if (here.idx === top.idx && !isStillOn(top)) {\n // Replaced: the top entry IS the new page now, and gone as ours. Those beneath it\n // are buried under it.\n run = [];\n for (let i = records.length - 2; i >= 0 && records[i].idx === top.idx; i -= 1) run.unshift(records[i]);\n } else {\n return false;\n }\n run.forEach((r, i) => {\n buried.set(r.id, {\n idx: top.idx as number,\n aboveIdx: here.idx as number,\n belowKey: run[0].key,\n aboveKey: here.key,\n below: i,\n above: run.length - 1 - i,\n });\n });\n for (const r of records) forgetPushed(r.id);\n lastSeen = here;\n return true;\n}\n\n/**\n * A traversal came to rest on one of our buried entries: carry it on over the run, the\n * way it was going. Back from above (a higher `idx`, or the very entry that buried us)\n * continues down to the page beneath; Forward from that page continues up. A landing\n * from anywhere this cannot place is left alone — one idle press is the price of not\n * guessing, and a wrong guess would move the user somewhere they did not ask to go.\n */\nfunction skipBuried(from: typeof lastSeen): void {\n const tag = taggedSentinel();\n const rec = tag !== null ? buried.get(tag) : undefined;\n if (!rec || !from || stack.some((e) => e.id === tag)) return;\n let steps = 0;\n if ((from.idx !== undefined && from.idx > rec.idx) || (from.key !== undefined && from.key === rec.aboveKey)) {\n steps = -(rec.below + 1);\n } else if (\n from.idx !== undefined &&\n // Below the run, or level with it when nothing above it is (a push buried it),\n // or level with it and provably the page beneath (a replace buried it). The\n // router's first entry has no key at all, so an absent key is a key here.\n (from.idx < rec.idx || (from.idx === rec.idx && (rec.aboveIdx > rec.idx || from.key === rec.belowKey)))\n ) {\n steps = rec.above + 1;\n }\n if (steps === 0) return;\n pendingProgrammatic += 1;\n window.history.go(steps);\n}\n\nfunction forgetPushed(id: string): void {\n const at = pushed.findIndex((p) => p.id === id);\n if (at >= 0) pushed.splice(at, 1);\n}\n\n/**\n * The sentinel this module believes is on the entry we are standing on.\n *\n * `history.state` is where the tag physically sits, because it is the only\n * per-entry storage a browser has — but it is not this module's storage, so\n * since the 2026-09-22 audit it is no longer read as the last word.\n * The ROUTER owns that object and writes it WHOLE: every\n * `setSearchParams(…, { replace: true })` stamps `{usr, key, idx}` over\n * whatever was there. A `DataTable` with `urlSync` does exactly that, on mount\n * and on every filter change — so an overlay opened from such a table had its\n * tag wiped off the entry it was standing on, and then:\n *\n * * its cleanup read \"not my entry\" and abandoned a live entry as a husk;\n * * the push path below could not find itself in `pushed`, so it discarded\n * every record it had (`splice(0)`) and nothing was ever unwound again;\n * * the StrictMode adoption never matched, so in development every dialog\n * pushed a second entry and the deferred `go(-1)` ate the live one — which\n * is Back no longer closing the overlay at all.\n *\n * So `standing` answers when the tag is gone: a MISSING tag on an entry we have\n * not left is a wipe to repair rather than a verdict. The tag is still read\n * FIRST wherever it is there, because it is the one thing that can see a FOREIGN\n * same-URL entry stacked above ours (feedback #424/#426, below) — module-level\n * bookkeeping cannot, since nobody tells us about someone else's `pushState`.\n */\nlet standing: string | null = null;\n\n/** The tag actually on the current entry, with no repair and no belief. */\nfunction taggedSentinel(): string | null {\n const state = typeof window !== \"undefined\" ? window.history.state : null;\n if (!state || typeof state !== \"object\") return null;\n const id = (state as Record<string, unknown>)[SENTINEL_KEY];\n return typeof id === \"string\" ? id : null;\n}\n\n/** Put our tag back on the current entry, keeping whatever the router has since\n * written there — we are a passenger in that object, not its owner. */\nfunction retag(id: string): void {\n const prev = window.history.state;\n window.history.replaceState(\n { ...(prev && typeof prev === \"object\" ? prev : {}), [SENTINEL_KEY]: id },\n \"\",\n );\n}\n\nfunction currentSentinel(): string | null {\n const tag = taggedSentinel();\n if (tag !== null) {\n standing = tag;\n return tag;\n }\n if (standing === null || typeof window === \"undefined\") return null;\n // Only ever re-stamp an entry we still hold a record for. Without that guard a\n // belief left over from an overlay that is long gone — a tab that has since\n // navigated, a test file that ran another case — would be stamped onto a\n // stranger's entry, and the next cleanup would traverse off it.\n //\n // And only an entry that is still the one the marker was placed on. A row that\n // NAVIGATES (a palette result, a dialog's link) has the router push the new page\n // before the overlay's cleanup runs; that entry carries no tag either, and\n // re-stamping it made the cleanup \"unwind\" the navigation itself — the page\n // changed and changed straight back. See `isStillOn` for how the two are told apart.\n const record = pushed.find((p) => p.id === standing);\n if (!record || !isStillOn(record)) {\n standing = null;\n return null;\n }\n retag(standing);\n return standing;\n}\n\n/**\n * The user is about to act: what the current entry's address is NOW is what the\n * overlay standing on it has lived through.\n *\n * The cleanup needs to tell two same-page query rewrites apart, and the address alone\n * cannot. A `DataTable` with `urlSync` replaces `f.*` over the entry while its filter\n * sheet is up, and the sheet is closed LATER, by a press of its own — that entry is\n * still the sheet's and must be unwound (the audit's drift fix, below). A page whose\n * `?row=` drives the overlay replaces the clean URL over the entry and the overlay\n * unmounts BECAUSE of it (keksdose dev #584) — that entry is the owner's now, and\n * unwinding lands on `?row=` beneath and reopens what was just closed. What separates\n * them is whether the user acted in between: the rewrite that closes an overlay is\n * made by the very press that closes it, so it postdates this snapshot.\n *\n * Capture phase, so the snapshot is taken before any handler of the press runs.\n */\nfunction noteAddress(): void {\n const top = pushed[pushed.length - 1];\n if (!top || top.dead || !stack.some((e) => e.id === top.id)) return;\n const tag = taggedSentinel();\n // Read without `currentSentinel`'s repair: an observation must not write history.\n if (tag === top.id || (tag === null && standing === top.id && isStillOn(top))) {\n top.href = currentHref();\n top.seen = top.href;\n }\n}\n\n/** After the user's gesture has run its handlers: the address its entry now shows —\n * what a Back press must carry down (keksdose live #412). Again a little later, for a\n * handler that writes the address a task after the event. */\nfunction noteSeen(): void {\n const top = pushed[pushed.length - 1];\n if (!top || top.dead || !stack.some((e) => e.id === top.id)) return;\n const tag = taggedSentinel();\n if (tag === top.id || (tag === null && standing === top.id && isStillOn(top))) top.seen = currentHref();\n}\n\nfunction noteSeenNowAndLater(): void {\n noteSeen();\n setTimeout(noteSeen, 0);\n}\n\nfunction handlePop() {\n // A pop MOVES us, so the belief is stale by definition: re-read it from the\n // entry we have landed on before anything below consults it, or the repair in\n // `currentSentinel` would stamp the entry we just left onto the one we are on.\n standing = taggedSentinel();\n const from = lastSeen;\n lastSeen = routerMark();\n if (pendingProgrammatic > 0) {\n pendingProgrammatic -= 1;\n const job = carry;\n carry = null;\n if (job) carryOnto(job.href, job);\n return;\n }\n closeOnPop();\n skipBuried(from);\n}\n\nfunction closeOnPop() {\n // Nothing open → an ordinary navigation, which is none of our business.\n const top = stack[stack.length - 1];\n if (!top) return;\n // We landed back ON the top overlay's own entry, so that entry was NOT what the\n // pop consumed — something stacked ABOVE it was (Keksdose feedback #424/#426).\n // The app layers a second same-URL sentinel of its own on top of ours for the\n // unsaved-edit trap (`use-soft-leave-guard.ts`), and it pops that sentinel every\n // time the form goes back to clean — twice-toggling one field was enough to\n // close the whole dialog. A pop is only ours when the entry we came to rest on\n // is no longer ours: either the layer below (a different overlay's marker) or\n // the plain page entry (none at all).\n if (currentSentinel() === top.id) return;\n stack.pop();\n // The browser consumed this entry, so the overlay's own cleanup has nothing left\n // to unwind and nothing to record as owed.\n consumed.add(top.id);\n const record = pushed.find((p) => p.id === top.id);\n forgetPushed(top.id);\n // What the page wrote into the address while we were up stays (#412).\n if (record && !record.dead) carryOnto(record.seen, record);\n top.close();\n}\n\nfunction ensureListening() {\n if (listening || typeof window === \"undefined\") return;\n window.addEventListener(\"popstate\", handlePop);\n window.addEventListener(\"pointerdown\", noteAddress, true);\n window.addEventListener(\"keydown\", noteAddress, true);\n for (const type of [\"click\", \"keyup\", \"change\", \"input\"]) window.addEventListener(type, noteSeenNowAndLater);\n listening = true;\n}\n\n/**\n * Make the platform Back gesture dismiss an overlay.\n *\n * @param open whether the overlay is currently showing. Components that mount\n * only while open (the common case) pass `true`.\n * @param onClose the overlay's own close handler — the same one Escape calls.\n */\nexport function useOverlayHistory(open: boolean, onClose: () => void): void {\n // The listener fires outside React's render, so it reads the latest handler\n // through a ref rather than through the closure the effect captured.\n const closeRef = useRef(onClose);\n useEffect(() => {\n closeRef.current = onClose;\n });\n\n useEffect(() => {\n if (!open || typeof window === \"undefined\") return;\n ensureListening();\n const id = `overlay-${(nextId += 1)}`;\n const entry: OverlayEntry = { id, close: () => closeRef.current() };\n stack.push(entry);\n const prev = window.history.state;\n const marked = { ...(prev && typeof prev === \"object\" ? prev : {}), [SENTINEL_KEY]: id };\n // An entry that is on its way out and that we are still standing on is ours\n // to take over: same URL, same slot, only the marker changes. Pushing a\n // second one on top of it is what used to leave a husk on every StrictMode\n // remount — see {@link Unwind}.\n const adopting = unwind && unwind.id === currentSentinel() && unwind.href === currentHref();\n if (adopting && unwind) {\n clearTimeout(unwind.timer);\n const inherited = unwind.entries;\n unwind = null;\n // Preserve react-router's own `{usr,key,idx}` — we only swap the marker,\n // and the entry we are swapping it on is the one the router is already on.\n window.history.replaceState(marked, \"\");\n // The abandoned ones beneath come back owed; ours takes the top slot.\n for (const owed of inherited.slice(0, -1)) pushed.push({ ...owed, dead: true });\n const was = inherited[inherited.length - 1];\n pushed.push({ id, href: currentHref(), seen: currentHref(), dead: false, idx: was.idx, key: was.key, route: was.route });\n standing = id;\n } else {\n // A push DESTROYS every entry ahead of the one we are on, so anything we\n // still had recorded above our own position is gone. Kept, those would be\n // counted into a later `history.go(-n)` and the overshoot would come out\n // of a navigation the user actually made. Where we cannot tell which of\n // ours are beneath us — we are standing on a plain page entry, or on a\n // husk nobody tracks — none of them can be proven to be, so none survive.\n const under = pushed.findIndex((p) => p.id === currentSentinel());\n pushed.splice(under + 1);\n // Preserve react-router's own `{usr,key,idx}` — we only tack a marker on, and\n // the URL is unchanged, so the router treats popping this as a no-op re-render.\n window.history.pushState(marked, \"\");\n const page = routerMark();\n const at = currentHref();\n pushed.push({ id, href: at, seen: at, dead: false, idx: page.idx, key: page.key, route: routeOf(at) });\n standing = id;\n }\n return () => {\n const at = stack.indexOf(entry);\n if (at >= 0) stack.splice(at, 1);\n\n // A Back press already took this entry; there is nothing to unwind or to owe.\n if (consumed.delete(id)) return;\n\n const mine = pushed.findIndex((p) => p.id === id);\n // Still our entry — same page, same router `idx` — but its address is not the one\n // the user last acted on: the owner rewrote it as part of closing us (see\n // {@link noteAddress}). The entry is the owner's page now, and the one beneath is\n // the address it replaced — going back would restore exactly what was closed.\n // Nothing to unwind; forget the record rather than re-stamp a page we left.\n if (mine >= 0 && currentHref() !== pushed[mine].href) {\n const tag = taggedSentinel();\n if (tag === id || (tag === null && standing === id && isStillOn(pushed[mine]))) {\n pushed.splice(mine, 1);\n if (standing === id) standing = null;\n return;\n }\n }\n // Only unwind an entry that is still THE current one. If a router push landed\n // on top of it, going back would undo the user's navigation instead — and if\n // a SIBLING overlay's sentinel landed on top, that overlay is about to unwind\n // and is the one that can take ours with it.\n if (currentSentinel() !== id) {\n // A router navigation from inside the overlay: nothing to unwind — the\n // entries it left beneath the new page are skipped over instead.\n if (mine >= 0 && taggedSentinel() === null && buryUnder(pushed.slice(0, pushed.length))) return;\n if (mine >= 0) pushed[mine].dead = true;\n return;\n }\n\n // Ours plus every abandoned entry lying directly beneath it at this address.\n // One traversal, so one popstate, so one programmatic pop to absorb.\n const here = currentHref();\n let steps = 1;\n while (steps <= mine && pushed[mine - steps].dead && pushed[mine - steps].href === here) {\n steps += 1;\n }\n const leaving = pushed.splice(mine - (steps - 1), steps);\n // Scheduled, not issued: an overlay opening in this same tick takes the\n // entry over instead, which is what makes the hook survive a StrictMode\n // remount. An unwind already waiting is dropped rather than merged — it\n // was the entry ABOVE ours and something has since moved off it, so its\n // count can no longer be proven, and an over-counted traversal costs a\n // navigation the user made while an under-counted one costs one dead Back.\n if (unwind) clearTimeout(unwind.timer);\n unwind = { timer: setTimeout(runUnwind, 0), id, href: here, entries: leaving };\n };\n }, [open]);\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/hooks/use-overlay-history.ts"],"sourcesContent":["import { useEffect, useRef } from \"react\";\n\n/**\n * Back closes the overlay, not the page (Keksdose feedback #172).\n *\n * On a phone the Back gesture is the universal \"dismiss this\" — so a dialog or a\n * full-screen search surface that ignores it doesn't merely fail to close: the\n * navigation lands somewhere else entirely, and on a route with nothing beneath it\n * that means leaving the app with the overlay still notionally open.\n *\n * The mechanism is one throwaway, SAME-URL history entry pushed while the overlay is\n * up. Back then pops a same-document entry — no route change, no re-render of the\n * page under it — and this hook turns that pop into `onClose()`. Same trick the\n * transactions page already uses for its unsaved-edit trap\n * (`use-soft-leave-guard.ts`); the two coexist because they use different marker\n * keys AND because `handlePop` only claims a pop that actually consumed OUR entry\n * — a foreign sentinel stacked on top of ours pops back ONTO us, and an overlay\n * that closed on that would vanish mid-edit (feedback #424/#426).\n *\n * Not a URL parameter, deliberately. Encoding \"a dialog is open\" in the address bar\n * makes it shareable and restorable, which is wrong for the overlays this covers —\n * nobody wants to send someone a link that opens a confirm dialog — and it would\n * fight the page-owned `?row=` / `f.*` params that ARE meaningful state.\n *\n * ## Ordering\n *\n * The stack is module-level because correctness here is about order ACROSS\n * instances: every mounted overlay hears the same `popstate`, but only the one whose\n * entry was actually popped may close. Overlays nest and unwind last-in-first-out —\n * a dialog opened from a search surface closes before the surface — so the top of\n * this stack is by construction the entry the browser just popped.\n *\n * ## Not undoing a real navigation\n *\n * The cleanup pops our entry when the overlay closes some other way (a button,\n * Escape, a backdrop click) so history doesn't silently accumulate dead entries. It\n * first checks that our marker is still the CURRENT entry: if the user followed a\n * link while the overlay was open, the router has pushed on top of us, and a blind\n * `history.back()` there would undo their navigation.\n *\n * ## Not undoing the owner's own close\n *\n * Nor when the address under the entry changed in the same breath as the close\n * (keksdose dev #584). A page that opens its editor by PUSHING `?row=x` and closes it\n * by REPLACING the clean URL over the top entry rewrites OUR entry — the overlay sits\n * above `?row=x` — and that rewrite is what unmounts the overlay. Going back from there\n * lands on `?row=x` and the router opens the editor that was just closed. See\n * {@link noteAddress}: an address the overlay LIVED THROUGH (a filter the table wrote\n * while its sheet was up) is still unwound, because the user has acted since.\n *\n * ## Not undoing the owner's replaces either\n *\n * An address the overlay lived through is unwound as an ENTRY, never as an address\n * (keksdose live #412). A report that writes each tick of its payee sheet into the query\n * with `setSearchParams(…, { replace: true })` rewrites the overlay's own entry, and the\n * page entry beneath still carries the address from before the sheet opened — so the\n * traversal that took our entry away also took every tick with it. See {@link carryOnto}:\n * whichever way the entry goes (the unwind after a press, or a Back press), the address it\n * last showed is carried onto the entry it lands on, and the router is told.\n *\n * ## The traversal is deferred\n *\n * And it is *scheduled* rather than issued, because `history.go()` lands a task\n * later while everything this module knows is written synchronously. See\n * {@link Unwind}: an overlay opening inside that window adopts the entry instead\n * of stacking a second one on it, which is what makes the hook survive React\n * StrictMode's mount → clean up → mount (steering-design feedback #49).\n */\nconst SENTINEL_KEY = \"__hbUiOverlayHistory\";\n\ninterface OverlayEntry {\n id: string;\n close: () => void;\n}\n\nconst stack: OverlayEntry[] = [];\n\n/**\n * The sentinels we have PUSHED and not yet unwound, oldest first.\n *\n * The stack above tracks live overlays; this tracks live history entries, and the\n * two come apart in exactly one case — which is the bug this list exists for.\n * Nested overlays that close in the same commit unwind parent-first, so the dialog's\n * cleanup runs while the sheet's entry is still on top of its own. It cannot call\n * `history.back()` there (that would consume the SHEET's entry, and the sheet is\n * about to consume it itself), so its entry was simply abandoned: a husk with no\n * live owner sitting between the user and the page.\n *\n * The user paid for it on the next Back press. It popped the husk, `handlePop` found\n * an empty stack and returned, and nothing visible happened — Back had to be pressed\n * twice to leave the page.\n *\n * So an abandoned entry is recorded as `dead` instead, and whichever overlay DOES\n * get to unwind pays the debt with a single `history.go(-n)`. `href` is what keeps\n * that safe: only dead entries pushed at the address we are still sitting at are\n * unwound, so a router navigation that landed between two sentinels is never\n * reversed on the user's behalf.\n */\ninterface PushedEntry {\n id: string;\n /** The full address of the entry (query and hash included) as of the last time the\n * user acted while it was current — see {@link noteAddress}. */\n href: string;\n /** Its overlay is gone but the entry is not — someone above owes this pop. */\n dead: boolean;\n /** The router's `idx` on this entry when the marker went on — see {@link locate}. */\n idx: number | undefined;\n /** The router's `key` copied from the page entry beneath ours. */\n key: string | undefined;\n /** {@link routeOf} the address the marker was placed at. */\n route: string;\n /** The entry's address as last OBSERVED, after the user's last gesture had run its\n * handlers — what a Back press must carry down (see {@link carryOnto}). `href` is\n * the snapshot BEFORE the gesture, which the owner's-close check needs instead. */\n seen: string;\n}\nconst pushed: PushedEntry[] = [];\n\n/**\n * Entries of ours that a ROUTER NAVIGATION made from inside the overlay has left\n * behind — ours, still in the history, but no longer on top and no longer anyone's.\n *\n * Nothing can take an entry out from under a newer one, so they cannot be unwound;\n * what can be done is to not let the user land on one. A traversal that comes to\n * rest on a buried entry is carried straight on over the whole run of them, in the\n * direction it was going (see `skipBuried`): Back from the new page lands on the page\n * the overlay was opened from, Forward from there lands on the new page, and no press\n * is spent on an entry with nothing on screen for it.\n *\n * `below` / `above` count the buried entries of the same run on either side. The\n * router's `idx` tells the direction apart — except after a REPLACE navigation, when\n * the page above the run shares the run's `idx`; the two keys decide there.\n */\ninterface BuriedEntry {\n idx: number;\n /** The `idx` of the router entry that buried the run: higher after a push, the\n * same after a replace. */\n aboveIdx: number;\n /** The key of the page entry directly beneath the run. */\n belowKey: string | undefined;\n /** The key of the router entry that buried the run, when it did. */\n aboveKey: string | undefined;\n below: number;\n above: number;\n}\nconst buried = new Map<string, BuriedEntry>();\n\n/** The router `{idx, key}` of the entry the last traversal (or burial) left us on —\n * what `skipBuried` compares against to know which way the user was going. */\nlet lastSeen: { idx: number | undefined; key: string | undefined } | null = null;\n\n/** Entries a real Back press already consumed. Their overlay's cleanup still has to\n * run, and it must not mistake \"my entry is not current\" for \"my entry is buried\"\n * — it has no entry left at all. */\nconst consumed = new Set<string>();\n\n/** Pops WE caused (a cleanup unwinding its own entries) — their `popstate` is\n * bookkeeping, not a user gesture, and must not close a second overlay. A\n * `history.go(-n)` traversal fires exactly ONE popstate however far it travels. */\nlet pendingProgrammatic = 0;\nlet listening = false;\nlet nextId = 0;\n\n/**\n * The unwind a cleanup has asked for and not yet issued.\n *\n * **`history.go()` is asynchronous and everything above is written\n * synchronously**, which is the seam the whole of this block exists to close.\n * The traversal lands a task later, so an overlay that opens in the *same* tick\n * as one that closed used to push its sentinel on top of the entry that was\n * about to be popped — and the pop then took the NEW entry. The overlay left on\n * screen had no entry of its own (so Back no longer closed it) and the old one\n * stayed behind as a husk between the user and their page.\n *\n * That is not an exotic race. **React StrictMode remounts every effect** — mount,\n * clean up, mount again, all in one commit — so in development *every* dialog\n * anyone opened left a husk behind, the position drifted one entry further from\n * the page on each open, and the Back press that was meant to leave the page was\n * spent on something invisible instead (steering-design feedback #49).\n *\n * So the traversal is deferred by a task, and an overlay mounting inside that\n * window **adopts** the entry instead of pushing a second one (see the effect).\n * Adoption is `replaceState` on an entry that is already there: no traversal, no\n * window in which the count can be wrong, and the StrictMode remount costs\n * exactly nothing.\n */\ninterface Unwind {\n timer: ReturnType<typeof setTimeout>;\n /** The sentinel on the entry to leave — the top of `entries`. */\n id: string;\n href: string;\n /** That entry plus the abandoned ones directly beneath it, oldest first. */\n entries: PushedEntry[];\n}\nlet unwind: Unwind | null = null;\n\n/** Issue the deferred traversal, if the ground has not moved under it.\n *\n * A real Back press or a router navigation during the deferred task leaves us\n * somewhere that is not the entry we meant to leave, and going back from there\n * would take something that is not ours. The entries are then simply forgotten\n * rather than handed back as `dead`: a forgotten entry costs one dead Back\n * press, while an over-counted one costs the user a navigation they did make. */\nfunction runUnwind(): void {\n const job = unwind;\n unwind = null;\n if (!job) return;\n if (currentSentinel() !== job.id || currentHref() !== job.href) {\n // A router navigation landed between the cleanup and this task: the entries\n // are under it (or were replaced by it) rather than simply forgotten.\n if (taggedSentinel() === null) buryUnder(job.entries);\n return;\n }\n const bottom = job.entries[0];\n carry = { href: job.href, idx: bottom.idx, key: bottom.key };\n pendingProgrammatic += 1;\n window.history.go(-job.entries.length);\n}\n\n/** The address an unwind in flight must carry onto the entry it lands on. */\nlet carry: { href: string; idx: number | undefined; key: string | undefined } | null = null;\n\n/**\n * Put `href` — the address the overlay's entry last showed — onto the entry a traversal\n * has just landed on, and tell the router (keksdose live #412).\n *\n * Only the query and the hash can differ: an address naming another page is a\n * navigation, never carried. And only onto the page entry the overlay was opened over,\n * which our push copied the router's `{idx, key}` from — a traversal that came to rest\n * anywhere else is not ours to rewrite.\n *\n * The router heard the traversal land on the OLD address in this same event, so a\n * `replaceState` alone would leave it showing the old query under the new one. A\n * synthetic `popstate` makes it read the address again; both reads happen in one event\n * dispatch, so React commits only the second. Ours absorbs it as programmatic.\n */\nfunction carryOnto(href: string, mark: { idx: number | undefined; key: string | undefined }): void {\n const here = currentHref();\n if (here === href || routeOf(here) !== routeOf(href)) return;\n const now = routerMark();\n if (mark.idx !== undefined && now.idx !== undefined && mark.idx !== now.idx) return;\n if (mark.key !== undefined && now.key !== undefined && mark.key !== now.key) return;\n const state: unknown = window.history.state;\n window.history.replaceState(state, \"\", href);\n pendingProgrammatic += 1;\n window.dispatchEvent(new PopStateEvent(\"popstate\", { state }));\n}\n\nfunction currentHref(): string {\n return typeof window === \"undefined\" ? \"\" : window.location.href;\n}\n\n/** The router's own `{idx, key}` on the current entry, where a router has put them. */\nfunction routerMark(): { idx: number | undefined; key: string | undefined } {\n const state = typeof window !== \"undefined\" ? (window.history.state as unknown) : null;\n if (!state || typeof state !== \"object\") return { idx: undefined, key: undefined };\n const { idx, key } = state as Record<string, unknown>;\n return { idx: typeof idx === \"number\" ? idx : undefined, key: typeof key === \"string\" ? key : undefined };\n}\n\n/**\n * The part of an address that names the PAGE — the query string left out.\n *\n * A `setSearchParams(…, { replace: true })` rewrites the query on the entry the\n * overlay is standing on; that is a wipe to repair, not a move. Anything else changing\n * is a navigation. Under a hash router the page lives in the hash (`#/table?f.q=x`), so\n * a hash that starts with `#/` counts up to its own `?`; an ordinary `#fragment` does\n * not name a page and is left out.\n */\nfunction routeOf(href: string): string {\n try {\n const url = new URL(href);\n const hashRoute = url.hash.startsWith(\"#/\") ? url.hash.split(\"?\")[0] : \"\";\n return url.origin + url.pathname + hashRoute;\n } catch {\n return href;\n }\n}\n\n/**\n * Is the untagged entry we are standing on still `p`'s — merely wiped by the router —\n * or an entry the router NAVIGATED to from inside the overlay?\n *\n * The router's `idx` is the one per-entry fact that separates the two, because it is\n * the one the router itself keeps straight: a replace leaves it as it was, a push adds\n * one. Our own push copies the page's state, so our entry carries the page's `idx` and\n * a router push on top of it carries one more. The key would not do — every replace\n * mints a new one, so the wipe this repair exists for would already fail it. Neither\n * would the address alone: a push to the address we are already at is still a push.\n *\n * `idx` does not see one move, the router REPLACING our entry with a different page\n * (same `idx`), so the page named by the address must also be unchanged. Where there is\n * no `idx` to compare (no router, or one that keeps none) the address is all there is.\n */\nfunction isStillOn(p: PushedEntry): boolean {\n if (routeOf(currentHref()) !== p.route) return false;\n const { idx } = routerMark();\n return idx === undefined || p.idx === undefined || idx === p.idx;\n}\n\n/**\n * The router has navigated while our entries were on top: record which of `records`\n * (ours, oldest first, ending with the top one) it buried and which it replaced, and\n * drop them from `pushed` — none of them can be unwound any more. Returns whether it\n * recognised a navigation at all; when it cannot tell (no router `idx` to go by), it\n * leaves everything as it was and the caller falls back to marking the entry dead.\n */\nfunction buryUnder(records: PushedEntry[]): boolean {\n const top = records[records.length - 1];\n const here = routerMark();\n if (!top || top.idx === undefined || here.idx === undefined) return false;\n let run: PushedEntry[];\n if (here.idx > top.idx) {\n run = [];\n for (let i = records.length - 1; i >= 0 && records[i].idx === top.idx; i -= 1) run.unshift(records[i]);\n } else if (here.idx === top.idx && !isStillOn(top)) {\n // Replaced: the top entry IS the new page now, and gone as ours. Those beneath it\n // are buried under it.\n run = [];\n for (let i = records.length - 2; i >= 0 && records[i].idx === top.idx; i -= 1) run.unshift(records[i]);\n } else {\n return false;\n }\n run.forEach((r, i) => {\n buried.set(r.id, {\n idx: top.idx as number,\n aboveIdx: here.idx as number,\n belowKey: run[0].key,\n aboveKey: here.key,\n below: i,\n above: run.length - 1 - i,\n });\n });\n for (const r of records) forgetPushed(r.id);\n lastSeen = here;\n return true;\n}\n\n/**\n * A traversal came to rest on one of our buried entries: carry it on over the run, the\n * way it was going. Back from above (a higher `idx`, or the very entry that buried us)\n * continues down to the page beneath; Forward from that page continues up. A landing\n * from anywhere this cannot place is left alone — one idle press is the price of not\n * guessing, and a wrong guess would move the user somewhere they did not ask to go.\n */\nfunction skipBuried(from: typeof lastSeen): void {\n const tag = taggedSentinel();\n const rec = tag !== null ? buried.get(tag) : undefined;\n if (!rec || !from || stack.some((e) => e.id === tag)) return;\n let steps = 0;\n if ((from.idx !== undefined && from.idx > rec.idx) || (from.key !== undefined && from.key === rec.aboveKey)) {\n steps = -(rec.below + 1);\n } else if (\n from.idx !== undefined &&\n // Below the run, or level with it when nothing above it is (a push buried it),\n // or level with it and provably the page beneath (a replace buried it). The\n // router's first entry has no key at all, so an absent key is a key here.\n (from.idx < rec.idx || (from.idx === rec.idx && (rec.aboveIdx > rec.idx || from.key === rec.belowKey)))\n ) {\n steps = rec.above + 1;\n }\n if (steps === 0) return;\n pendingProgrammatic += 1;\n window.history.go(steps);\n}\n\nfunction forgetPushed(id: string): void {\n const at = pushed.findIndex((p) => p.id === id);\n if (at >= 0) pushed.splice(at, 1);\n}\n\n/**\n * The sentinel this module believes is on the entry we are standing on.\n *\n * `history.state` is where the tag physically sits, because it is the only\n * per-entry storage a browser has — but it is not this module's storage, so\n * since the 2026-09-22 audit it is no longer read as the last word.\n * The ROUTER owns that object and writes it WHOLE: every\n * `setSearchParams(…, { replace: true })` stamps `{usr, key, idx}` over\n * whatever was there. A `DataTable` with `urlSync` does exactly that, on mount\n * and on every filter change — so an overlay opened from such a table had its\n * tag wiped off the entry it was standing on, and then:\n *\n * * its cleanup read \"not my entry\" and abandoned a live entry as a husk;\n * * the push path below could not find itself in `pushed`, so it discarded\n * every record it had (`splice(0)`) and nothing was ever unwound again;\n * * the StrictMode adoption never matched, so in development every dialog\n * pushed a second entry and the deferred `go(-1)` ate the live one — which\n * is Back no longer closing the overlay at all.\n *\n * So `standing` answers when the tag is gone: a MISSING tag on an entry we have\n * not left is a wipe to repair rather than a verdict. The tag is still read\n * FIRST wherever it is there, because it is the one thing that can see a FOREIGN\n * same-URL entry stacked above ours (feedback #424/#426, below) — module-level\n * bookkeeping cannot, since nobody tells us about someone else's `pushState`.\n */\nlet standing: string | null = null;\n\n/** The tag actually on the current entry, with no repair and no belief. */\nfunction taggedSentinel(): string | null {\n const state = typeof window !== \"undefined\" ? window.history.state : null;\n if (!state || typeof state !== \"object\") return null;\n const id = (state as Record<string, unknown>)[SENTINEL_KEY];\n return typeof id === \"string\" ? id : null;\n}\n\n/** Put our tag back on the current entry, keeping whatever the router has since\n * written there — we are a passenger in that object, not its owner. */\nfunction retag(id: string): void {\n const prev = window.history.state;\n window.history.replaceState({ ...(prev && typeof prev === \"object\" ? prev : {}), [SENTINEL_KEY]: id }, \"\");\n}\n\nfunction currentSentinel(): string | null {\n const tag = taggedSentinel();\n if (tag !== null) {\n standing = tag;\n return tag;\n }\n if (standing === null || typeof window === \"undefined\") return null;\n // Only ever re-stamp an entry we still hold a record for. Without that guard a\n // belief left over from an overlay that is long gone — a tab that has since\n // navigated, a test file that ran another case — would be stamped onto a\n // stranger's entry, and the next cleanup would traverse off it.\n //\n // And only an entry that is still the one the marker was placed on. A row that\n // NAVIGATES (a palette result, a dialog's link) has the router push the new page\n // before the overlay's cleanup runs; that entry carries no tag either, and\n // re-stamping it made the cleanup \"unwind\" the navigation itself — the page\n // changed and changed straight back. See `isStillOn` for how the two are told apart.\n const record = pushed.find((p) => p.id === standing);\n if (!record || !isStillOn(record)) {\n standing = null;\n return null;\n }\n retag(standing);\n return standing;\n}\n\n/**\n * The user is about to act: what the current entry's address is NOW is what the\n * overlay standing on it has lived through.\n *\n * The cleanup needs to tell two same-page query rewrites apart, and the address alone\n * cannot. A `DataTable` with `urlSync` replaces `f.*` over the entry while its filter\n * sheet is up, and the sheet is closed LATER, by a press of its own — that entry is\n * still the sheet's and must be unwound (the audit's drift fix, below). A page whose\n * `?row=` drives the overlay replaces the clean URL over the entry and the overlay\n * unmounts BECAUSE of it (keksdose dev #584) — that entry is the owner's now, and\n * unwinding lands on `?row=` beneath and reopens what was just closed. What separates\n * them is whether the user acted in between: the rewrite that closes an overlay is\n * made by the very press that closes it, so it postdates this snapshot.\n *\n * Capture phase, so the snapshot is taken before any handler of the press runs.\n */\nfunction noteAddress(): void {\n const top = pushed[pushed.length - 1];\n if (!top || top.dead || !stack.some((e) => e.id === top.id)) return;\n const tag = taggedSentinel();\n // Read without `currentSentinel`'s repair: an observation must not write history.\n if (tag === top.id || (tag === null && standing === top.id && isStillOn(top))) {\n top.href = currentHref();\n top.seen = top.href;\n }\n}\n\n/** After the user's gesture has run its handlers: the address its entry now shows —\n * what a Back press must carry down (keksdose live #412). Again a little later, for a\n * handler that writes the address a task after the event. */\nfunction noteSeen(): void {\n const top = pushed[pushed.length - 1];\n if (!top || top.dead || !stack.some((e) => e.id === top.id)) return;\n const tag = taggedSentinel();\n if (tag === top.id || (tag === null && standing === top.id && isStillOn(top))) top.seen = currentHref();\n}\n\nfunction noteSeenNowAndLater(): void {\n noteSeen();\n setTimeout(noteSeen, 0);\n}\n\nfunction handlePop() {\n // A pop MOVES us, so the belief is stale by definition: re-read it from the\n // entry we have landed on before anything below consults it, or the repair in\n // `currentSentinel` would stamp the entry we just left onto the one we are on.\n standing = taggedSentinel();\n const from = lastSeen;\n lastSeen = routerMark();\n if (pendingProgrammatic > 0) {\n pendingProgrammatic -= 1;\n const job = carry;\n carry = null;\n if (job) carryOnto(job.href, job);\n return;\n }\n closeOnPop();\n skipBuried(from);\n}\n\nfunction closeOnPop() {\n // Nothing open → an ordinary navigation, which is none of our business.\n const top = stack[stack.length - 1];\n if (!top) return;\n // We landed back ON the top overlay's own entry, so that entry was NOT what the\n // pop consumed — something stacked ABOVE it was (Keksdose feedback #424/#426).\n // The app layers a second same-URL sentinel of its own on top of ours for the\n // unsaved-edit trap (`use-soft-leave-guard.ts`), and it pops that sentinel every\n // time the form goes back to clean — twice-toggling one field was enough to\n // close the whole dialog. A pop is only ours when the entry we came to rest on\n // is no longer ours: either the layer below (a different overlay's marker) or\n // the plain page entry (none at all).\n if (currentSentinel() === top.id) return;\n stack.pop();\n // The browser consumed this entry, so the overlay's own cleanup has nothing left\n // to unwind and nothing to record as owed.\n consumed.add(top.id);\n const record = pushed.find((p) => p.id === top.id);\n forgetPushed(top.id);\n // What the page wrote into the address while we were up stays (#412).\n if (record && !record.dead) carryOnto(record.seen, record);\n top.close();\n}\n\nfunction ensureListening() {\n if (listening || typeof window === \"undefined\") return;\n window.addEventListener(\"popstate\", handlePop);\n window.addEventListener(\"pointerdown\", noteAddress, true);\n window.addEventListener(\"keydown\", noteAddress, true);\n for (const type of [\"click\", \"keyup\", \"change\", \"input\"]) window.addEventListener(type, noteSeenNowAndLater);\n listening = true;\n}\n\n/**\n * Make the platform Back gesture dismiss an overlay.\n *\n * @param open whether the overlay is currently showing. Components that mount\n * only while open (the common case) pass `true`.\n * @param onClose the overlay's own close handler — the same one Escape calls.\n */\nexport function useOverlayHistory(open: boolean, onClose: () => void): void {\n // The listener fires outside React's render, so it reads the latest handler\n // through a ref rather than through the closure the effect captured.\n const closeRef = useRef(onClose);\n useEffect(() => {\n closeRef.current = onClose;\n });\n\n useEffect(() => {\n if (!open || typeof window === \"undefined\") return;\n ensureListening();\n const id = `overlay-${(nextId += 1)}`;\n const entry: OverlayEntry = { id, close: () => closeRef.current() };\n stack.push(entry);\n const prev = window.history.state;\n const marked = { ...(prev && typeof prev === \"object\" ? prev : {}), [SENTINEL_KEY]: id };\n // An entry that is on its way out and that we are still standing on is ours\n // to take over: same URL, same slot, only the marker changes. Pushing a\n // second one on top of it is what used to leave a husk on every StrictMode\n // remount — see {@link Unwind}.\n const adopting = unwind && unwind.id === currentSentinel() && unwind.href === currentHref();\n if (adopting && unwind) {\n clearTimeout(unwind.timer);\n const inherited = unwind.entries;\n unwind = null;\n // Preserve react-router's own `{usr,key,idx}` — we only swap the marker,\n // and the entry we are swapping it on is the one the router is already on.\n window.history.replaceState(marked, \"\");\n // The abandoned ones beneath come back owed; ours takes the top slot.\n for (const owed of inherited.slice(0, -1)) pushed.push({ ...owed, dead: true });\n const was = inherited[inherited.length - 1];\n pushed.push({\n id,\n href: currentHref(),\n seen: currentHref(),\n dead: false,\n idx: was.idx,\n key: was.key,\n route: was.route,\n });\n standing = id;\n } else {\n // A push DESTROYS every entry ahead of the one we are on, so anything we\n // still had recorded above our own position is gone. Kept, those would be\n // counted into a later `history.go(-n)` and the overshoot would come out\n // of a navigation the user actually made. Where we cannot tell which of\n // ours are beneath us — we are standing on a plain page entry, or on a\n // husk nobody tracks — none of them can be proven to be, so none survive.\n const under = pushed.findIndex((p) => p.id === currentSentinel());\n pushed.splice(under + 1);\n // Preserve react-router's own `{usr,key,idx}` — we only tack a marker on, and\n // the URL is unchanged, so the router treats popping this as a no-op re-render.\n window.history.pushState(marked, \"\");\n const page = routerMark();\n const at = currentHref();\n pushed.push({ id, href: at, seen: at, dead: false, idx: page.idx, key: page.key, route: routeOf(at) });\n standing = id;\n }\n return () => {\n const at = stack.indexOf(entry);\n if (at >= 0) stack.splice(at, 1);\n\n // A Back press already took this entry; there is nothing to unwind or to owe.\n if (consumed.delete(id)) return;\n\n const mine = pushed.findIndex((p) => p.id === id);\n // Still our entry — same page, same router `idx` — but its address is not the one\n // the user last acted on: the owner rewrote it as part of closing us (see\n // {@link noteAddress}). The entry is the owner's page now, and the one beneath is\n // the address it replaced — going back would restore exactly what was closed.\n // Nothing to unwind; forget the record rather than re-stamp a page we left.\n if (mine >= 0 && currentHref() !== pushed[mine].href) {\n const tag = taggedSentinel();\n if (tag === id || (tag === null && standing === id && isStillOn(pushed[mine]))) {\n pushed.splice(mine, 1);\n if (standing === id) standing = null;\n return;\n }\n }\n // Only unwind an entry that is still THE current one. If a router push landed\n // on top of it, going back would undo the user's navigation instead — and if\n // a SIBLING overlay's sentinel landed on top, that overlay is about to unwind\n // and is the one that can take ours with it.\n if (currentSentinel() !== id) {\n // A router navigation from inside the overlay: nothing to unwind — the\n // entries it left beneath the new page are skipped over instead.\n if (mine >= 0 && taggedSentinel() === null && buryUnder(pushed.slice(0, pushed.length))) return;\n if (mine >= 0) pushed[mine].dead = true;\n return;\n }\n\n // Ours plus every abandoned entry lying directly beneath it at this address.\n // One traversal, so one popstate, so one programmatic pop to absorb.\n const here = currentHref();\n let steps = 1;\n while (steps <= mine && pushed[mine - steps].dead && pushed[mine - steps].href === here) {\n steps += 1;\n }\n const leaving = pushed.splice(mine - (steps - 1), steps);\n // Scheduled, not issued: an overlay opening in this same tick takes the\n // entry over instead, which is what makes the hook survive a StrictMode\n // remount. An unwind already waiting is dropped rather than merged — it\n // was the entry ABOVE ours and something has since moved off it, so its\n // count can no longer be proven, and an over-counted traversal costs a\n // navigation the user made while an under-counted one costs one dead Back.\n if (unwind) clearTimeout(unwind.timer);\n unwind = { timer: setTimeout(runUnwind, 0), id, href: here, entries: leaving };\n };\n }, [open]);\n}\n"],"mappings":"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","names":[]}
@@ -24,4 +24,4 @@ export interface RowSwipeReturn {
24
24
  };
25
25
  consumeClick: () => boolean;
26
26
  }
27
- export declare function useRowSwipe({ enabled, leftStages, rightStages, maxDrag, }: RowSwipeOptions): RowSwipeReturn;
27
+ export declare function useRowSwipe({ enabled, leftStages, rightStages, maxDrag }: RowSwipeOptions): RowSwipeReturn;
@@ -15,12 +15,7 @@ function lastArmed(stages, magnitude) {
15
15
  }
16
16
  return idx;
17
17
  }
18
- function useRowSwipe({
19
- enabled,
20
- leftStages,
21
- rightStages,
22
- maxDrag = 252
23
- }) {
18
+ function useRowSwipe({ enabled, leftStages, rightStages, maxDrag = 252 }) {
24
19
  const [dx, setDx] = useState(0);
25
20
  const [dragging, setDragging] = useState(false);
26
21
  const startX = useRef(0);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/hooks/use-row-swipe.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\nimport type { PointerEvent } from \"react\";\n\n// ---------- Mobile swipe gesture ----------\n//\n// Multi-stage half-swipe affordance (feedback #103, extended #9): drag a row\n// horizontally to commit an action; how FAR you drag picks which action, so a\n// single gesture can walk a chain of increasingly involved outcomes (e.g. on a\n// cleared row, a short left-swipe un-clears it, further un-accepts it, further\n// still deletes it). We track via Pointer Events, lock the gesture to the X axis\n// only after the user has moved more horizontally than vertically, and clamp the\n// drag to `maxDrag` so the row can't be pulled across the whole viewport.\n//\n// Each side is an ordered list of stages sorted by ascending `threshold` (px of\n// drag magnitude). On release the LAST stage whose threshold the drag passed is\n// committed; while dragging, `armedRightIndex`/`armedLeftIndex` report which\n// stage is currently armed so the reveal panel can preview its label/colour.\n\nexport interface SwipeStage {\n /** Drag magnitude (px) at which this stage arms. Stages must be sorted ascending. */\n threshold: number;\n onCommit: () => void;\n}\n\n/** Below this much horizontal travel a gesture is treated as a clumsy click, not\n * a swipe: the row still follows the pointer, but the click that follows is NOT\n * swallowed. Without it, `swallowClick` was set the instant the axis lock\n * resolved to \"x\" — 8px — so a slightly-draggy click on a control INSIDE a\n * swipeable row silently did nothing, which is a poor trade on rows whose\n * content is itself interactive (Keksdose feedback #144: budget rows edit their\n * assigned amount and their name by clicking them). */\nconst CLICK_SWALLOW_PX = 24;\n\n/** How far a drag may travel toward a side that has no actions. Zero — the old\n * behaviour — makes the row completely inert in that direction, which reads as\n * \"this row is not swipeable at all\" rather than \"there is nothing on this\n * side\"; that is exactly the report behind Keksdose feedback #144 (\"tried, but\n * there is nothing swipeable\"). A short, resisted travel that snaps back is the\n * conventional answer and the one that answers the question the drag asked. */\nconst EMPTY_SIDE_RUBBER_PX = 28;\n\n/** Resistance applied past a bound: the row keeps moving, at a fraction of the\n * pointer, so the gesture feels answered without ever looking committed. */\nfunction rubberBand(overshoot: number, limit: number): number {\n const eased = limit * (1 - Math.exp(-Math.abs(overshoot) / limit));\n return Math.sign(overshoot) * eased;\n}\n\nexport interface RowSwipeOptions {\n enabled: boolean;\n // Ordered stages per direction (ascending threshold). A longer drag commits a\n // later — usually more involved — stage. An empty/undefined side means that\n // direction rubber-bands a little and snaps back, committing nothing.\n leftStages?: SwipeStage[];\n rightStages?: SwipeStage[];\n maxDrag?: number;\n}\n\nexport interface RowSwipeReturn {\n dx: number;\n dragging: boolean;\n /** Highest armed stage index for the current drag (-1 when none armed). */\n armedRightIndex: number;\n armedLeftIndex: number;\n handlers: {\n onPointerDown: (e: PointerEvent) => void;\n onPointerMove: (e: PointerEvent) => void;\n onPointerUp: (e: PointerEvent) => void;\n onPointerCancel: (e: PointerEvent) => void;\n };\n consumeClick: () => boolean;\n}\n\n/** The highest index whose threshold the magnitude has reached (-1 if none).\n * Stages are ascending, so we can stop at the first one not yet reached. */\nfunction lastArmed(stages: SwipeStage[] | undefined, magnitude: number): number {\n if (!stages) return -1;\n let idx = -1;\n for (let i = 0; i < stages.length; i++) {\n if (magnitude >= stages[i].threshold) idx = i;\n else break;\n }\n return idx;\n}\n\nexport function useRowSwipe({\n enabled,\n leftStages,\n rightStages,\n maxDrag = 252,\n}: RowSwipeOptions): RowSwipeReturn {\n const [dx, setDx] = useState(0);\n const [dragging, setDragging] = useState(false);\n const startX = useRef(0);\n const startY = useRef(0);\n const axis = useRef<\"none\" | \"x\" | \"y\">(\"none\");\n const pointerId = useRef<number | null>(null);\n const dxRef = useRef(0);\n // True after the user has dragged horizontally far enough that the following\n // synthetic click should be swallowed (so a swipe doesn't also expand the row).\n const swallowClick = useRef(false);\n\n // Keep the latest stage arrays in refs so the memoised pointer handlers always\n // commit against the current plan rather than a stale closure — the arrays and\n // their onCommit closures are rebuilt on every render. Written in an effect (not\n // during render) so it satisfies react-hooks/refs; pointer events fire after the\n // commit, so the refs are always current by the time a gesture reads them.\n const leftRef = useRef(leftStages);\n const rightRef = useRef(rightStages);\n useEffect(() => {\n leftRef.current = leftStages;\n rightRef.current = rightStages;\n });\n\n const reset = useCallback(() => {\n setDx(0);\n dxRef.current = 0;\n setDragging(false);\n startX.current = 0;\n startY.current = 0;\n axis.current = \"none\";\n pointerId.current = null;\n }, []);\n\n const onPointerDown = useCallback(\n (e: PointerEvent) => {\n if (!enabled) return;\n if (e.pointerType === \"mouse\" && e.button !== 0) return;\n pointerId.current = e.pointerId;\n startX.current = e.clientX;\n startY.current = e.clientY;\n axis.current = \"none\";\n swallowClick.current = false;\n setDragging(true);\n },\n [enabled],\n );\n\n const onPointerMove = useCallback(\n (e: PointerEvent) => {\n if (pointerId.current !== e.pointerId) return;\n const rawDx = e.clientX - startX.current;\n const rawDy = e.clientY - startY.current;\n if (axis.current === \"none\") {\n if (Math.abs(rawDx) < 8 && Math.abs(rawDy) < 8) return;\n axis.current = Math.abs(rawDx) > Math.abs(rawDy) ? \"x\" : \"y\";\n if (axis.current === \"x\") {\n (e.currentTarget as Element).setPointerCapture?.(e.pointerId);\n } else {\n // Vertical scroll wins — bail out of the gesture entirely.\n reset();\n return;\n }\n }\n if (axis.current !== \"x\") return;\n if (Math.abs(rawDx) >= CLICK_SWALLOW_PX) swallowClick.current = true;\n // Toward a side that HAS actions the row tracks the pointer up to maxDrag.\n // Toward an empty one it rubber-bands a little and snaps back on release —\n // inert would be indistinguishable from \"not swipeable\".\n const hasLeft = (leftRef.current?.length ?? 0) > 0;\n const hasRight = (rightRef.current?.length ?? 0) > 0;\n let next: number;\n if (rawDx > 0) {\n next = hasRight ? Math.min(maxDrag, rawDx) : rubberBand(rawDx, EMPTY_SIDE_RUBBER_PX);\n } else if (rawDx < 0) {\n next = hasLeft ? Math.max(-maxDrag, rawDx) : rubberBand(rawDx, EMPTY_SIDE_RUBBER_PX);\n } else {\n next = 0;\n }\n dxRef.current = next;\n setDx(next);\n },\n [maxDrag, reset],\n );\n\n const onPointerUp = useCallback(\n (e: PointerEvent) => {\n if (pointerId.current !== e.pointerId) return;\n const finalDx = dxRef.current;\n const wasDragX = axis.current === \"x\";\n reset();\n if (!wasDragX) return;\n if (finalDx > 0) {\n const i = lastArmed(rightRef.current, finalDx);\n if (i >= 0) rightRef.current![i].onCommit();\n } else if (finalDx < 0) {\n const i = lastArmed(leftRef.current, -finalDx);\n if (i >= 0) leftRef.current![i].onCommit();\n }\n },\n [reset],\n );\n\n const onPointerCancel = useCallback(() => reset(), [reset]);\n\n const consumeClick = useCallback(() => {\n if (swallowClick.current) {\n swallowClick.current = false;\n return true;\n }\n return false;\n }, []);\n\n return {\n dx,\n dragging,\n armedRightIndex: lastArmed(rightStages, dx > 0 ? dx : 0),\n armedLeftIndex: lastArmed(leftStages, dx < 0 ? -dx : 0),\n handlers: { onPointerDown, onPointerMove, onPointerUp, onPointerCancel },\n consumeClick,\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/hooks/use-row-swipe.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\nimport type { PointerEvent } from \"react\";\n\n// ---------- Mobile swipe gesture ----------\n//\n// Multi-stage half-swipe affordance (feedback #103, extended #9): drag a row\n// horizontally to commit an action; how FAR you drag picks which action, so a\n// single gesture can walk a chain of increasingly involved outcomes (e.g. on a\n// cleared row, a short left-swipe un-clears it, further un-accepts it, further\n// still deletes it). We track via Pointer Events, lock the gesture to the X axis\n// only after the user has moved more horizontally than vertically, and clamp the\n// drag to `maxDrag` so the row can't be pulled across the whole viewport.\n//\n// Each side is an ordered list of stages sorted by ascending `threshold` (px of\n// drag magnitude). On release the LAST stage whose threshold the drag passed is\n// committed; while dragging, `armedRightIndex`/`armedLeftIndex` report which\n// stage is currently armed so the reveal panel can preview its label/colour.\n\nexport interface SwipeStage {\n /** Drag magnitude (px) at which this stage arms. Stages must be sorted ascending. */\n threshold: number;\n onCommit: () => void;\n}\n\n/** Below this much horizontal travel a gesture is treated as a clumsy click, not\n * a swipe: the row still follows the pointer, but the click that follows is NOT\n * swallowed. Without it, `swallowClick` was set the instant the axis lock\n * resolved to \"x\" — 8px — so a slightly-draggy click on a control INSIDE a\n * swipeable row silently did nothing, which is a poor trade on rows whose\n * content is itself interactive (Keksdose feedback #144: budget rows edit their\n * assigned amount and their name by clicking them). */\nconst CLICK_SWALLOW_PX = 24;\n\n/** How far a drag may travel toward a side that has no actions. Zero — the old\n * behaviour — makes the row completely inert in that direction, which reads as\n * \"this row is not swipeable at all\" rather than \"there is nothing on this\n * side\"; that is exactly the report behind Keksdose feedback #144 (\"tried, but\n * there is nothing swipeable\"). A short, resisted travel that snaps back is the\n * conventional answer and the one that answers the question the drag asked. */\nconst EMPTY_SIDE_RUBBER_PX = 28;\n\n/** Resistance applied past a bound: the row keeps moving, at a fraction of the\n * pointer, so the gesture feels answered without ever looking committed. */\nfunction rubberBand(overshoot: number, limit: number): number {\n const eased = limit * (1 - Math.exp(-Math.abs(overshoot) / limit));\n return Math.sign(overshoot) * eased;\n}\n\nexport interface RowSwipeOptions {\n enabled: boolean;\n // Ordered stages per direction (ascending threshold). A longer drag commits a\n // later — usually more involved — stage. An empty/undefined side means that\n // direction rubber-bands a little and snaps back, committing nothing.\n leftStages?: SwipeStage[];\n rightStages?: SwipeStage[];\n maxDrag?: number;\n}\n\nexport interface RowSwipeReturn {\n dx: number;\n dragging: boolean;\n /** Highest armed stage index for the current drag (-1 when none armed). */\n armedRightIndex: number;\n armedLeftIndex: number;\n handlers: {\n onPointerDown: (e: PointerEvent) => void;\n onPointerMove: (e: PointerEvent) => void;\n onPointerUp: (e: PointerEvent) => void;\n onPointerCancel: (e: PointerEvent) => void;\n };\n consumeClick: () => boolean;\n}\n\n/** The highest index whose threshold the magnitude has reached (-1 if none).\n * Stages are ascending, so we can stop at the first one not yet reached. */\nfunction lastArmed(stages: SwipeStage[] | undefined, magnitude: number): number {\n if (!stages) return -1;\n let idx = -1;\n for (let i = 0; i < stages.length; i++) {\n if (magnitude >= stages[i].threshold) idx = i;\n else break;\n }\n return idx;\n}\n\nexport function useRowSwipe({ enabled, leftStages, rightStages, maxDrag = 252 }: RowSwipeOptions): RowSwipeReturn {\n const [dx, setDx] = useState(0);\n const [dragging, setDragging] = useState(false);\n const startX = useRef(0);\n const startY = useRef(0);\n const axis = useRef<\"none\" | \"x\" | \"y\">(\"none\");\n const pointerId = useRef<number | null>(null);\n const dxRef = useRef(0);\n // True after the user has dragged horizontally far enough that the following\n // synthetic click should be swallowed (so a swipe doesn't also expand the row).\n const swallowClick = useRef(false);\n\n // Keep the latest stage arrays in refs so the memoised pointer handlers always\n // commit against the current plan rather than a stale closure — the arrays and\n // their onCommit closures are rebuilt on every render. Written in an effect (not\n // during render) so it satisfies react-hooks/refs; pointer events fire after the\n // commit, so the refs are always current by the time a gesture reads them.\n const leftRef = useRef(leftStages);\n const rightRef = useRef(rightStages);\n useEffect(() => {\n leftRef.current = leftStages;\n rightRef.current = rightStages;\n });\n\n const reset = useCallback(() => {\n setDx(0);\n dxRef.current = 0;\n setDragging(false);\n startX.current = 0;\n startY.current = 0;\n axis.current = \"none\";\n pointerId.current = null;\n }, []);\n\n const onPointerDown = useCallback(\n (e: PointerEvent) => {\n if (!enabled) return;\n if (e.pointerType === \"mouse\" && e.button !== 0) return;\n pointerId.current = e.pointerId;\n startX.current = e.clientX;\n startY.current = e.clientY;\n axis.current = \"none\";\n swallowClick.current = false;\n setDragging(true);\n },\n [enabled],\n );\n\n const onPointerMove = useCallback(\n (e: PointerEvent) => {\n if (pointerId.current !== e.pointerId) return;\n const rawDx = e.clientX - startX.current;\n const rawDy = e.clientY - startY.current;\n if (axis.current === \"none\") {\n if (Math.abs(rawDx) < 8 && Math.abs(rawDy) < 8) return;\n axis.current = Math.abs(rawDx) > Math.abs(rawDy) ? \"x\" : \"y\";\n if (axis.current === \"x\") {\n (e.currentTarget as Element).setPointerCapture?.(e.pointerId);\n } else {\n // Vertical scroll wins — bail out of the gesture entirely.\n reset();\n return;\n }\n }\n if (axis.current !== \"x\") return;\n if (Math.abs(rawDx) >= CLICK_SWALLOW_PX) swallowClick.current = true;\n // Toward a side that HAS actions the row tracks the pointer up to maxDrag.\n // Toward an empty one it rubber-bands a little and snaps back on release —\n // inert would be indistinguishable from \"not swipeable\".\n const hasLeft = (leftRef.current?.length ?? 0) > 0;\n const hasRight = (rightRef.current?.length ?? 0) > 0;\n let next: number;\n if (rawDx > 0) {\n next = hasRight ? Math.min(maxDrag, rawDx) : rubberBand(rawDx, EMPTY_SIDE_RUBBER_PX);\n } else if (rawDx < 0) {\n next = hasLeft ? Math.max(-maxDrag, rawDx) : rubberBand(rawDx, EMPTY_SIDE_RUBBER_PX);\n } else {\n next = 0;\n }\n dxRef.current = next;\n setDx(next);\n },\n [maxDrag, reset],\n );\n\n const onPointerUp = useCallback(\n (e: PointerEvent) => {\n if (pointerId.current !== e.pointerId) return;\n const finalDx = dxRef.current;\n const wasDragX = axis.current === \"x\";\n reset();\n if (!wasDragX) return;\n if (finalDx > 0) {\n const i = lastArmed(rightRef.current, finalDx);\n if (i >= 0) rightRef.current![i].onCommit();\n } else if (finalDx < 0) {\n const i = lastArmed(leftRef.current, -finalDx);\n if (i >= 0) leftRef.current![i].onCommit();\n }\n },\n [reset],\n );\n\n const onPointerCancel = useCallback(() => reset(), [reset]);\n\n const consumeClick = useCallback(() => {\n if (swallowClick.current) {\n swallowClick.current = false;\n return true;\n }\n return false;\n }, []);\n\n return {\n dx,\n dragging,\n armedRightIndex: lastArmed(rightStages, dx > 0 ? dx : 0),\n armedLeftIndex: lastArmed(leftStages, dx < 0 ? -dx : 0),\n handlers: { onPointerDown, onPointerMove, onPointerUp, onPointerCancel },\n consumeClick,\n };\n}\n"],"mappings":"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","names":[]}
@@ -34,10 +34,7 @@ import { DEFAULT_CONFIRM_DIALOG_LABELS } from "../components/confirm-dialog-labe
34
34
  import { DEFAULT_FLOATING_PANEL_LABELS } from "../components/floating-panel-labels.js";
35
35
  import { DEFAULT_FEEDBACK_ATTACHMENT_LABELS } from "../feedback/feedback-attachment-labels.js";
36
36
  import { DEFAULT_FEEDBACK_DIALOG_LABELS } from "../feedback/feedback-dialog-labels.js";
37
- import {
38
- DEFAULT_FEEDBACK_COMPOSER_LABELS,
39
- DEFAULT_FEEDBACK_THREAD_LABELS
40
- } from "../feedback/feedback-thread-labels.js";
37
+ import { DEFAULT_FEEDBACK_COMPOSER_LABELS, DEFAULT_FEEDBACK_THREAD_LABELS } from "../feedback/feedback-thread-labels.js";
41
38
  import {
42
39
  DEFAULT_FEEDBACK_CATEGORY_LABELS,
43
40
  DEFAULT_FEEDBACK_STATUS_LABELS,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/i18n/defaults.ts"],"sourcesContent":["import { DEFAULT_DATA_TABLE_LABELS } from \"../components/data-table-labels\";\nimport { DEFAULT_MINI_CALENDAR_LABELS } from \"../components/mini-calendar-labels\";\nimport { DEFAULT_CALENDAR_HEATMAP_LABELS } from \"../components/calendar-heatmap-labels\";\nimport { DEFAULT_MONTH_PICKER_LABELS } from \"../components/month-picker-labels\";\nimport { DEFAULT_PAGE_CONTENTS_LABELS } from \"../components/page-contents-labels\";\nimport { DEFAULT_POPOVER_LABELS } from \"../components/popover-labels\";\nimport { DEFAULT_CHIP_INPUT_LABELS } from \"../components/chip-labels\";\nimport { DEFAULT_FIELD_SYNC_LABELS } from \"../components/field-sync-labels\";\nimport {\n DEFAULT_CHARACTER_COUNT_LABELS,\n DEFAULT_PASSWORD_REVEAL_LABELS,\n DEFAULT_TABS_LABELS,\n} from \"../components/ui-labels\";\nimport { DEFAULT_WIZARD_LABELS } from \"../wizard/wizard-labels\";\nimport { DEFAULT_TOUR_LABELS } from \"../tour/tour-labels\";\nimport { DEFAULT_TOUR_CATALOGUE_LABELS } from \"../tour/tour-catalogue-labels\";\nimport { DEFAULT_TOUR_GUIDE_LABELS } from \"../tour/tour-guide-labels\";\nimport { DEFAULT_COMMAND_PALETTE_LABELS } from \"../search/command-palette-labels\";\nimport { DEFAULT_GLOBAL_SEARCH_LABELS } from \"../search/global-search-labels\";\nimport { DEFAULT_SERIES_CHART_LABELS } from \"../components/series-chart-labels\";\nimport { DEFAULT_PIE_CHART_LABELS } from \"../components/pie-chart-labels\";\nimport { DEFAULT_SPARKLINE_LABELS } from \"../components/sparkline-labels\";\nimport { DEFAULT_STAT_TILE_LABELS } from \"../components/stat-tile-labels\";\nimport { DEFAULT_SIGNATURE_PAD_LABELS } from \"../components/signature-pad-labels\";\nimport { DEFAULT_PASSWORD_STRENGTH_LABELS } from \"../components/password-strength-labels\";\nimport { DEFAULT_DANGER_CONFIRM_LABELS } from \"../components/danger-confirm-labels\";\nimport { DEFAULT_SWATCH_PICKER_LABELS } from \"../components/swatch-picker-labels\";\nimport { DEFAULT_ICON_PICKER_LABELS } from \"../components/icon-picker-labels\";\nimport { DEFAULT_DIALOG_FRAME_LABELS } from \"../components/dialog-frame-labels\";\nimport { DEFAULT_FILE_PICKER_LABELS } from \"../hooks/use-file-picker-labels\";\nimport { DEFAULT_MEASURED_GRID_LABELS } from \"../components/measured-grid-labels\";\nimport { DEFAULT_CONFIRM_DIALOG_LABELS } from \"../components/confirm-dialog-labels\";\nimport { DEFAULT_FLOATING_PANEL_LABELS } from \"../components/floating-panel-labels\";\nimport { DEFAULT_FEEDBACK_ATTACHMENT_LABELS } from \"../feedback/feedback-attachment-labels\";\nimport { DEFAULT_FEEDBACK_DIALOG_LABELS } from \"../feedback/feedback-dialog-labels\";\nimport {\n DEFAULT_FEEDBACK_COMPOSER_LABELS,\n DEFAULT_FEEDBACK_THREAD_LABELS,\n} from \"../feedback/feedback-thread-labels\";\nimport {\n DEFAULT_FEEDBACK_CATEGORY_LABELS,\n DEFAULT_FEEDBACK_STATUS_LABELS,\n DEFAULT_FEEDBACK_TOAST_LABELS,\n} from \"../feedback/feedback-labels\";\nimport { DEFAULT_FEEDBACK_MENU_LABELS } from \"../feedback/feedback-menu-labels\";\nimport { DEFAULT_FEEDBACK_PUBLIC_LABELS } from \"../feedback/feedback-public-labels\";\nimport { DEFAULT_FEEDBACK_NOTIFICATION_TYPES_LABELS } from \"../notifications/feedback-notification-types-labels\";\nimport { DEFAULT_FEEDBACK_CONTEXT_LABELS } from \"../feedback/feedback-context-labels\";\nimport { DEFAULT_FEEDBACK_PAGE_LABELS } from \"../feedback/feedback-table-labels\";\nimport { DEFAULT_FEEDBACK_DETAIL_LABELS } from \"../feedback/feedback-row-detail-labels\";\nimport {\n DEFAULT_FEEDBACK_COMMENTS_LABELS,\n DEFAULT_FEEDBACK_REPLIES_LABELS,\n DEFAULT_FEEDBACK_VOTE_LABELS,\n} from \"../feedback/feedback-thread-parts-labels\";\nimport { DEFAULT_ACCOUNT_SETTINGS_LABELS } from \"../account/account-settings-labels\";\nimport { DEFAULT_COPY_BUTTON_LABELS } from \"../components/copy-button-labels\";\nimport { DEFAULT_BULK_ACTION_BAR_LABELS } from \"../components/bulk-action-bar-labels\";\nimport { DEFAULT_LIST_LABELS } from \"../components/list-labels\";\nimport { DEFAULT_BREADCRUMBS_LABELS } from \"../components/breadcrumbs-labels\";\nimport { DEFAULT_TOAST_LABELS } from \"../components/toast-labels\";\nimport { DEFAULT_FORM_ACTIONS_LABELS } from \"../components/form-actions-labels\";\nimport { DEFAULT_DESCRIPTION_LIST_LABELS } from \"../components/description-list-labels\";\nimport { DEFAULT_LINE_ITEMS_LABELS } from \"../components/line-items-labels\";\nimport { DEFAULT_PROGRESS_BAR_LABELS } from \"../components/progress-bar-labels\";\nimport { DEFAULT_SIGNED_AMOUNT_LABELS } from \"../components/signed-amount-labels\";\nimport { DEFAULT_ERROR_BOUNDARY_LABELS } from \"../components/error-boundary-labels\";\nimport { DEFAULT_AUTHED_IMAGE_LABELS } from \"../components/authed-image-labels\";\nimport { DEFAULT_IMAGE_GRID_LABELS } from \"../components/image-grid-labels\";\nimport { DEFAULT_LIGHTBOX_LABELS } from \"../components/lightbox-labels\";\nimport { DEFAULT_WRITE_LOCK_LABELS } from \"../components/write-lock-labels\";\nimport { DEFAULT_LEGAL_LABELS } from \"../components/legal-labels\";\nimport { DEFAULT_SIGN_IN_LABELS } from \"../auth/sign-in-form-labels\";\nimport { DEFAULT_REGISTER_LABELS } from \"../auth/register-form-labels\";\nimport { DEFAULT_COMPLETE_NAME_LABELS } from \"../auth/complete-name-dialog-labels\";\nimport { DEFAULT_FORGOT_PASSWORD_LABELS } from \"../auth/forgot-password-form-labels\";\nimport { DEFAULT_RESET_PASSWORD_LABELS } from \"../auth/reset-password-form-labels\";\nimport { DEFAULT_VERIFY_EMAIL_LABELS } from \"../auth/verify-email-labels\";\nimport { DEFAULT_NOT_FOUND_LABELS } from \"../auth/not-found-page-labels\";\nimport { DEFAULT_ACCEPT_INVITATION_LABELS } from \"../auth/accept-invitation-labels\";\nimport { DEFAULT_COMPANY_SWITCHER_LABELS } from \"../shell/company-switcher-labels\";\nimport { DEFAULT_EMAIL_CHANGE_LABELS } from \"../account/email-change-setting-labels\";\nimport { DEFAULT_SESSIONS_LABELS } from \"../account/sessions-setting-labels\";\nimport { DEFAULT_DELETE_ACCOUNT_LABELS } from \"../account/delete-account-setting-labels\";\nimport { DEFAULT_DATA_EXPORT_LABELS } from \"../account/data-export-setting-labels\";\nimport { DEFAULT_USER_ROSTER_LABELS } from \"../admin/user-roster-labels\";\nimport { DEFAULT_ROLE_SELECT_LABELS } from \"../admin/role-select-labels\";\nimport { DEFAULT_REVIEWER_SCOPE_LABELS } from \"../admin/reviewer-scope-editor-labels\";\nimport { DEFAULT_ADMIN_ACTION_LABELS } from \"../admin/admin-action-confirm-labels\";\nimport { DEFAULT_ADMIN_ACTION_LOG_LABELS } from \"../admin/admin-action-log-labels\";\nimport { DEFAULT_TRANSFER_OWNERSHIP_LABELS } from \"../admin/transfer-ownership-dialog-labels\";\nimport { DEFAULT_INVITATIONS_LABELS } from \"../admin/invitations-panel-labels\";\nimport { DEFAULT_SETTINGS_LABELS } from \"../settings/settings-labels\";\nimport { DEFAULT_LANDING_LABELS } from \"../landing/landing-labels\";\nimport { DEFAULT_DEMO_LABELS } from \"../demo/demo-labels\";\nimport { DEFAULT_BILLING_LABELS } from \"../billing/billing-labels\";\nimport { DEFAULT_PLAN_CHANGE_LABELS } from \"../admin/plan-change-labels\";\nimport { DEFAULT_APPEARANCE_LABELS } from \"../components/appearance-labels\";\nimport { DEFAULT_DEVICE_RESET_LABELS } from \"../settings/device-reset-setting-labels\";\nimport { DEFAULT_ROW_ACTIONS_LABELS } from \"../components/row-actions-labels\";\nimport { DEFAULT_APP_SHELL_MORE_LABELS } from \"../shell/app-shell-labels\";\nimport { DEFAULT_ACCOUNT_STATE_LABELS } from \"../components/account-chips-labels\";\nimport { DEFAULT_ADMIN_AREA_LABELS } from \"../admin/admin-area-labels\";\nimport { DEFAULT_MAIL_TEST_CARD_LABELS } from \"../admin/mail-test-card-labels\";\nimport { DEFAULT_SIGN_OUT_PENDING_DIALOG_LABELS } from \"../offline/sign-out-pending-dialog-labels\";\nimport { DEFAULT_PWA_UPDATE_PROMPT_LABELS } from \"../offline/pwa-update-prompt-labels\";\nimport { DEFAULT_SHARED_COMPUTER_LABELS } from \"../offline/shared-computer-labels\";\nimport { DEFAULT_TOP_BAR_PARTS_LABELS } from \"../shell/top-bar-parts-labels\";\nimport { DEFAULT_SHARE_CARD_LABELS } from \"../components/share-card-labels\";\nimport { DEFAULT_REAUTH_DIALOG_LABELS } from \"../components/reauth-dialog-labels\";\nimport { DEFAULT_SERVER_WAKE_LABELS } from \"../components/server-wake-labels\";\nimport { DEFAULT_TRANSLATION_REVIEW_LABELS } from \"../components/translation-review-labels\";\nimport { DEFAULT_COUNTRY_SELECT_LABELS } from \"../components/country-select-labels\";\nimport { DEFAULT_INLINE_EDIT_LABELS } from \"../components/inline-edit-field-labels\";\nimport { DEFAULT_IBAN_INPUT_LABELS } from \"../components/iban-input-labels\";\nimport { DEFAULT_PHONE_INPUT_LABELS } from \"../components/phone-input-labels\";\nimport { DEFAULT_SIGN_CHIP_LABELS } from \"../components/sign-chip-labels\";\nimport { DEFAULT_COLUMN_MAPPER_LABELS } from \"../components/column-mapper-labels\";\nimport { DEFAULT_NOTIFICATIONS_LABELS } from \"../notifications/notifications-labels\";\nimport {\n DEFAULT_APP_SHELL_LABELS,\n DEFAULT_CALCULATOR_LABELS,\n DEFAULT_COMBOBOX_LABELS,\n DEFAULT_COMMON_LABELS,\n DEFAULT_CURRENCY_LABELS,\n DEFAULT_DATE_PICKER_LABELS,\n DEFAULT_FILE_LABELS,\n DEFAULT_MULTI_SELECT_LABELS,\n DEFAULT_PICKER_SHEET_LABELS,\n DEFAULT_SWIPEABLE_ROW_LABELS,\n DEFAULT_TOP_BAR_LABELS,\n} from \"./shared-labels\";\nimport type { UiKitLabels } from \"./kit-labels\";\n\n/**\n * The whole English tree in one object: what a translation is written against, and\n * the `reference` for {@link missingKitLabels}.\n *\n * A separate module from the provider on purpose. Components import the provider's\n * hooks; this file imports the components' defaults. Kept together they would be an\n * import cycle through every component in the kit.\n *\n * Every import here is a module of WORDS — a `*-labels.ts` beside its component, which\n * the component re-exports (`shared-labels` for the namespaces the provider types\n * itself, which `kit-labels` re-exports) — never a component. The\n * English catalogue (`locales/en.ts`) is built on this object, so a component imported\n * here rides along with English: in 0.33 that was 79 of them, and an app fetching `en`\n * lazily fetched ~40 chunks of component code with it, where every other language is one\n * (0.34, KS U8). `__tests__/english-catalogue-graph.test.ts` holds the line, and the\n * build makes `dist/i18n/locales/en.js` one file (scripts/flatten-en.mjs).\n */\nexport const DEFAULT_UI_KIT_LABELS: UiKitLabels = {\n common: DEFAULT_COMMON_LABELS,\n dataTable: DEFAULT_DATA_TABLE_LABELS,\n miniCalendar: DEFAULT_MINI_CALENDAR_LABELS,\n calendarHeatmap: DEFAULT_CALENDAR_HEATMAP_LABELS,\n datePicker: DEFAULT_DATE_PICKER_LABELS,\n monthPicker: DEFAULT_MONTH_PICKER_LABELS,\n popover: DEFAULT_POPOVER_LABELS,\n combobox: DEFAULT_COMBOBOX_LABELS,\n multiSelect: DEFAULT_MULTI_SELECT_LABELS,\n calculator: DEFAULT_CALCULATOR_LABELS,\n currency: DEFAULT_CURRENCY_LABELS,\n chipInput: DEFAULT_CHIP_INPUT_LABELS,\n fieldSync: DEFAULT_FIELD_SYNC_LABELS,\n passwordReveal: DEFAULT_PASSWORD_REVEAL_LABELS,\n tabs: DEFAULT_TABS_LABELS,\n appShell: DEFAULT_APP_SHELL_LABELS,\n pageContents: DEFAULT_PAGE_CONTENTS_LABELS,\n topBar: DEFAULT_TOP_BAR_LABELS,\n pickerSheet: DEFAULT_PICKER_SHEET_LABELS,\n swipeableRow: DEFAULT_SWIPEABLE_ROW_LABELS,\n file: DEFAULT_FILE_LABELS,\n wizard: DEFAULT_WIZARD_LABELS,\n tour: DEFAULT_TOUR_LABELS,\n tourCatalogue: DEFAULT_TOUR_CATALOGUE_LABELS,\n tourGuide: DEFAULT_TOUR_GUIDE_LABELS,\n commandPalette: DEFAULT_COMMAND_PALETTE_LABELS,\n globalSearch: DEFAULT_GLOBAL_SEARCH_LABELS,\n seriesChart: DEFAULT_SERIES_CHART_LABELS,\n pieChart: DEFAULT_PIE_CHART_LABELS,\n sparkline: DEFAULT_SPARKLINE_LABELS,\n statTile: DEFAULT_STAT_TILE_LABELS,\n signaturePad: DEFAULT_SIGNATURE_PAD_LABELS,\n passwordStrength: DEFAULT_PASSWORD_STRENGTH_LABELS,\n dangerConfirm: DEFAULT_DANGER_CONFIRM_LABELS,\n swatchPicker: DEFAULT_SWATCH_PICKER_LABELS,\n iconPicker: DEFAULT_ICON_PICKER_LABELS,\n dialogFrame: DEFAULT_DIALOG_FRAME_LABELS,\n filePicker: DEFAULT_FILE_PICKER_LABELS,\n measuredGrid: DEFAULT_MEASURED_GRID_LABELS,\n feedbackAttachment: DEFAULT_FEEDBACK_ATTACHMENT_LABELS,\n feedbackDialog: DEFAULT_FEEDBACK_DIALOG_LABELS,\n feedbackThread: DEFAULT_FEEDBACK_THREAD_LABELS,\n feedbackComposer: DEFAULT_FEEDBACK_COMPOSER_LABELS,\n feedbackStatus: DEFAULT_FEEDBACK_STATUS_LABELS,\n feedbackCategory: DEFAULT_FEEDBACK_CATEGORY_LABELS,\n feedbackToast: DEFAULT_FEEDBACK_TOAST_LABELS,\n feedbackMenu: DEFAULT_FEEDBACK_MENU_LABELS,\n feedbackPublic: DEFAULT_FEEDBACK_PUBLIC_LABELS,\n feedbackNotificationTypes: DEFAULT_FEEDBACK_NOTIFICATION_TYPES_LABELS,\n feedbackContext: DEFAULT_FEEDBACK_CONTEXT_LABELS,\n feedbackPage: DEFAULT_FEEDBACK_PAGE_LABELS,\n feedbackDetail: DEFAULT_FEEDBACK_DETAIL_LABELS,\n feedbackVote: DEFAULT_FEEDBACK_VOTE_LABELS,\n feedbackComments: DEFAULT_FEEDBACK_COMMENTS_LABELS,\n feedbackReplies: DEFAULT_FEEDBACK_REPLIES_LABELS,\n accountSettings: DEFAULT_ACCOUNT_SETTINGS_LABELS,\n confirmDialog: DEFAULT_CONFIRM_DIALOG_LABELS,\n floatingPanel: DEFAULT_FLOATING_PANEL_LABELS,\n copyButton: DEFAULT_COPY_BUTTON_LABELS,\n bulkActionBar: DEFAULT_BULK_ACTION_BAR_LABELS,\n list: DEFAULT_LIST_LABELS,\n breadcrumbs: DEFAULT_BREADCRUMBS_LABELS,\n toast: DEFAULT_TOAST_LABELS,\n form: DEFAULT_FORM_ACTIONS_LABELS,\n descriptionList: DEFAULT_DESCRIPTION_LIST_LABELS,\n lineItems: DEFAULT_LINE_ITEMS_LABELS,\n progressBar: DEFAULT_PROGRESS_BAR_LABELS,\n signedAmount: DEFAULT_SIGNED_AMOUNT_LABELS,\n errorBoundary: DEFAULT_ERROR_BOUNDARY_LABELS,\n authedImage: DEFAULT_AUTHED_IMAGE_LABELS,\n imageGrid: DEFAULT_IMAGE_GRID_LABELS,\n lightbox: DEFAULT_LIGHTBOX_LABELS,\n writeLock: DEFAULT_WRITE_LOCK_LABELS,\n accountState: DEFAULT_ACCOUNT_STATE_LABELS,\n shareCard: DEFAULT_SHARE_CARD_LABELS,\n reauthDialog: DEFAULT_REAUTH_DIALOG_LABELS,\n serverWake: DEFAULT_SERVER_WAKE_LABELS,\n translationReview: DEFAULT_TRANSLATION_REVIEW_LABELS,\n legal: DEFAULT_LEGAL_LABELS,\n signIn: DEFAULT_SIGN_IN_LABELS,\n register: DEFAULT_REGISTER_LABELS,\n completeName: DEFAULT_COMPLETE_NAME_LABELS,\n forgotPassword: DEFAULT_FORGOT_PASSWORD_LABELS,\n resetPassword: DEFAULT_RESET_PASSWORD_LABELS,\n verifyEmail: DEFAULT_VERIFY_EMAIL_LABELS,\n notFound: DEFAULT_NOT_FOUND_LABELS,\n acceptInvitation: DEFAULT_ACCEPT_INVITATION_LABELS,\n companySwitcher: DEFAULT_COMPANY_SWITCHER_LABELS,\n emailChange: DEFAULT_EMAIL_CHANGE_LABELS,\n sessions: DEFAULT_SESSIONS_LABELS,\n deleteAccount: DEFAULT_DELETE_ACCOUNT_LABELS,\n dataExport: DEFAULT_DATA_EXPORT_LABELS,\n userRoster: DEFAULT_USER_ROSTER_LABELS,\n roleSelect: DEFAULT_ROLE_SELECT_LABELS,\n reviewerScope: DEFAULT_REVIEWER_SCOPE_LABELS,\n adminAction: DEFAULT_ADMIN_ACTION_LABELS,\n adminActionLog: DEFAULT_ADMIN_ACTION_LOG_LABELS,\n transferOwnership: DEFAULT_TRANSFER_OWNERSHIP_LABELS,\n invitations: DEFAULT_INVITATIONS_LABELS,\n settings: DEFAULT_SETTINGS_LABELS,\n landing: DEFAULT_LANDING_LABELS,\n demo: DEFAULT_DEMO_LABELS,\n billing: DEFAULT_BILLING_LABELS,\n planChange: DEFAULT_PLAN_CHANGE_LABELS,\n appearance: DEFAULT_APPEARANCE_LABELS,\n deviceReset: DEFAULT_DEVICE_RESET_LABELS,\n rowActions: DEFAULT_ROW_ACTIONS_LABELS,\n appShellMore: DEFAULT_APP_SHELL_MORE_LABELS,\n characterCount: DEFAULT_CHARACTER_COUNT_LABELS,\n countrySelect: DEFAULT_COUNTRY_SELECT_LABELS,\n inlineEdit: DEFAULT_INLINE_EDIT_LABELS,\n ibanInput: DEFAULT_IBAN_INPUT_LABELS,\n phoneInput: DEFAULT_PHONE_INPUT_LABELS,\n signChip: DEFAULT_SIGN_CHIP_LABELS,\n columnMapper: DEFAULT_COLUMN_MAPPER_LABELS,\n notifications: DEFAULT_NOTIFICATIONS_LABELS,\n adminArea: DEFAULT_ADMIN_AREA_LABELS,\n topBarParts: DEFAULT_TOP_BAR_PARTS_LABELS,\n mailTestCard: DEFAULT_MAIL_TEST_CARD_LABELS,\n signOutPending: DEFAULT_SIGN_OUT_PENDING_DIALOG_LABELS,\n pwaUpdate: DEFAULT_PWA_UPDATE_PROMPT_LABELS,\n sharedComputer: DEFAULT_SHARED_COMPUTER_LABELS,\n};\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/i18n/defaults.ts"],"sourcesContent":["import { DEFAULT_DATA_TABLE_LABELS } from \"../components/data-table-labels\";\nimport { DEFAULT_MINI_CALENDAR_LABELS } from \"../components/mini-calendar-labels\";\nimport { DEFAULT_CALENDAR_HEATMAP_LABELS } from \"../components/calendar-heatmap-labels\";\nimport { DEFAULT_MONTH_PICKER_LABELS } from \"../components/month-picker-labels\";\nimport { DEFAULT_PAGE_CONTENTS_LABELS } from \"../components/page-contents-labels\";\nimport { DEFAULT_POPOVER_LABELS } from \"../components/popover-labels\";\nimport { DEFAULT_CHIP_INPUT_LABELS } from \"../components/chip-labels\";\nimport { DEFAULT_FIELD_SYNC_LABELS } from \"../components/field-sync-labels\";\nimport {\n DEFAULT_CHARACTER_COUNT_LABELS,\n DEFAULT_PASSWORD_REVEAL_LABELS,\n DEFAULT_TABS_LABELS,\n} from \"../components/ui-labels\";\nimport { DEFAULT_WIZARD_LABELS } from \"../wizard/wizard-labels\";\nimport { DEFAULT_TOUR_LABELS } from \"../tour/tour-labels\";\nimport { DEFAULT_TOUR_CATALOGUE_LABELS } from \"../tour/tour-catalogue-labels\";\nimport { DEFAULT_TOUR_GUIDE_LABELS } from \"../tour/tour-guide-labels\";\nimport { DEFAULT_COMMAND_PALETTE_LABELS } from \"../search/command-palette-labels\";\nimport { DEFAULT_GLOBAL_SEARCH_LABELS } from \"../search/global-search-labels\";\nimport { DEFAULT_SERIES_CHART_LABELS } from \"../components/series-chart-labels\";\nimport { DEFAULT_PIE_CHART_LABELS } from \"../components/pie-chart-labels\";\nimport { DEFAULT_SPARKLINE_LABELS } from \"../components/sparkline-labels\";\nimport { DEFAULT_STAT_TILE_LABELS } from \"../components/stat-tile-labels\";\nimport { DEFAULT_SIGNATURE_PAD_LABELS } from \"../components/signature-pad-labels\";\nimport { DEFAULT_PASSWORD_STRENGTH_LABELS } from \"../components/password-strength-labels\";\nimport { DEFAULT_DANGER_CONFIRM_LABELS } from \"../components/danger-confirm-labels\";\nimport { DEFAULT_SWATCH_PICKER_LABELS } from \"../components/swatch-picker-labels\";\nimport { DEFAULT_ICON_PICKER_LABELS } from \"../components/icon-picker-labels\";\nimport { DEFAULT_DIALOG_FRAME_LABELS } from \"../components/dialog-frame-labels\";\nimport { DEFAULT_FILE_PICKER_LABELS } from \"../hooks/use-file-picker-labels\";\nimport { DEFAULT_MEASURED_GRID_LABELS } from \"../components/measured-grid-labels\";\nimport { DEFAULT_CONFIRM_DIALOG_LABELS } from \"../components/confirm-dialog-labels\";\nimport { DEFAULT_FLOATING_PANEL_LABELS } from \"../components/floating-panel-labels\";\nimport { DEFAULT_FEEDBACK_ATTACHMENT_LABELS } from \"../feedback/feedback-attachment-labels\";\nimport { DEFAULT_FEEDBACK_DIALOG_LABELS } from \"../feedback/feedback-dialog-labels\";\nimport { DEFAULT_FEEDBACK_COMPOSER_LABELS, DEFAULT_FEEDBACK_THREAD_LABELS } from \"../feedback/feedback-thread-labels\";\nimport {\n DEFAULT_FEEDBACK_CATEGORY_LABELS,\n DEFAULT_FEEDBACK_STATUS_LABELS,\n DEFAULT_FEEDBACK_TOAST_LABELS,\n} from \"../feedback/feedback-labels\";\nimport { DEFAULT_FEEDBACK_MENU_LABELS } from \"../feedback/feedback-menu-labels\";\nimport { DEFAULT_FEEDBACK_PUBLIC_LABELS } from \"../feedback/feedback-public-labels\";\nimport { DEFAULT_FEEDBACK_NOTIFICATION_TYPES_LABELS } from \"../notifications/feedback-notification-types-labels\";\nimport { DEFAULT_FEEDBACK_CONTEXT_LABELS } from \"../feedback/feedback-context-labels\";\nimport { DEFAULT_FEEDBACK_PAGE_LABELS } from \"../feedback/feedback-table-labels\";\nimport { DEFAULT_FEEDBACK_DETAIL_LABELS } from \"../feedback/feedback-row-detail-labels\";\nimport {\n DEFAULT_FEEDBACK_COMMENTS_LABELS,\n DEFAULT_FEEDBACK_REPLIES_LABELS,\n DEFAULT_FEEDBACK_VOTE_LABELS,\n} from \"../feedback/feedback-thread-parts-labels\";\nimport { DEFAULT_ACCOUNT_SETTINGS_LABELS } from \"../account/account-settings-labels\";\nimport { DEFAULT_COPY_BUTTON_LABELS } from \"../components/copy-button-labels\";\nimport { DEFAULT_BULK_ACTION_BAR_LABELS } from \"../components/bulk-action-bar-labels\";\nimport { DEFAULT_LIST_LABELS } from \"../components/list-labels\";\nimport { DEFAULT_BREADCRUMBS_LABELS } from \"../components/breadcrumbs-labels\";\nimport { DEFAULT_TOAST_LABELS } from \"../components/toast-labels\";\nimport { DEFAULT_FORM_ACTIONS_LABELS } from \"../components/form-actions-labels\";\nimport { DEFAULT_DESCRIPTION_LIST_LABELS } from \"../components/description-list-labels\";\nimport { DEFAULT_LINE_ITEMS_LABELS } from \"../components/line-items-labels\";\nimport { DEFAULT_PROGRESS_BAR_LABELS } from \"../components/progress-bar-labels\";\nimport { DEFAULT_SIGNED_AMOUNT_LABELS } from \"../components/signed-amount-labels\";\nimport { DEFAULT_ERROR_BOUNDARY_LABELS } from \"../components/error-boundary-labels\";\nimport { DEFAULT_AUTHED_IMAGE_LABELS } from \"../components/authed-image-labels\";\nimport { DEFAULT_IMAGE_GRID_LABELS } from \"../components/image-grid-labels\";\nimport { DEFAULT_LIGHTBOX_LABELS } from \"../components/lightbox-labels\";\nimport { DEFAULT_WRITE_LOCK_LABELS } from \"../components/write-lock-labels\";\nimport { DEFAULT_LEGAL_LABELS } from \"../components/legal-labels\";\nimport { DEFAULT_SIGN_IN_LABELS } from \"../auth/sign-in-form-labels\";\nimport { DEFAULT_REGISTER_LABELS } from \"../auth/register-form-labels\";\nimport { DEFAULT_COMPLETE_NAME_LABELS } from \"../auth/complete-name-dialog-labels\";\nimport { DEFAULT_FORGOT_PASSWORD_LABELS } from \"../auth/forgot-password-form-labels\";\nimport { DEFAULT_RESET_PASSWORD_LABELS } from \"../auth/reset-password-form-labels\";\nimport { DEFAULT_VERIFY_EMAIL_LABELS } from \"../auth/verify-email-labels\";\nimport { DEFAULT_NOT_FOUND_LABELS } from \"../auth/not-found-page-labels\";\nimport { DEFAULT_ACCEPT_INVITATION_LABELS } from \"../auth/accept-invitation-labels\";\nimport { DEFAULT_COMPANY_SWITCHER_LABELS } from \"../shell/company-switcher-labels\";\nimport { DEFAULT_EMAIL_CHANGE_LABELS } from \"../account/email-change-setting-labels\";\nimport { DEFAULT_SESSIONS_LABELS } from \"../account/sessions-setting-labels\";\nimport { DEFAULT_DELETE_ACCOUNT_LABELS } from \"../account/delete-account-setting-labels\";\nimport { DEFAULT_DATA_EXPORT_LABELS } from \"../account/data-export-setting-labels\";\nimport { DEFAULT_USER_ROSTER_LABELS } from \"../admin/user-roster-labels\";\nimport { DEFAULT_ROLE_SELECT_LABELS } from \"../admin/role-select-labels\";\nimport { DEFAULT_REVIEWER_SCOPE_LABELS } from \"../admin/reviewer-scope-editor-labels\";\nimport { DEFAULT_ADMIN_ACTION_LABELS } from \"../admin/admin-action-confirm-labels\";\nimport { DEFAULT_ADMIN_ACTION_LOG_LABELS } from \"../admin/admin-action-log-labels\";\nimport { DEFAULT_TRANSFER_OWNERSHIP_LABELS } from \"../admin/transfer-ownership-dialog-labels\";\nimport { DEFAULT_INVITATIONS_LABELS } from \"../admin/invitations-panel-labels\";\nimport { DEFAULT_SETTINGS_LABELS } from \"../settings/settings-labels\";\nimport { DEFAULT_LANDING_LABELS } from \"../landing/landing-labels\";\nimport { DEFAULT_DEMO_LABELS } from \"../demo/demo-labels\";\nimport { DEFAULT_BILLING_LABELS } from \"../billing/billing-labels\";\nimport { DEFAULT_PLAN_CHANGE_LABELS } from \"../admin/plan-change-labels\";\nimport { DEFAULT_APPEARANCE_LABELS } from \"../components/appearance-labels\";\nimport { DEFAULT_DEVICE_RESET_LABELS } from \"../settings/device-reset-setting-labels\";\nimport { DEFAULT_ROW_ACTIONS_LABELS } from \"../components/row-actions-labels\";\nimport { DEFAULT_APP_SHELL_MORE_LABELS } from \"../shell/app-shell-labels\";\nimport { DEFAULT_ACCOUNT_STATE_LABELS } from \"../components/account-chips-labels\";\nimport { DEFAULT_ADMIN_AREA_LABELS } from \"../admin/admin-area-labels\";\nimport { DEFAULT_MAIL_TEST_CARD_LABELS } from \"../admin/mail-test-card-labels\";\nimport { DEFAULT_SIGN_OUT_PENDING_DIALOG_LABELS } from \"../offline/sign-out-pending-dialog-labels\";\nimport { DEFAULT_PWA_UPDATE_PROMPT_LABELS } from \"../offline/pwa-update-prompt-labels\";\nimport { DEFAULT_SHARED_COMPUTER_LABELS } from \"../offline/shared-computer-labels\";\nimport { DEFAULT_TOP_BAR_PARTS_LABELS } from \"../shell/top-bar-parts-labels\";\nimport { DEFAULT_SHARE_CARD_LABELS } from \"../components/share-card-labels\";\nimport { DEFAULT_REAUTH_DIALOG_LABELS } from \"../components/reauth-dialog-labels\";\nimport { DEFAULT_SERVER_WAKE_LABELS } from \"../components/server-wake-labels\";\nimport { DEFAULT_TRANSLATION_REVIEW_LABELS } from \"../components/translation-review-labels\";\nimport { DEFAULT_COUNTRY_SELECT_LABELS } from \"../components/country-select-labels\";\nimport { DEFAULT_INLINE_EDIT_LABELS } from \"../components/inline-edit-field-labels\";\nimport { DEFAULT_IBAN_INPUT_LABELS } from \"../components/iban-input-labels\";\nimport { DEFAULT_PHONE_INPUT_LABELS } from \"../components/phone-input-labels\";\nimport { DEFAULT_SIGN_CHIP_LABELS } from \"../components/sign-chip-labels\";\nimport { DEFAULT_COLUMN_MAPPER_LABELS } from \"../components/column-mapper-labels\";\nimport { DEFAULT_NOTIFICATIONS_LABELS } from \"../notifications/notifications-labels\";\nimport {\n DEFAULT_APP_SHELL_LABELS,\n DEFAULT_CALCULATOR_LABELS,\n DEFAULT_COMBOBOX_LABELS,\n DEFAULT_COMMON_LABELS,\n DEFAULT_CURRENCY_LABELS,\n DEFAULT_DATE_PICKER_LABELS,\n DEFAULT_FILE_LABELS,\n DEFAULT_MULTI_SELECT_LABELS,\n DEFAULT_PICKER_SHEET_LABELS,\n DEFAULT_SWIPEABLE_ROW_LABELS,\n DEFAULT_TOP_BAR_LABELS,\n} from \"./shared-labels\";\nimport type { UiKitLabels } from \"./kit-labels\";\n\n/**\n * The whole English tree in one object: what a translation is written against, and\n * the `reference` for {@link missingKitLabels}.\n *\n * A separate module from the provider on purpose. Components import the provider's\n * hooks; this file imports the components' defaults. Kept together they would be an\n * import cycle through every component in the kit.\n *\n * Every import here is a module of WORDS — a `*-labels.ts` beside its component, which\n * the component re-exports (`shared-labels` for the namespaces the provider types\n * itself, which `kit-labels` re-exports) — never a component. The\n * English catalogue (`locales/en.ts`) is built on this object, so a component imported\n * here rides along with English: in 0.33 that was 79 of them, and an app fetching `en`\n * lazily fetched ~40 chunks of component code with it, where every other language is one\n * (0.34, KS U8). `__tests__/english-catalogue-graph.test.ts` holds the line, and the\n * build makes `dist/i18n/locales/en.js` one file (scripts/flatten-en.mjs).\n */\nexport const DEFAULT_UI_KIT_LABELS: UiKitLabels = {\n common: DEFAULT_COMMON_LABELS,\n dataTable: DEFAULT_DATA_TABLE_LABELS,\n miniCalendar: DEFAULT_MINI_CALENDAR_LABELS,\n calendarHeatmap: DEFAULT_CALENDAR_HEATMAP_LABELS,\n datePicker: DEFAULT_DATE_PICKER_LABELS,\n monthPicker: DEFAULT_MONTH_PICKER_LABELS,\n popover: DEFAULT_POPOVER_LABELS,\n combobox: DEFAULT_COMBOBOX_LABELS,\n multiSelect: DEFAULT_MULTI_SELECT_LABELS,\n calculator: DEFAULT_CALCULATOR_LABELS,\n currency: DEFAULT_CURRENCY_LABELS,\n chipInput: DEFAULT_CHIP_INPUT_LABELS,\n fieldSync: DEFAULT_FIELD_SYNC_LABELS,\n passwordReveal: DEFAULT_PASSWORD_REVEAL_LABELS,\n tabs: DEFAULT_TABS_LABELS,\n appShell: DEFAULT_APP_SHELL_LABELS,\n pageContents: DEFAULT_PAGE_CONTENTS_LABELS,\n topBar: DEFAULT_TOP_BAR_LABELS,\n pickerSheet: DEFAULT_PICKER_SHEET_LABELS,\n swipeableRow: DEFAULT_SWIPEABLE_ROW_LABELS,\n file: DEFAULT_FILE_LABELS,\n wizard: DEFAULT_WIZARD_LABELS,\n tour: DEFAULT_TOUR_LABELS,\n tourCatalogue: DEFAULT_TOUR_CATALOGUE_LABELS,\n tourGuide: DEFAULT_TOUR_GUIDE_LABELS,\n commandPalette: DEFAULT_COMMAND_PALETTE_LABELS,\n globalSearch: DEFAULT_GLOBAL_SEARCH_LABELS,\n seriesChart: DEFAULT_SERIES_CHART_LABELS,\n pieChart: DEFAULT_PIE_CHART_LABELS,\n sparkline: DEFAULT_SPARKLINE_LABELS,\n statTile: DEFAULT_STAT_TILE_LABELS,\n signaturePad: DEFAULT_SIGNATURE_PAD_LABELS,\n passwordStrength: DEFAULT_PASSWORD_STRENGTH_LABELS,\n dangerConfirm: DEFAULT_DANGER_CONFIRM_LABELS,\n swatchPicker: DEFAULT_SWATCH_PICKER_LABELS,\n iconPicker: DEFAULT_ICON_PICKER_LABELS,\n dialogFrame: DEFAULT_DIALOG_FRAME_LABELS,\n filePicker: DEFAULT_FILE_PICKER_LABELS,\n measuredGrid: DEFAULT_MEASURED_GRID_LABELS,\n feedbackAttachment: DEFAULT_FEEDBACK_ATTACHMENT_LABELS,\n feedbackDialog: DEFAULT_FEEDBACK_DIALOG_LABELS,\n feedbackThread: DEFAULT_FEEDBACK_THREAD_LABELS,\n feedbackComposer: DEFAULT_FEEDBACK_COMPOSER_LABELS,\n feedbackStatus: DEFAULT_FEEDBACK_STATUS_LABELS,\n feedbackCategory: DEFAULT_FEEDBACK_CATEGORY_LABELS,\n feedbackToast: DEFAULT_FEEDBACK_TOAST_LABELS,\n feedbackMenu: DEFAULT_FEEDBACK_MENU_LABELS,\n feedbackPublic: DEFAULT_FEEDBACK_PUBLIC_LABELS,\n feedbackNotificationTypes: DEFAULT_FEEDBACK_NOTIFICATION_TYPES_LABELS,\n feedbackContext: DEFAULT_FEEDBACK_CONTEXT_LABELS,\n feedbackPage: DEFAULT_FEEDBACK_PAGE_LABELS,\n feedbackDetail: DEFAULT_FEEDBACK_DETAIL_LABELS,\n feedbackVote: DEFAULT_FEEDBACK_VOTE_LABELS,\n feedbackComments: DEFAULT_FEEDBACK_COMMENTS_LABELS,\n feedbackReplies: DEFAULT_FEEDBACK_REPLIES_LABELS,\n accountSettings: DEFAULT_ACCOUNT_SETTINGS_LABELS,\n confirmDialog: DEFAULT_CONFIRM_DIALOG_LABELS,\n floatingPanel: DEFAULT_FLOATING_PANEL_LABELS,\n copyButton: DEFAULT_COPY_BUTTON_LABELS,\n bulkActionBar: DEFAULT_BULK_ACTION_BAR_LABELS,\n list: DEFAULT_LIST_LABELS,\n breadcrumbs: DEFAULT_BREADCRUMBS_LABELS,\n toast: DEFAULT_TOAST_LABELS,\n form: DEFAULT_FORM_ACTIONS_LABELS,\n descriptionList: DEFAULT_DESCRIPTION_LIST_LABELS,\n lineItems: DEFAULT_LINE_ITEMS_LABELS,\n progressBar: DEFAULT_PROGRESS_BAR_LABELS,\n signedAmount: DEFAULT_SIGNED_AMOUNT_LABELS,\n errorBoundary: DEFAULT_ERROR_BOUNDARY_LABELS,\n authedImage: DEFAULT_AUTHED_IMAGE_LABELS,\n imageGrid: DEFAULT_IMAGE_GRID_LABELS,\n lightbox: DEFAULT_LIGHTBOX_LABELS,\n writeLock: DEFAULT_WRITE_LOCK_LABELS,\n accountState: DEFAULT_ACCOUNT_STATE_LABELS,\n shareCard: DEFAULT_SHARE_CARD_LABELS,\n reauthDialog: DEFAULT_REAUTH_DIALOG_LABELS,\n serverWake: DEFAULT_SERVER_WAKE_LABELS,\n translationReview: DEFAULT_TRANSLATION_REVIEW_LABELS,\n legal: DEFAULT_LEGAL_LABELS,\n signIn: DEFAULT_SIGN_IN_LABELS,\n register: DEFAULT_REGISTER_LABELS,\n completeName: DEFAULT_COMPLETE_NAME_LABELS,\n forgotPassword: DEFAULT_FORGOT_PASSWORD_LABELS,\n resetPassword: DEFAULT_RESET_PASSWORD_LABELS,\n verifyEmail: DEFAULT_VERIFY_EMAIL_LABELS,\n notFound: DEFAULT_NOT_FOUND_LABELS,\n acceptInvitation: DEFAULT_ACCEPT_INVITATION_LABELS,\n companySwitcher: DEFAULT_COMPANY_SWITCHER_LABELS,\n emailChange: DEFAULT_EMAIL_CHANGE_LABELS,\n sessions: DEFAULT_SESSIONS_LABELS,\n deleteAccount: DEFAULT_DELETE_ACCOUNT_LABELS,\n dataExport: DEFAULT_DATA_EXPORT_LABELS,\n userRoster: DEFAULT_USER_ROSTER_LABELS,\n roleSelect: DEFAULT_ROLE_SELECT_LABELS,\n reviewerScope: DEFAULT_REVIEWER_SCOPE_LABELS,\n adminAction: DEFAULT_ADMIN_ACTION_LABELS,\n adminActionLog: DEFAULT_ADMIN_ACTION_LOG_LABELS,\n transferOwnership: DEFAULT_TRANSFER_OWNERSHIP_LABELS,\n invitations: DEFAULT_INVITATIONS_LABELS,\n settings: DEFAULT_SETTINGS_LABELS,\n landing: DEFAULT_LANDING_LABELS,\n demo: DEFAULT_DEMO_LABELS,\n billing: DEFAULT_BILLING_LABELS,\n planChange: DEFAULT_PLAN_CHANGE_LABELS,\n appearance: DEFAULT_APPEARANCE_LABELS,\n deviceReset: DEFAULT_DEVICE_RESET_LABELS,\n rowActions: DEFAULT_ROW_ACTIONS_LABELS,\n appShellMore: DEFAULT_APP_SHELL_MORE_LABELS,\n characterCount: DEFAULT_CHARACTER_COUNT_LABELS,\n countrySelect: DEFAULT_COUNTRY_SELECT_LABELS,\n inlineEdit: DEFAULT_INLINE_EDIT_LABELS,\n ibanInput: DEFAULT_IBAN_INPUT_LABELS,\n phoneInput: DEFAULT_PHONE_INPUT_LABELS,\n signChip: DEFAULT_SIGN_CHIP_LABELS,\n columnMapper: DEFAULT_COLUMN_MAPPER_LABELS,\n notifications: DEFAULT_NOTIFICATIONS_LABELS,\n adminArea: DEFAULT_ADMIN_AREA_LABELS,\n topBarParts: DEFAULT_TOP_BAR_PARTS_LABELS,\n mailTestCard: DEFAULT_MAIL_TEST_CARD_LABELS,\n signOutPending: DEFAULT_SIGN_OUT_PENDING_DIALOG_LABELS,\n pwaUpdate: DEFAULT_PWA_UPDATE_PROMPT_LABELS,\n sharedComputer: DEFAULT_SHARED_COMPUTER_LABELS,\n};\n"],"mappings":"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","names":[]}