@eifi1/ui-kit 0.51.7 → 0.53.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (775) hide show
  1. package/README.md +178 -67
  2. package/dist/account/account-parts.d.ts +10 -8
  3. package/dist/account/account-parts.js +1 -18
  4. package/dist/account/account-parts.js.map +1 -1
  5. package/dist/account/data-export-setting-labels.js +1 -1
  6. package/dist/account/data-export-setting-labels.js.map +1 -1
  7. package/dist/account/delete-account-setting-labels.js +1 -1
  8. package/dist/account/delete-account-setting-labels.js.map +1 -1
  9. package/dist/account/email-change-setting-labels.js +1 -1
  10. package/dist/account/email-change-setting-labels.js.map +1 -1
  11. package/dist/account/passkeys-setting.d.ts +9 -2
  12. package/dist/account/passkeys-setting.js +41 -29
  13. package/dist/account/passkeys-setting.js.map +1 -1
  14. package/dist/account/sessions-setting.d.ts +3 -2
  15. package/dist/account/sessions-setting.js +10 -8
  16. package/dist/account/sessions-setting.js.map +1 -1
  17. package/dist/admin/admin-action-confirm.js +1 -3
  18. package/dist/admin/admin-action-confirm.js.map +1 -1
  19. package/dist/admin/admin-parts.d.ts +6 -3
  20. package/dist/admin/admin-parts.js +6 -21
  21. package/dist/admin/admin-parts.js.map +1 -1
  22. package/dist/admin/mail-test-card-labels.js +1 -1
  23. package/dist/admin/mail-test-card-labels.js.map +1 -1
  24. package/dist/admin/plan-change-labels.d.ts +2 -2
  25. package/dist/admin/plan-change-labels.js +1 -17
  26. package/dist/admin/plan-change-labels.js.map +1 -1
  27. package/dist/admin/plan-change.d.ts +3 -42
  28. package/dist/admin/plan-change.js +1 -1
  29. package/dist/admin/plan-change.js.map +1 -1
  30. package/dist/admin/reviewer-scope-editor.js +3 -3
  31. package/dist/admin/reviewer-scope-editor.js.map +1 -1
  32. package/dist/auth/auth-errors-labels.d.ts +11 -0
  33. package/dist/auth/auth-errors-labels.js +13 -0
  34. package/dist/auth/auth-errors-labels.js.map +1 -0
  35. package/dist/auth/auth-errors.d.ts +7 -23
  36. package/dist/auth/auth-errors.js +2 -15
  37. package/dist/auth/auth-errors.js.map +1 -1
  38. package/dist/auth/complete-name-dialog.js +2 -1
  39. package/dist/auth/complete-name-dialog.js.map +1 -1
  40. package/dist/auth/forgot-password-form-labels.js +1 -1
  41. package/dist/auth/forgot-password-form-labels.js.map +1 -1
  42. package/dist/auth/forgot-password-form.js +7 -2
  43. package/dist/auth/forgot-password-form.js.map +1 -1
  44. package/dist/auth/register-form-labels.js +1 -1
  45. package/dist/auth/register-form-labels.js.map +1 -1
  46. package/dist/auth/reset-password-form-labels.js +1 -1
  47. package/dist/auth/reset-password-form-labels.js.map +1 -1
  48. package/dist/auth/reset-password-form.js +5 -1
  49. package/dist/auth/reset-password-form.js.map +1 -1
  50. package/dist/auth/sign-in-form-labels.js +1 -1
  51. package/dist/auth/sign-in-form-labels.js.map +1 -1
  52. package/dist/auth/status-parts.d.ts +0 -3
  53. package/dist/auth/status-parts.js +0 -4
  54. package/dist/auth/status-parts.js.map +1 -1
  55. package/dist/auth/verify-email-labels.js +1 -1
  56. package/dist/auth/verify-email-labels.js.map +1 -1
  57. package/dist/auth/verify-email.js +7 -2
  58. package/dist/auth/verify-email.js.map +1 -1
  59. package/dist/billing/cancel-subscription.d.ts +10 -7
  60. package/dist/billing/cancel-subscription.js +6 -6
  61. package/dist/billing/cancel-subscription.js.map +1 -1
  62. package/dist/billing/plan-card.d.ts +5 -3
  63. package/dist/billing/plan-card.js +7 -8
  64. package/dist/billing/plan-card.js.map +1 -1
  65. package/dist/billing/plan-change-labels.d.ts +42 -0
  66. package/dist/billing/plan-change-labels.js +22 -0
  67. package/dist/billing/plan-change-labels.js.map +1 -0
  68. package/dist/billing/plan-switch.d.ts +9 -6
  69. package/dist/billing/plan-switch.js +7 -5
  70. package/dist/billing/plan-switch.js.map +1 -1
  71. package/dist/chart.d.ts +4 -3
  72. package/dist/chart.js +72 -4
  73. package/dist/chart.js.map +1 -1
  74. package/dist/components/account-chips.d.ts +5 -5
  75. package/dist/components/account-chips.js +11 -13
  76. package/dist/components/account-chips.js.map +1 -1
  77. package/dist/components/alert-banner.d.ts +16 -5
  78. package/dist/components/alert-banner.js +46 -1
  79. package/dist/components/alert-banner.js.map +1 -1
  80. package/dist/components/amount-input.d.ts +13 -0
  81. package/dist/components/amount-input.js +1 -0
  82. package/dist/components/amount-input.js.map +1 -1
  83. package/dist/components/autocomplete.js +57 -65
  84. package/dist/components/autocomplete.js.map +1 -1
  85. package/dist/components/bulk-action-bar.d.ts +12 -2
  86. package/dist/components/bulk-action-bar.js +65 -21
  87. package/dist/components/bulk-action-bar.js.map +1 -1
  88. package/dist/components/button-look.d.ts +5 -5
  89. package/dist/components/button-look.js.map +1 -1
  90. package/dist/components/button.d.ts +2 -2
  91. package/dist/components/button.js.map +1 -1
  92. package/dist/components/chart-edge.d.ts +16 -0
  93. package/dist/components/chart-edge.js +14 -1
  94. package/dist/components/chart-edge.js.map +1 -1
  95. package/dist/components/chart-figure.d.ts +13 -0
  96. package/dist/components/chart-figure.js +15 -0
  97. package/dist/components/chart-figure.js.map +1 -0
  98. package/dist/components/chart-zoom-core.js +1 -4
  99. package/dist/components/chart-zoom-core.js.map +1 -1
  100. package/dist/components/chart.d.ts +8 -1
  101. package/dist/components/chart.js +19 -10
  102. package/dist/components/chart.js.map +1 -1
  103. package/dist/components/combobox-core.d.ts +111 -2
  104. package/dist/components/combobox-core.js +148 -42
  105. package/dist/components/combobox-core.js.map +1 -1
  106. package/dist/components/combobox.d.ts +1 -1
  107. package/dist/components/combobox.js +141 -130
  108. package/dist/components/combobox.js.map +1 -1
  109. package/dist/components/copy-button.d.ts +6 -5
  110. package/dist/components/copy-button.js +5 -3
  111. package/dist/components/copy-button.js.map +1 -1
  112. package/dist/components/country-select.js +2 -2
  113. package/dist/components/country-select.js.map +1 -1
  114. package/dist/components/data-table-column-settings.d.ts +1 -1
  115. package/dist/components/data-table-column-settings.js +33 -7
  116. package/dist/components/data-table-column-settings.js.map +1 -1
  117. package/dist/components/data-table-filters.d.ts +4 -2
  118. package/dist/components/data-table-filters.js +2 -2
  119. package/dist/components/data-table-filters.js.map +1 -1
  120. package/dist/components/data-table-fit.d.ts +6 -4
  121. package/dist/components/data-table-fit.js +4 -1
  122. package/dist/components/data-table-fit.js.map +1 -1
  123. package/dist/components/data-table-groups.d.ts +1 -1
  124. package/dist/components/data-table-groups.js.map +1 -1
  125. package/dist/components/data-table-head.d.ts +1 -1
  126. package/dist/components/data-table-head.js +15 -12
  127. package/dist/components/data-table-head.js.map +1 -1
  128. package/dist/components/data-table-mobile.d.ts +12 -7
  129. package/dist/components/data-table-mobile.js +43 -30
  130. package/dist/components/data-table-mobile.js.map +1 -1
  131. package/dist/components/data-table-pagination.js +11 -6
  132. package/dist/components/data-table-pagination.js.map +1 -1
  133. package/dist/components/data-table-resize.d.ts +1 -1
  134. package/dist/components/data-table-resize.js.map +1 -1
  135. package/dist/components/data-table-row-actions.d.ts +2 -2
  136. package/dist/components/data-table-row-actions.js.map +1 -1
  137. package/dist/components/data-table-row.d.ts +6 -2
  138. package/dist/components/data-table-row.js +17 -10
  139. package/dist/components/data-table-row.js.map +1 -1
  140. package/dist/components/data-table-selection.d.ts +1 -1
  141. package/dist/components/data-table-selection.js.map +1 -1
  142. package/dist/components/data-table-shared.d.ts +10 -2
  143. package/dist/components/data-table-shared.js +18 -0
  144. package/dist/components/data-table-shared.js.map +1 -1
  145. package/dist/components/data-table-sort.d.ts +21 -2
  146. package/dist/components/data-table-sort.js +24 -0
  147. package/dist/components/data-table-sort.js.map +1 -1
  148. package/dist/components/data-table-virtual.d.ts +1 -1
  149. package/dist/components/data-table-virtual.js +18 -6
  150. package/dist/components/data-table-virtual.js.map +1 -1
  151. package/dist/components/data-table.d.ts +13 -4
  152. package/dist/components/data-table.js +139 -36
  153. package/dist/components/data-table.js.map +1 -1
  154. package/dist/components/date-field.d.ts +25 -3
  155. package/dist/components/date-field.js +33 -8
  156. package/dist/components/date-field.js.map +1 -1
  157. package/dist/components/date-picker.d.ts +16 -3
  158. package/dist/components/date-picker.js +55 -26
  159. package/dist/components/date-picker.js.map +1 -1
  160. package/dist/components/date-range-picker.d.ts +38 -24
  161. package/dist/components/date-range-picker.js +23 -7
  162. package/dist/components/date-range-picker.js.map +1 -1
  163. package/dist/components/error-boundary.js +37 -30
  164. package/dist/components/error-boundary.js.map +1 -1
  165. package/dist/components/{ui-labels.d.ts → field-base-labels.d.ts} +0 -2
  166. package/dist/components/{ui-labels.js → field-base-labels.js} +2 -9
  167. package/dist/components/field-base-labels.js.map +1 -0
  168. package/dist/components/field-base.d.ts +4 -3
  169. package/dist/components/field-base.js +3 -3
  170. package/dist/components/field-base.js.map +1 -1
  171. package/dist/components/form-actions.js +11 -5
  172. package/dist/components/form-actions.js.map +1 -1
  173. package/dist/components/hover-menu.d.ts +3 -0
  174. package/dist/components/hover-menu.js +5 -3
  175. package/dist/components/hover-menu.js.map +1 -1
  176. package/dist/components/icon-button.js +14 -9
  177. package/dist/components/icon-button.js.map +1 -1
  178. package/dist/components/inline-confirm.d.ts +43 -0
  179. package/dist/components/inline-confirm.js +67 -0
  180. package/dist/components/inline-confirm.js.map +1 -0
  181. package/dist/components/inline-entity-combobox.d.ts +4 -3
  182. package/dist/components/inline-entity-combobox.js +148 -140
  183. package/dist/components/inline-entity-combobox.js.map +1 -1
  184. package/dist/components/input.js +1 -1
  185. package/dist/components/input.js.map +1 -1
  186. package/dist/components/jump-calendar.js +28 -112
  187. package/dist/components/jump-calendar.js.map +1 -1
  188. package/dist/components/legal-page.d.ts +3 -1
  189. package/dist/components/legal-page.js +3 -2
  190. package/dist/components/legal-page.js.map +1 -1
  191. package/dist/components/legal.d.ts +4 -2
  192. package/dist/components/legal.js.map +1 -1
  193. package/dist/components/lightbox.d.ts +2 -2
  194. package/dist/components/lightbox.js +17 -6
  195. package/dist/components/lightbox.js.map +1 -1
  196. package/dist/components/list.d.ts +6 -2
  197. package/dist/components/list.js +3 -2
  198. package/dist/components/list.js.map +1 -1
  199. package/dist/components/measured-grid.d.ts +1 -1
  200. package/dist/components/measured-grid.js +2 -1
  201. package/dist/components/measured-grid.js.map +1 -1
  202. package/dist/components/mini-calendar.js +3 -5
  203. package/dist/components/mini-calendar.js.map +1 -1
  204. package/dist/components/modal.js +3 -1
  205. package/dist/components/modal.js.map +1 -1
  206. package/dist/components/month-grid.d.ts +55 -0
  207. package/dist/components/month-grid.js +148 -0
  208. package/dist/components/month-grid.js.map +1 -0
  209. package/dist/components/month-picker.d.ts +36 -4
  210. package/dist/components/month-picker.js +123 -146
  211. package/dist/components/month-picker.js.map +1 -1
  212. package/dist/components/multi-select.d.ts +2 -2
  213. package/dist/components/multi-select.js +11 -6
  214. package/dist/components/multi-select.js.map +1 -1
  215. package/dist/components/nav-pills.d.ts +1 -1
  216. package/dist/components/nav-pills.js.map +1 -1
  217. package/dist/components/picker-sheet.js +2 -1
  218. package/dist/components/picker-sheet.js.map +1 -1
  219. package/dist/components/pie-chart-labels.d.ts +7 -0
  220. package/dist/components/pie-chart-labels.js +1 -0
  221. package/dist/components/pie-chart-labels.js.map +1 -1
  222. package/dist/components/pie-chart.d.ts +5 -3
  223. package/dist/components/pie-chart.js +7 -22
  224. package/dist/components/pie-chart.js.map +1 -1
  225. package/dist/components/popover.d.ts +3 -0
  226. package/dist/components/popover.js +7 -1
  227. package/dist/components/popover.js.map +1 -1
  228. package/dist/components/row-actions.js +2 -1
  229. package/dist/components/row-actions.js.map +1 -1
  230. package/dist/components/series-chart-axes.d.ts +4 -4
  231. package/dist/components/series-chart-axes.js.map +1 -1
  232. package/dist/components/series-chart-labels.d.ts +7 -4
  233. package/dist/components/series-chart-labels.js.map +1 -1
  234. package/dist/components/series-chart.d.ts +92 -23
  235. package/dist/components/series-chart.js +74 -30
  236. package/dist/components/series-chart.js.map +1 -1
  237. package/dist/components/share-card.js +3 -3
  238. package/dist/components/share-card.js.map +1 -1
  239. package/dist/components/tabs-labels.d.ts +2 -0
  240. package/dist/components/tabs-labels.js +11 -0
  241. package/dist/components/tabs-labels.js.map +1 -0
  242. package/dist/components/tabs.d.ts +1 -1
  243. package/dist/components/tabs.js +1 -1
  244. package/dist/components/tabs.js.map +1 -1
  245. package/dist/components/toggle-legend.d.ts +2 -2
  246. package/dist/components/toggle-legend.js +6 -6
  247. package/dist/components/toggle-legend.js.map +1 -1
  248. package/dist/components/translation-review-editor.d.ts +4 -2
  249. package/dist/components/translation-review-editor.js +2 -3
  250. package/dist/components/translation-review-editor.js.map +1 -1
  251. package/dist/components/translation-review.d.ts +2 -1
  252. package/dist/components/translation-review.js +2 -2
  253. package/dist/components/translation-review.js.map +1 -1
  254. package/dist/components/treemap-labels.d.ts +31 -0
  255. package/dist/components/treemap-labels.js +10 -0
  256. package/dist/components/treemap-labels.js.map +1 -0
  257. package/dist/components/treemap-layout.d.ts +62 -0
  258. package/dist/components/treemap-layout.js +126 -0
  259. package/dist/components/treemap-layout.js.map +1 -0
  260. package/dist/components/treemap.d.ts +167 -19
  261. package/dist/components/treemap.js +340 -100
  262. package/dist/components/treemap.js.map +1 -1
  263. package/dist/components/use-table-state.d.ts +5 -5
  264. package/dist/components/use-table-state.js +4 -1
  265. package/dist/components/use-table-state.js.map +1 -1
  266. package/dist/data-table.d.ts +1 -2
  267. package/dist/data-table.js +1 -4
  268. package/dist/data-table.js.map +1 -1
  269. package/dist/demo/demo-countdown.d.ts +0 -5
  270. package/dist/demo/demo-countdown.js +0 -4
  271. package/dist/demo/demo-countdown.js.map +1 -1
  272. package/dist/eslint/index.js +1 -1
  273. package/dist/eslint/role-utilities.d.ts +36 -7
  274. package/dist/eslint/role-utilities.js +42 -9
  275. package/dist/eslint/role-utilities.js.map +1 -1
  276. package/dist/feedback/feedback-dialog.d.ts +25 -16
  277. package/dist/feedback/feedback-dialog.js +6 -2
  278. package/dist/feedback/feedback-dialog.js.map +1 -1
  279. package/dist/feedback/feedback-mutation.d.ts +18 -0
  280. package/dist/feedback/feedback-mutation.js +8 -0
  281. package/dist/feedback/feedback-mutation.js.map +1 -0
  282. package/dist/feedback/feedback-page-link.d.ts +53 -0
  283. package/dist/feedback/feedback-page-link.js +37 -0
  284. package/dist/feedback/feedback-page-link.js.map +1 -0
  285. package/dist/feedback/feedback-public-list.d.ts +8 -4
  286. package/dist/feedback/feedback-public-list.js +2 -2
  287. package/dist/feedback/feedback-public-list.js.map +1 -1
  288. package/dist/feedback/feedback-replies.d.ts +3 -1
  289. package/dist/feedback/feedback-replies.js +5 -4
  290. package/dist/feedback/feedback-replies.js.map +1 -1
  291. package/dist/feedback/feedback-row-detail-labels.js +0 -3
  292. package/dist/feedback/feedback-row-detail-labels.js.map +1 -1
  293. package/dist/feedback/feedback-row-detail.d.ts +33 -44
  294. package/dist/feedback/feedback-row-detail.js +28 -34
  295. package/dist/feedback/feedback-row-detail.js.map +1 -1
  296. package/dist/feedback/feedback-status-undo.d.ts +17 -14
  297. package/dist/feedback/feedback-status-undo.js +10 -15
  298. package/dist/feedback/feedback-status-undo.js.map +1 -1
  299. package/dist/feedback/feedback-submit-dialog.d.ts +1 -1
  300. package/dist/feedback/feedback-submit-dialog.js +1 -1
  301. package/dist/feedback/feedback-submit-dialog.js.map +1 -1
  302. package/dist/feedback/feedback-submit.js +9 -3
  303. package/dist/feedback/feedback-submit.js.map +1 -1
  304. package/dist/feedback/feedback-swipe.d.ts +1 -1
  305. package/dist/feedback/feedback-swipe.js.map +1 -1
  306. package/dist/feedback/feedback-table.d.ts +21 -9
  307. package/dist/feedback/feedback-table.js +45 -25
  308. package/dist/feedback/feedback-table.js.map +1 -1
  309. package/dist/feedback/feedback-thread-labels.d.ts +2 -2
  310. package/dist/feedback/feedback-thread-labels.js.map +1 -1
  311. package/dist/feedback/feedback-thread.d.ts +15 -23
  312. package/dist/feedback/feedback-thread.js +2 -1
  313. package/dist/feedback/feedback-thread.js.map +1 -1
  314. package/dist/feedback.d.ts +6 -6
  315. package/dist/feedback.js +90 -6
  316. package/dist/feedback.js.map +1 -1
  317. package/dist/hooks/use-focus-trap.d.ts +35 -0
  318. package/dist/hooks/use-focus-trap.js +6 -4
  319. package/dist/hooks/use-focus-trap.js.map +1 -1
  320. package/dist/hooks/use-hotkey.d.ts +5 -2
  321. package/dist/hooks/use-hotkey.js +5 -4
  322. package/dist/hooks/use-hotkey.js.map +1 -1
  323. package/dist/hooks/use-long-press.d.ts +7 -4
  324. package/dist/hooks/use-long-press.js +3 -0
  325. package/dist/hooks/use-long-press.js.map +1 -1
  326. package/dist/hooks/use-media-query.js +12 -4
  327. package/dist/hooks/use-media-query.js.map +1 -1
  328. package/dist/hooks/use-mounted.d.ts +22 -0
  329. package/dist/hooks/use-mounted.js +24 -0
  330. package/dist/hooks/use-mounted.js.map +1 -0
  331. package/dist/hooks/use-overlay-history.js +19 -3
  332. package/dist/hooks/use-overlay-history.js.map +1 -1
  333. package/dist/i18n/defaults.js +4 -9
  334. package/dist/i18n/defaults.js.map +1 -1
  335. package/dist/i18n/german.js +7 -18
  336. package/dist/i18n/german.js.map +1 -1
  337. package/dist/i18n/kit-labels.d.ts +9 -11
  338. package/dist/i18n/kit-labels.js.map +1 -1
  339. package/dist/i18n/locales/en.js +15 -64
  340. package/dist/i18n/locales/en.js.map +3 -3
  341. package/dist/i18n/locales/es.js +7 -18
  342. package/dist/i18n/locales/es.js.map +1 -1
  343. package/dist/i18n/locales/fr.js +20 -27
  344. package/dist/i18n/locales/fr.js.map +1 -1
  345. package/dist/i18n/locales/hu.js +9 -18
  346. package/dist/i18n/locales/hu.js.map +1 -1
  347. package/dist/i18n/locales/it.js +7 -19
  348. package/dist/i18n/locales/it.js.map +1 -1
  349. package/dist/i18n/locales/zh.js +8 -18
  350. package/dist/i18n/locales/zh.js.map +1 -1
  351. package/dist/i18n/review.js +12 -2
  352. package/dist/i18n/review.js.map +1 -1
  353. package/dist/i18n/shared-labels.js +0 -1
  354. package/dist/i18n/shared-labels.js.map +1 -1
  355. package/dist/index.d.ts +21 -23
  356. package/dist/index.js +50 -65
  357. package/dist/index.js.map +1 -1
  358. package/dist/landing/routing.d.ts +3 -1
  359. package/dist/landing/routing.js +4 -7
  360. package/dist/landing/routing.js.map +1 -1
  361. package/dist/lib/broadcast.d.ts +17 -0
  362. package/dist/lib/broadcast.js +30 -0
  363. package/dist/lib/broadcast.js.map +1 -0
  364. package/dist/lib/date-format.d.ts +37 -2
  365. package/dist/lib/date-format.js +34 -8
  366. package/dist/lib/date-format.js.map +1 -1
  367. package/dist/lib/day-format.d.ts +46 -0
  368. package/dist/lib/day-format.js +46 -0
  369. package/dist/lib/day-format.js.map +1 -0
  370. package/dist/lib/describe-failure.d.ts +14 -0
  371. package/dist/lib/describe-failure.js +9 -0
  372. package/dist/lib/describe-failure.js.map +1 -0
  373. package/dist/lib/direction.d.ts +16 -0
  374. package/dist/lib/direction.js +9 -1
  375. package/dist/lib/direction.js.map +1 -1
  376. package/dist/lib/focus-left.d.ts +63 -0
  377. package/dist/lib/focus-left.js +67 -0
  378. package/dist/lib/focus-left.js.map +1 -0
  379. package/dist/lib/format.js +5 -14
  380. package/dist/lib/format.js.map +1 -1
  381. package/dist/lib/http-status.d.ts +24 -0
  382. package/dist/lib/http-status.js +14 -0
  383. package/dist/lib/http-status.js.map +1 -0
  384. package/dist/lib/intl-cache.d.ts +14 -0
  385. package/dist/lib/intl-cache.js +15 -0
  386. package/dist/lib/intl-cache.js.map +1 -0
  387. package/dist/lib/iso-day.d.ts +11 -0
  388. package/dist/lib/iso-day.js +8 -0
  389. package/dist/lib/iso-day.js.map +1 -0
  390. package/dist/lib/lazy-with-fallback.d.ts +31 -0
  391. package/dist/lib/lazy-with-fallback.js +40 -0
  392. package/dist/lib/lazy-with-fallback.js.map +1 -0
  393. package/dist/lib/path-pattern.d.ts +34 -0
  394. package/dist/lib/path-pattern.js +24 -0
  395. package/dist/lib/path-pattern.js.map +1 -0
  396. package/dist/lib/reduced-motion.d.ts +2 -1
  397. package/dist/lib/reduced-motion.js.map +1 -1
  398. package/dist/lib/round.d.ts +2 -1
  399. package/dist/lib/round.js +2 -1
  400. package/dist/lib/round.js.map +1 -1
  401. package/dist/lib/safe-storage.d.ts +24 -5
  402. package/dist/lib/safe-storage.js +27 -2
  403. package/dist/lib/safe-storage.js.map +1 -1
  404. package/dist/lib/session-refresh.js +1 -1
  405. package/dist/lib/session-refresh.js.map +1 -1
  406. package/dist/lib/share-format.d.ts +13 -0
  407. package/dist/lib/share-format.js +10 -0
  408. package/dist/lib/share-format.js.map +1 -0
  409. package/dist/lib/text-case.d.ts +17 -0
  410. package/dist/lib/text-case.js +8 -0
  411. package/dist/lib/text-case.js.map +1 -0
  412. package/dist/lib/text-match.d.ts +29 -0
  413. package/dist/lib/text-match.js +20 -2
  414. package/dist/lib/text-match.js.map +1 -1
  415. package/dist/lib/thenable.d.ts +13 -0
  416. package/dist/lib/thenable.js +8 -0
  417. package/dist/lib/thenable.js.map +1 -0
  418. package/dist/lib/translation-review.d.ts +7 -2
  419. package/dist/lib/translation-review.js +4 -4
  420. package/dist/lib/translation-review.js.map +1 -1
  421. package/dist/notifications/device-zone-report.js +1 -1
  422. package/dist/notifications/device-zone-report.js.map +1 -1
  423. package/dist/notifications/notification-bell.d.ts +2 -1
  424. package/dist/notifications/notification-bell.js +3 -3
  425. package/dist/notifications/notification-bell.js.map +1 -1
  426. package/dist/notifications/notification-inbox.d.ts +10 -6
  427. package/dist/notifications/notification-inbox.js +26 -48
  428. package/dist/notifications/notification-inbox.js.map +1 -1
  429. package/dist/notifications/notification-parts.d.ts +3 -2
  430. package/dist/notifications/notification-parts.js +1 -3
  431. package/dist/notifications/notification-parts.js.map +1 -1
  432. package/dist/notifications/notification-types-setting.js +1 -1
  433. package/dist/notifications/notification-types-setting.js.map +1 -1
  434. package/dist/notifications/notifications-labels.d.ts +6 -0
  435. package/dist/notifications/notifications-labels.js +1 -0
  436. package/dist/notifications/notifications-labels.js.map +1 -1
  437. package/dist/notifications/push-device-setting.js +1 -1
  438. package/dist/notifications/push-device-setting.js.map +1 -1
  439. package/dist/notifications/push-devices-setting.d.ts +2 -1
  440. package/dist/notifications/push-devices-setting.js +27 -49
  441. package/dist/notifications/push-devices-setting.js.map +1 -1
  442. package/dist/notifications/quiet-hours-setting.d.ts +10 -3
  443. package/dist/notifications/quiet-hours-setting.js +65 -26
  444. package/dist/notifications/quiet-hours-setting.js.map +1 -1
  445. package/dist/notifications/unsubscribe-panel.js +4 -7
  446. package/dist/notifications/unsubscribe-panel.js.map +1 -1
  447. package/dist/notifications/use-push-device.d.ts +39 -1
  448. package/dist/notifications/use-push-device.js +28 -7
  449. package/dist/notifications/use-push-device.js.map +1 -1
  450. package/dist/offline/query-persistence.d.ts +3 -1
  451. package/dist/offline/query-persistence.js +19 -2
  452. package/dist/offline/query-persistence.js.map +1 -1
  453. package/dist/rhf/fields.d.ts +11 -4
  454. package/dist/rhf/fields.js.map +1 -1
  455. package/dist/search/command-palette.js +1 -1
  456. package/dist/search/command-palette.js.map +1 -1
  457. package/dist/search/global-search.js +19 -9
  458. package/dist/search/global-search.js.map +1 -1
  459. package/dist/search/search-index.d.ts +0 -6
  460. package/dist/search/search-index.js +0 -4
  461. package/dist/search/search-index.js.map +1 -1
  462. package/dist/search/use-command-key.d.ts +12 -1
  463. package/dist/search/use-command-key.js +3 -12
  464. package/dist/search/use-command-key.js.map +1 -1
  465. package/dist/search.d.ts +1 -1
  466. package/dist/search.js +6 -1
  467. package/dist/search.js.map +1 -1
  468. package/dist/settings/use-settings-focus.js +1 -0
  469. package/dist/settings/use-settings-focus.js.map +1 -1
  470. package/dist/shell/account-menu-language.d.ts +5 -3
  471. package/dist/shell/account-menu-language.js +2 -2
  472. package/dist/shell/account-menu-language.js.map +1 -1
  473. package/dist/shell/account-menu-text-size.d.ts +9 -6
  474. package/dist/shell/account-menu-text-size.js +2 -2
  475. package/dist/shell/account-menu-text-size.js.map +1 -1
  476. package/dist/shell/app-shell.d.ts +0 -10
  477. package/dist/shell/app-shell.js +1 -7
  478. package/dist/shell/app-shell.js.map +1 -1
  479. package/dist/shell/company-switcher.d.ts +1 -1
  480. package/dist/shell/company-switcher.js.map +1 -1
  481. package/dist/shell/dev-menu.d.ts +1 -1
  482. package/dist/shell/dev-menu.js +5 -18
  483. package/dist/shell/dev-menu.js.map +1 -1
  484. package/dist/shell/top-bar-actions.d.ts +1 -1
  485. package/dist/shell/top-bar-actions.js.map +1 -1
  486. package/dist/shell/top-bar-parts-labels.d.ts +1 -3
  487. package/dist/shell/top-bar-parts-labels.js +0 -1
  488. package/dist/shell/top-bar-parts-labels.js.map +1 -1
  489. package/dist/shell.d.ts +7 -10
  490. package/dist/shell.js +14 -23
  491. package/dist/shell.js.map +1 -1
  492. package/dist/sync/engine.d.ts +30 -4
  493. package/dist/sync/engine.js +172 -52
  494. package/dist/sync/engine.js.map +1 -1
  495. package/dist/sync.d.ts +2 -2
  496. package/dist/sync.js +2 -10
  497. package/dist/sync.js.map +1 -1
  498. package/dist/testing/css-variables.js +3 -8
  499. package/dist/testing/css-variables.js.map +1 -1
  500. package/dist/theme/chart-palette.d.ts +0 -5
  501. package/dist/theme/chart-palette.js +0 -2
  502. package/dist/theme/chart-palette.js.map +1 -1
  503. package/dist/theme/contrast.d.ts +2 -1
  504. package/dist/theme/contrast.js +3 -8
  505. package/dist/theme/contrast.js.map +1 -1
  506. package/dist/theme/palette-store.js +6 -6
  507. package/dist/theme/palette-store.js.map +1 -1
  508. package/dist/theme/text-size.js +3 -8
  509. package/dist/theme/text-size.js.map +1 -1
  510. package/dist/theme/theme-store.js +3 -6
  511. package/dist/theme/theme-store.js.map +1 -1
  512. package/dist/tour/tour-catalogue.d.ts +7 -4
  513. package/dist/tour/tour-catalogue.js +12 -10
  514. package/dist/tour/tour-catalogue.js.map +1 -1
  515. package/dist/tour/tour-dock.d.ts +0 -2
  516. package/dist/tour/tour-dock.js +0 -2
  517. package/dist/tour/tour-dock.js.map +1 -1
  518. package/dist/tour/tour-gates.d.ts +4 -0
  519. package/dist/tour/tour-gates.js +14 -0
  520. package/dist/tour/tour-gates.js.map +1 -1
  521. package/dist/tour/tour-overlay.d.ts +27 -0
  522. package/dist/tour/tour-overlay.js +741 -0
  523. package/dist/tour/tour-overlay.js.map +1 -0
  524. package/dist/tour/tour.d.ts +13 -3
  525. package/dist/tour/tour.js +59 -781
  526. package/dist/tour/tour.js.map +1 -1
  527. package/dist/tour/use-tour-progress.js +1 -1
  528. package/dist/tour/use-tour-progress.js.map +1 -1
  529. package/dist/wizard/stepper-nav.d.ts +5 -1
  530. package/dist/wizard/stepper-nav.js.map +1 -1
  531. package/dist/wizard/types.d.ts +6 -2
  532. package/dist/wizard/types.js.map +1 -1
  533. package/dist/wizard/use-wizard.d.ts +3 -1
  534. package/dist/wizard/use-wizard.js.map +1 -1
  535. package/dist/wizard/validation.d.ts +3 -1
  536. package/dist/wizard/validation.js.map +1 -1
  537. package/package.json +10 -6
  538. package/src/account/account-parts.ts +5 -25
  539. package/src/account/data-export-setting-labels.ts +1 -1
  540. package/src/account/delete-account-setting-labels.ts +1 -1
  541. package/src/account/email-change-setting-labels.ts +1 -1
  542. package/src/account/passkeys-setting.tsx +60 -38
  543. package/src/account/sessions-setting.tsx +18 -12
  544. package/src/admin/admin-action-confirm.tsx +1 -5
  545. package/src/admin/admin-parts.ts +11 -25
  546. package/src/admin/mail-test-card-labels.ts +1 -1
  547. package/src/admin/plan-change-labels.ts +5 -23
  548. package/src/admin/plan-change.tsx +7 -44
  549. package/src/admin/reviewer-scope-editor.tsx +3 -3
  550. package/src/auth/auth-errors-labels.ts +23 -0
  551. package/src/auth/auth-errors.ts +11 -38
  552. package/src/auth/complete-name-dialog.tsx +4 -1
  553. package/src/auth/forgot-password-form-labels.ts +1 -1
  554. package/src/auth/forgot-password-form.tsx +11 -6
  555. package/src/auth/register-form-labels.ts +1 -1
  556. package/src/auth/reset-password-form-labels.ts +1 -1
  557. package/src/auth/reset-password-form.tsx +8 -4
  558. package/src/auth/sign-in-form-labels.ts +1 -1
  559. package/src/auth/status-parts.tsx +0 -6
  560. package/src/auth/verify-email-labels.ts +1 -1
  561. package/src/auth/verify-email.tsx +11 -6
  562. package/src/billing/cancel-subscription.tsx +15 -12
  563. package/src/billing/plan-card.tsx +18 -10
  564. package/src/billing/plan-change-labels.ts +66 -0
  565. package/src/billing/plan-switch.tsx +15 -12
  566. package/src/chart.ts +73 -6
  567. package/src/components/account-chips.tsx +20 -23
  568. package/src/components/alert-banner.tsx +82 -5
  569. package/src/components/amount-input.tsx +18 -7
  570. package/src/components/autocomplete.tsx +64 -70
  571. package/src/components/bulk-action-bar.tsx +70 -2
  572. package/src/components/button-look.ts +5 -5
  573. package/src/components/button.tsx +2 -2
  574. package/src/components/chart-edge.ts +34 -1
  575. package/src/components/chart-figure.tsx +35 -0
  576. package/src/components/chart-zoom-core.tsx +6 -5
  577. package/src/components/chart.tsx +26 -5
  578. package/src/components/combobox-core.tsx +293 -62
  579. package/src/components/combobox.tsx +183 -153
  580. package/src/components/copy-button.tsx +34 -23
  581. package/src/components/country-select.tsx +16 -11
  582. package/src/components/data-table-column-settings.tsx +52 -10
  583. package/src/components/data-table-filters.ts +6 -4
  584. package/src/components/data-table-fit.ts +19 -5
  585. package/src/components/data-table-groups.tsx +1 -1
  586. package/src/components/data-table-head.tsx +32 -22
  587. package/src/components/data-table-mobile.tsx +61 -26
  588. package/src/components/data-table-pagination.tsx +21 -6
  589. package/src/components/data-table-resize.ts +1 -1
  590. package/src/components/data-table-row-actions.tsx +3 -3
  591. package/src/components/data-table-row.tsx +25 -14
  592. package/src/components/data-table-selection.ts +1 -1
  593. package/src/components/data-table-shared.tsx +33 -1
  594. package/src/components/data-table-sort.ts +57 -2
  595. package/src/components/data-table-virtual.ts +32 -8
  596. package/src/components/data-table.tsx +239 -68
  597. package/src/components/date-field.tsx +66 -9
  598. package/src/components/date-picker.tsx +125 -49
  599. package/src/components/date-range-picker.tsx +71 -36
  600. package/src/components/error-boundary.tsx +53 -39
  601. package/src/components/field-base-labels.ts +20 -0
  602. package/src/components/field-base.tsx +13 -7
  603. package/src/components/form-actions.tsx +20 -5
  604. package/src/components/hover-menu.tsx +18 -8
  605. package/src/components/icon-button.tsx +14 -9
  606. package/src/components/inline-confirm.tsx +116 -0
  607. package/src/components/inline-entity-combobox.tsx +197 -153
  608. package/src/components/input.tsx +1 -1
  609. package/src/components/jump-calendar.tsx +30 -146
  610. package/src/components/legal-page.tsx +10 -7
  611. package/src/components/legal.tsx +4 -2
  612. package/src/components/lightbox.tsx +21 -8
  613. package/src/components/list.tsx +10 -4
  614. package/src/components/measured-grid.tsx +8 -4
  615. package/src/components/mini-calendar.tsx +6 -5
  616. package/src/components/modal.tsx +6 -1
  617. package/src/components/month-grid.tsx +229 -0
  618. package/src/components/month-picker.tsx +192 -200
  619. package/src/components/multi-select.tsx +21 -9
  620. package/src/components/nav-pills.tsx +1 -1
  621. package/src/components/picker-sheet.tsx +12 -9
  622. package/src/components/pie-chart-labels.ts +8 -0
  623. package/src/components/pie-chart.tsx +22 -28
  624. package/src/components/popover.tsx +18 -1
  625. package/src/components/row-actions.tsx +2 -1
  626. package/src/components/series-chart-axes.ts +9 -4
  627. package/src/components/series-chart-labels.ts +7 -4
  628. package/src/components/series-chart.tsx +253 -87
  629. package/src/components/share-card.tsx +6 -6
  630. package/src/components/tabs-labels.ts +13 -0
  631. package/src/components/tabs.tsx +2 -2
  632. package/src/components/toggle-legend.tsx +16 -11
  633. package/src/components/translation-review-editor.tsx +6 -5
  634. package/src/components/translation-review.tsx +4 -3
  635. package/src/components/treemap-labels.ts +47 -0
  636. package/src/components/treemap-layout.ts +210 -0
  637. package/src/components/treemap.tsx +780 -138
  638. package/src/components/use-table-state.ts +12 -9
  639. package/src/data-table.ts +1 -4
  640. package/src/demo/demo-countdown.ts +3 -8
  641. package/src/eslint/role-utilities.ts +97 -19
  642. package/src/feedback/feedback-dialog.tsx +30 -19
  643. package/src/feedback/feedback-mutation.ts +20 -0
  644. package/src/feedback/feedback-page-link.ts +90 -0
  645. package/src/feedback/feedback-public-list.tsx +13 -7
  646. package/src/feedback/feedback-replies.tsx +13 -6
  647. package/src/feedback/feedback-row-detail-labels.ts +0 -3
  648. package/src/feedback/feedback-row-detail.tsx +70 -84
  649. package/src/feedback/feedback-status-undo.ts +28 -28
  650. package/src/feedback/feedback-submit-dialog.tsx +2 -2
  651. package/src/feedback/feedback-submit.tsx +21 -7
  652. package/src/feedback/feedback-swipe.tsx +1 -1
  653. package/src/feedback/feedback-table.tsx +86 -38
  654. package/src/feedback/feedback-thread-labels.ts +2 -2
  655. package/src/feedback/feedback-thread.tsx +18 -26
  656. package/src/feedback.ts +89 -6
  657. package/src/hooks/use-focus-trap.ts +8 -24
  658. package/src/hooks/use-hotkey.ts +24 -7
  659. package/src/hooks/use-long-press.ts +16 -2
  660. package/src/hooks/use-media-query.ts +20 -4
  661. package/src/hooks/use-mounted.ts +39 -0
  662. package/src/hooks/use-overlay-history.ts +32 -3
  663. package/src/i18n/defaults.ts +4 -9
  664. package/src/i18n/german.ts +8 -19
  665. package/src/i18n/kit-labels.tsx +9 -11
  666. package/src/i18n/locales/es.ts +8 -19
  667. package/src/i18n/locales/fr.ts +21 -30
  668. package/src/i18n/locales/hu.ts +10 -19
  669. package/src/i18n/locales/it.ts +8 -20
  670. package/src/i18n/locales/zh.ts +9 -19
  671. package/src/i18n/review.ts +12 -2
  672. package/src/i18n/shared-labels.ts +0 -1
  673. package/src/index.ts +57 -49
  674. package/src/landing/routing.tsx +14 -10
  675. package/src/lib/broadcast.ts +52 -0
  676. package/src/lib/date-format.ts +89 -13
  677. package/src/lib/day-format.ts +96 -0
  678. package/src/lib/describe-failure.ts +18 -0
  679. package/src/lib/direction.ts +39 -0
  680. package/src/lib/focus-left.ts +156 -0
  681. package/src/lib/format.ts +5 -17
  682. package/src/lib/http-status.ts +38 -0
  683. package/src/lib/intl-cache.ts +25 -0
  684. package/src/lib/iso-day.ts +13 -0
  685. package/src/lib/lazy-with-fallback.ts +82 -0
  686. package/src/lib/path-pattern.ts +55 -0
  687. package/src/lib/reduced-motion.ts +2 -1
  688. package/src/lib/round.ts +7 -2
  689. package/src/lib/safe-storage.ts +48 -6
  690. package/src/lib/session-refresh.ts +1 -1
  691. package/src/lib/share-format.ts +18 -0
  692. package/src/lib/text-case.ts +19 -0
  693. package/src/lib/text-match.ts +57 -2
  694. package/src/lib/thenable.ts +19 -0
  695. package/src/lib/translation-review.ts +13 -5
  696. package/src/notifications/device-zone-report.ts +1 -1
  697. package/src/notifications/notification-bell.tsx +7 -4
  698. package/src/notifications/notification-inbox.tsx +40 -58
  699. package/src/notifications/notification-parts.tsx +3 -4
  700. package/src/notifications/notification-types-setting.tsx +1 -1
  701. package/src/notifications/notifications-labels.ts +7 -0
  702. package/src/notifications/push-device-setting.tsx +1 -1
  703. package/src/notifications/push-devices-setting.tsx +30 -55
  704. package/src/notifications/quiet-hours-setting.tsx +93 -31
  705. package/src/notifications/unsubscribe-panel.tsx +4 -7
  706. package/src/notifications/use-push-device.ts +76 -10
  707. package/src/offline/query-persistence.ts +35 -3
  708. package/src/rhf/fields.tsx +11 -4
  709. package/src/search/command-palette.tsx +2 -4
  710. package/src/search/global-search.tsx +36 -15
  711. package/src/search/search-index.ts +0 -15
  712. package/src/search/use-command-key.ts +13 -12
  713. package/src/search.ts +10 -1
  714. package/src/settings/use-settings-focus.ts +3 -0
  715. package/src/shell/account-menu-language.tsx +6 -4
  716. package/src/shell/account-menu-text-size.tsx +10 -7
  717. package/src/shell/app-shell.tsx +0 -13
  718. package/src/shell/company-switcher.tsx +1 -1
  719. package/src/shell/dev-menu.tsx +8 -22
  720. package/src/shell/top-bar-actions.tsx +1 -1
  721. package/src/shell/top-bar-parts-labels.ts +1 -4
  722. package/src/shell.ts +21 -17
  723. package/src/sync/engine.ts +339 -61
  724. package/src/sync.ts +2 -8
  725. package/src/testing/css-variables.ts +8 -9
  726. package/src/theme/chart-palette.ts +0 -2
  727. package/src/theme/contrast.ts +8 -9
  728. package/src/theme/palette-store.ts +9 -9
  729. package/src/theme/text-size.ts +6 -8
  730. package/src/theme/theme-store.ts +7 -9
  731. package/src/tour/tour-catalogue.tsx +26 -15
  732. package/src/tour/tour-dock.ts +0 -2
  733. package/src/tour/tour-gates.ts +23 -0
  734. package/src/tour/tour-overlay.tsx +1170 -0
  735. package/src/tour/tour.tsx +108 -1202
  736. package/src/tour/use-tour-progress.ts +1 -1
  737. package/src/wizard/stepper-nav.tsx +3 -1
  738. package/src/wizard/types.ts +11 -2
  739. package/src/wizard/use-wizard.ts +2 -1
  740. package/src/wizard/validation.ts +8 -2
  741. package/tokens.css +6 -5
  742. package/dist/components/field-sync-labels.d.ts +0 -2
  743. package/dist/components/field-sync-labels.js +0 -12
  744. package/dist/components/field-sync-labels.js.map +0 -1
  745. package/dist/components/field-sync.d.ts +0 -164
  746. package/dist/components/field-sync.js +0 -269
  747. package/dist/components/field-sync.js.map +0 -1
  748. package/dist/components/section-nav.d.ts +0 -37
  749. package/dist/components/section-nav.js +0 -33
  750. package/dist/components/section-nav.js.map +0 -1
  751. package/dist/components/sign-chip-labels.d.ts +0 -2
  752. package/dist/components/sign-chip-labels.js +0 -11
  753. package/dist/components/sign-chip-labels.js.map +0 -1
  754. package/dist/components/sign-chip.d.ts +0 -68
  755. package/dist/components/sign-chip.js +0 -47
  756. package/dist/components/sign-chip.js.map +0 -1
  757. package/dist/components/ui-labels.js.map +0 -1
  758. package/dist/lib/isin.d.ts +0 -28
  759. package/dist/lib/isin.js +0 -36
  760. package/dist/lib/isin.js.map +0 -1
  761. package/dist/shell/dev-switchers.d.ts +0 -6
  762. package/dist/shell/dev-switchers.js +0 -12
  763. package/dist/shell/dev-switchers.js.map +0 -1
  764. package/dist/shell/role-switcher.d.ts +0 -21
  765. package/dist/shell/role-switcher.js +0 -31
  766. package/dist/shell/role-switcher.js.map +0 -1
  767. package/src/components/field-sync-labels.ts +0 -13
  768. package/src/components/field-sync.tsx +0 -482
  769. package/src/components/section-nav.tsx +0 -65
  770. package/src/components/sign-chip-labels.ts +0 -12
  771. package/src/components/sign-chip.tsx +0 -124
  772. package/src/components/ui-labels.ts +0 -24
  773. package/src/lib/isin.ts +0 -62
  774. package/src/shell/dev-switchers.tsx +0 -18
  775. package/src/shell/role-switcher.tsx +0 -46
@@ -1,5 +1,5 @@
1
- import { useId, useState } from "react";
2
- import type { ComponentPropsWithRef, ReactNode } from "react";
1
+ import { useEffect, useId, useRef, useState } from "react";
2
+ import type { ComponentPropsWithRef, KeyboardEvent, ReactNode } from "react";
3
3
 
4
4
  import type { LabelOverride } from "../i18n/kit-labels";
5
5
  import { cn } from "../lib/cn";
@@ -8,7 +8,7 @@ import { TimeInput } from "../components/time-input";
8
8
  import { COMMIT_EXCEPT_BILLING } from "../components/write-lock";
9
9
  import { CARD_DESCRIPTION_CLASS } from "../settings/settings-card-classes";
10
10
  import { SettingsCardTitle } from "../settings/settings-heading";
11
- import { settle, useMounted } from "../account/account-parts";
11
+ import { settle, useMounted } from "../hooks/use-mounted";
12
12
  import { describeFailure, FailureLine, useNotificationsLabels, useWriteOverlay } from "./notification-parts";
13
13
  import type { NotificationRegionLabels, NotificationsLabels } from "./notifications-labels";
14
14
  import type { NotificationSettingsPatch } from "./notification-types";
@@ -49,7 +49,9 @@ export interface QuietHoursSettingProps extends Omit<ComponentPropsWithRef<"div"
49
49
  held?: boolean;
50
50
  /**
51
51
  * Save one change — `PATCH /notifications/settings`: a time, or `null` when cleared.
52
- * Never a zone. The field shows the new value while the call runs and goes back if it
52
+ * Never a zone. Called when a time is ENTERED — the field left, or Enter, or the card
53
+ * unmounted with a time still typed — never per keystroke, and one call per field at a
54
+ * time (0.52). The field shows the new value while the call runs and goes back if it
53
55
  * rejects.
54
56
  */
55
57
  onChange: (patch: NotificationSettingsPatch) => Promise<unknown> | void;
@@ -77,8 +79,13 @@ type QuietField = "quiet_hours_start" | "quiet_hours_end";
77
79
  * only** — the zone of the device last used, else `Europe/Zurich`. The card names it
78
80
  * read-only and never sends one; there is no picker and no "Use it".
79
81
  *
80
- * - **Two times**, each saved as it is entered; a cleared time sends `null`. While only
81
- * one is set the card says so: the window fails open until both are.
82
+ * - **Two times**, each saved when it is entered — on leaving the field or on Enter, or
83
+ * when the card goes while it is still typed — and a cleared time sends `null`. While
84
+ * only one is set the card says so: the window fails open until both are.
85
+ * - **One request per field at a time** (0.52): a time entered while that field's save
86
+ * runs waits for it, and only the latest waiting one is sent. Each input event used to
87
+ * PATCH — typing "30" over 22:00's minutes sent 22:03 and 22:30, and whichever answer
88
+ * landed last stayed on the server (refactor audit 2026-10-30, FLW-4).
82
89
  * - **The zone** shows while a time is set: `zone`, else `fallbackZone`.
83
90
  *
84
91
  * Under a write lock the fields are `commit` controls exempt from billing.
@@ -103,32 +110,83 @@ export function QuietHoursSetting({
103
110
  const mounted = useMounted();
104
111
  const id = useId();
105
112
 
106
- const shown = (field: QuietField, server: string | null) =>
107
- overlay.has(field) ? (overlay.get(field) ?? null) : server;
108
- const startShown = shown("quiet_hours_start", start);
109
- const endShown = shown("quiet_hours_end", end);
113
+ // What is typed and not yet entered, per field: the field shows it, nothing is sent.
114
+ const [drafts, setDrafts] = useState<Partial<Record<QuietField, string>>>({});
115
+ // Per field: whether a save runs, and the latest time entered while it did (own key =
116
+ // one is waiting). Refs: a save's answer reads them after renders it did not see.
117
+ const running = useRef<Partial<Record<QuietField, boolean>>>({});
118
+ const waiting = useRef<Partial<Record<QuietField, string | null>>>({});
119
+
120
+ const server = (field: QuietField) => (field === "quiet_hours_start" ? start : end);
121
+ const saved = (field: QuietField) => (overlay.has(field) ? (overlay.get(field) ?? null) : server(field));
122
+ const shown = (field: QuietField) => drafts[field] ?? saved(field) ?? "";
123
+ const startShown = shown("quiet_hours_start");
124
+ const endShown = shown("quiet_hours_end");
110
125
  const accountZone = zone ?? fallbackZone;
111
126
 
112
- const save = (patch: NotificationSettingsPatch) => {
113
- const fields = Object.keys(patch) as QuietField[];
114
- setFailure(null);
115
- for (const field of fields) overlay.set(field, patch[field] ?? null);
116
- settle(() => onChange(patch)).then(
117
- () => {
118
- if (mounted.current) for (const field of fields) overlay.settle(field, true);
119
- },
120
- (error: unknown) => {
121
- if (!mounted.current) return;
122
- for (const field of fields) overlay.settle(field, false);
123
- setFailure(describeFailure(describeError?.(error), labels.failed));
124
- },
127
+ const send = (field: QuietField, value: string | null) => {
128
+ running.current[field] = true;
129
+ if (mounted.current) setFailure(null);
130
+ settle(() => onChange({ [field]: value })).then(
131
+ () => done(field, true),
132
+ (error: unknown) => done(field, false, error),
125
133
  );
126
134
  };
127
135
 
128
- const saveTime = (field: "quiet_hours_start" | "quiet_hours_end", value: string) => {
129
- const next = value || null;
130
- if (next === shown(field, field === "quiet_hours_start" ? start : end)) return;
131
- save({ [field]: next });
136
+ /** A save answered: the waiting time goes next — even after an unmount, it is what the
137
+ * person entered last — and supersedes this answer, a failure included. */
138
+ const done = (field: QuietField, ok: boolean, error?: unknown) => {
139
+ running.current[field] = false;
140
+ if (Object.prototype.hasOwnProperty.call(waiting.current, field)) {
141
+ const next = waiting.current[field] ?? null;
142
+ delete waiting.current[field];
143
+ send(field, next);
144
+ return;
145
+ }
146
+ if (!mounted.current) return;
147
+ overlay.settle(field, ok);
148
+ if (!ok) setFailure(describeFailure(describeError?.(error), labels.failed));
149
+ };
150
+
151
+ /** `draft` entered: the time it names, or `undefined` when that is what the field
152
+ * already holds and nothing is to be sent. Queued behind the field's running save. */
153
+ const save = (field: QuietField, draft: string): string | null | undefined => {
154
+ const next = draft || null;
155
+ if (next === saved(field)) return undefined;
156
+ if (running.current[field]) waiting.current[field] = next;
157
+ else send(field, next);
158
+ return next;
159
+ };
160
+
161
+ /** The typed time, entered: saved unless it is what the field already holds. */
162
+ const enter = (field: QuietField) => {
163
+ const draft = drafts[field];
164
+ if (draft === undefined) return;
165
+ setDrafts((current) => {
166
+ const rest = { ...current };
167
+ delete rest[field];
168
+ return rest;
169
+ });
170
+ const next = save(field, draft);
171
+ if (next !== undefined) overlay.set(field, next);
172
+ };
173
+
174
+ // Leaving the card is leaving the field. A focused input removed with its card fires no
175
+ // blur — Escape on the settings sheet, a shortcut that navigates — and the time it showed
176
+ // as typed was dropped (0.52 code review, quiet-hours-setting.tsx; save-on-blur is FLW-4's
177
+ // decision). So whatever is still typed is entered as the card goes. A ref to this
178
+ // render's drafts and saved values, assigned in an effect, never during render.
179
+ const leave = useRef<() => void>(() => {});
180
+ useEffect(() => {
181
+ leave.current = () => {
182
+ for (const [field, draft] of Object.entries(drafts) as [QuietField, string][]) save(field, draft);
183
+ };
184
+ });
185
+ useEffect(() => () => leave.current(), []);
186
+
187
+ const type = (field: QuietField, value: string) => setDrafts((current) => ({ ...current, [field]: value }));
188
+ const enterOnEnter = (field: QuietField, event: KeyboardEvent<HTMLInputElement>) => {
189
+ if (event.key === "Enter") enter(field);
132
190
  };
133
191
 
134
192
  const anySet = Boolean(startShown || endShown);
@@ -144,19 +202,23 @@ export function QuietHoursSetting({
144
202
  <TimeInput
145
203
  id={`${id}-start`}
146
204
  label={labels.quietFrom}
147
- value={startShown ?? ""}
205
+ value={startShown}
206
+ onValueChange={(value) => type("quiet_hours_start", value)}
207
+ onBlur={() => enter("quiet_hours_start")}
208
+ onKeyDown={(event) => enterOnEnter("quiet_hours_start", event)}
148
209
  disabled={disabled}
149
210
  commit={COMMIT_EXCEPT_BILLING}
150
- onValueChange={(value) => saveTime("quiet_hours_start", value)}
151
211
  className="w-36"
152
212
  />
153
213
  <TimeInput
154
214
  id={`${id}-end`}
155
215
  label={labels.quietUntil}
156
- value={endShown ?? ""}
216
+ value={endShown}
217
+ onValueChange={(value) => type("quiet_hours_end", value)}
218
+ onBlur={() => enter("quiet_hours_end")}
219
+ onKeyDown={(event) => enterOnEnter("quiet_hours_end", event)}
157
220
  disabled={disabled}
158
221
  commit={COMMIT_EXCEPT_BILLING}
159
- onValueChange={(value) => saveTime("quiet_hours_end", value)}
160
222
  className="w-36"
161
223
  />
162
224
  </div>
@@ -4,12 +4,13 @@ import { CheckCircle2, Link2Off } from "lucide-react";
4
4
 
5
5
  import type { LabelOverride } from "../i18n/kit-labels";
6
6
  import { cn } from "../lib/cn";
7
- import { errorBodyWithCode, httpStatusOf } from "../auth/auth-errors";
7
+ import { errorBodyWithCode } from "../auth/auth-errors";
8
+ import { httpStatusOf } from "../lib/http-status";
8
9
  import { INTRO_CLASS, OUTCOME_CLASS, STATUS_HEADING_CLASS, useFocusWhen } from "../auth/status-parts";
9
10
  import type { AuthHeadingLevel } from "../auth/status-parts";
10
11
  import { Button } from "../components/button";
11
12
  import { EmptyState } from "../components/empty-state";
12
- import { useMounted } from "../account/account-parts";
13
+ import { useMounted } from "../hooks/use-mounted";
13
14
  import { FailureLine, useNotificationsLabels } from "./notification-parts";
14
15
  import type { NotificationsLabels } from "./notifications-labels";
15
16
 
@@ -154,11 +155,7 @@ export function UnsubscribePanel({
154
155
  tone="success"
155
156
  icon={<CheckCircle2 />}
156
157
  title={shown === "stopped-these" ? labels.stoppedThese : labels.stoppedAll(appName)}
157
- hint={
158
- <>
159
- {labels.accountMail} {labels.turnBackOn}
160
- </>
161
- }
158
+ hint={labels.stoppedHint(labels.accountMail, labels.turnBackOn)}
162
159
  action={settingsLink}
163
160
  className={OUTCOME_CLASS}
164
161
  />
@@ -1,7 +1,7 @@
1
1
  import { useCallback, useEffect, useRef, useState } from "react";
2
2
 
3
3
  import { useMediaQuery } from "../hooks/use-media-query";
4
- import { httpStatusOf } from "../auth/auth-errors";
4
+ import { httpStatusOf } from "../lib/http-status";
5
5
  import { PUSH_SUBSCRIPTION_CHANGED, keyBytes, pushSubscriptionInput as inputOf } from "./push-worker";
6
6
  import type { PushSubscriptionInput } from "./push-worker";
7
7
 
@@ -85,7 +85,8 @@ export interface PushDevice {
85
85
  * the permission was not granted or push is off on the server; rejects with a
86
86
  * {@link PushDeviceError} or the app's request's error. */
87
87
  enable: () => Promise<boolean>;
88
- /** Tell the server first, then the browser, so a dropped device stops at once. */
88
+ /** Tell the server first, then the browser, so a dropped device stops at once. At a
89
+ * sign-out, with no card mounted: {@link releaseThisDevicePush}. */
89
90
  disable: () => Promise<void>;
90
91
  sendTest: () => Promise<{ sent: number }>;
91
92
  }
@@ -152,6 +153,78 @@ async function activeRegistration(): Promise<ServiceWorkerRegistration> {
152
153
 
153
154
  const defaultEndpointTaken = (error: unknown) => httpStatusOf(error) === 409;
154
155
 
156
+ /**
157
+ * Drop this browser's subscription when the person turns push off: the server first, so a
158
+ * dropped device stops receiving even if the browser's unsubscribe fails, then the
159
+ * browser, cut at 15 s. Whether there was one. A refused server keeps the browser's
160
+ * subscription: the card says the turn-off failed, still reads on, and the person tries
161
+ * again. {@link usePushDevice}'s `disable`; a sign-out has its own
162
+ * ({@link releaseThisDevicePush}).
163
+ */
164
+ async function releaseSubscription(unregister: (endpoint: string) => Promise<unknown>): Promise<boolean> {
165
+ const registration = await activeRegistration();
166
+ const subscription = await registration.pushManager.getSubscription();
167
+ if (!subscription) return false;
168
+ await unregister(subscription.endpoint);
169
+ await within(subscription.unsubscribe(), PUSH_TIMEOUT_MS, "push_timeout");
170
+ return true;
171
+ }
172
+
173
+ /**
174
+ * Release this browser's push at an EXPLICIT sign-out (0.52, docs/notifications-
175
+ * harmonization.md §10.6; refactor audit 2026-10-30, FLW-2) — without the settings card
176
+ * mounted, which `usePushDevice().disable` needs.
177
+ *
178
+ * A sign-out left the subscription registered to the account, so the device went on
179
+ * showing that account's notifications, title and body (keksdose: invoices, bookings,
180
+ * shares), to whoever used it next — while the same sign-out wiped the mirror, the outbox
181
+ * and the query cache "for the next person on this device".
182
+ *
183
+ * Call it with the app's `unregister` (`POST /notifications/devices/unregister
184
+ * {endpoint}`) BEFORE the session is cleared, while the token is still valid:
185
+ *
186
+ * async function logOut() {
187
+ * await releaseThisDevicePush(api.unregisterDevice).catch(() => undefined);
188
+ * clear();
189
+ * }
190
+ *
191
+ * The server first, then the browser — and the browser EVEN WHEN THE SERVER REFUSED (0.52
192
+ * code review, this file): `disable`'s order left a sign-out on a flaky network, or one
193
+ * that met a 5xx, with the device still subscribed. Unsubscribed, the push service answers
194
+ * the server's next send with 410 and the server prunes the row. The browser's unsubscribe
195
+ * is cut at 15 s (`PushDeviceError("push_timeout")`). Then it tells every mounted
196
+ * {@link usePushDevice} ({@link PUSH_SUBSCRIPTION_CHANGED}).
197
+ *
198
+ * Resolves whether this browser held a subscription; `false` at once where push is
199
+ * unsupported or was never set up — no service worker registration, or none ever active
200
+ * (0.52 code review: most devices, which used to reject with `no_service_worker`, after
201
+ * 10 s for a worker stuck installing). It asks the registration, not `ready`: a
202
+ * subscription belongs to the registration, and a sign-out waits for no worker. Rejects
203
+ * as the app's request did — once the browser has let go — or with `push_timeout`. Best
204
+ * effort at a sign-out: catch it, a sign-out must not stop on push.
205
+ *
206
+ * Only an explicit sign-out (Marcel's decision 2 of the 2026-10-30 audit): a session that
207
+ * merely expired keeps its device, and whoever signs back in turns push on again.
208
+ */
209
+ export async function releaseThisDevicePush(unregister: (endpoint: string) => Promise<unknown>): Promise<boolean> {
210
+ if (!pushSupported()) return false;
211
+ try {
212
+ const registration = await navigator.serviceWorker.getRegistration();
213
+ // A worker never active holds no subscription: `subscribe` needs an active one.
214
+ if (!registration?.active) return false;
215
+ const subscription = await registration.pushManager.getSubscription();
216
+ if (!subscription) return false;
217
+ try {
218
+ await unregister(subscription.endpoint);
219
+ } finally {
220
+ await within(subscription.unsubscribe(), PUSH_TIMEOUT_MS, "push_timeout");
221
+ }
222
+ return true;
223
+ } finally {
224
+ notifyPushSubscriptionChanged();
225
+ }
226
+ }
227
+
155
228
  /**
156
229
  * This browser's push for the signed-in account. Hand it one function per endpoint
157
230
  * (§3.8); {@link PushDeviceSetting} draws it, and an app's own card may use it too.
@@ -344,14 +417,7 @@ export function usePushDevice(options: UsePushDeviceOptions): PushDevice {
344
417
  if (!supported) return;
345
418
  setBusy(true);
346
419
  try {
347
- const registration = await activeRegistration();
348
- const subscription = await registration.pushManager.getSubscription();
349
- if (subscription) {
350
- // The server first: a dropped device stops receiving even if the browser's
351
- // unsubscribe below fails.
352
- await latest.current.unregister(subscription.endpoint);
353
- await within(subscription.unsubscribe(), PUSH_TIMEOUT_MS, "push_timeout");
354
- }
420
+ await releaseSubscription((endpoint) => latest.current.unregister(endpoint));
355
421
  if (mounted.current) {
356
422
  setSubscribed(false);
357
423
  setForeignSubscription(false);
@@ -2,6 +2,7 @@ import { createAsyncStoragePersister } from "@tanstack/query-async-storage-persi
2
2
  import type { QueryClient } from "@tanstack/react-query";
3
3
  import type { PersistedClient, PersistQueryClientOptions } from "@tanstack/react-query-persist-client";
4
4
  import { del, get, keys, set } from "idb-keyval";
5
+ import { listenOn, postOnce } from "../lib/broadcast";
5
6
 
6
7
  /**
7
8
  * The persisted TanStack Query cache, keksdose's policy as a kit helper
@@ -101,7 +102,9 @@ export interface QueryPersistence {
101
102
  /** Everything `<PersistQueryClientProvider persistOptions>` needs. */
102
103
  persistOptions: Omit<PersistQueryClientOptions, "queryClient">;
103
104
  /** Sign-out: stops writing under this key, empties the in-memory cache and deletes this
104
- * key's persisted copy. Best-effort; never throws. */
105
+ * key's persisted copy. In every tab (0.52): the persistences of this key in the other tabs
106
+ * stop writing too, and delete the key once more after any save of theirs under way.
107
+ * Best-effort; never throws. */
105
108
  clear: (queryClient: QueryClient) => Promise<void>;
106
109
  /** The company switch (or any change of key without a sign-out): stops writing under this
107
110
  * key — a save already scheduled is dropped too — and empties the in-memory cache, so
@@ -223,6 +226,31 @@ export function createQueryPersistence({
223
226
  // throttled save of the emptied cache) is dropped. A restore re-arms it.
224
227
  let retired = false;
225
228
 
229
+ // 0.52 (refactor audit 2026-10-30, FLW-1): `retired` lived in this tab alone, so a sign-out
230
+ // in one tab deleted the key and another tab's persister wrote its in-memory cache straight
231
+ // back under it. While armed, a persistence listens on a channel named after its key; a
232
+ // `clear` there retires it, and it deletes the key once more — IndexedDB runs a save of its
233
+ // own already under way first. A `leave` posts nothing: another tab may still be on that
234
+ // company. Without BroadcastChannel, as before 0.52.
235
+ const across = `eifi1-query-cache:${key}`;
236
+ let unlisten: (() => void) | null = null;
237
+
238
+ const retire = () => {
239
+ retired = true;
240
+ unlisten?.();
241
+ unlisten = null;
242
+ };
243
+
244
+ const listen = () => {
245
+ unlisten ??= listenOn(across, (data) => {
246
+ if (data !== "clear") return;
247
+ retire();
248
+ Promise.resolve()
249
+ .then(() => storage.removeItem(key))
250
+ .catch(() => undefined);
251
+ });
252
+ };
253
+
226
254
  const keyAllowed = (queryKey: readonly unknown[]) =>
227
255
  !denied.has(String(queryKey[0])) && (shouldPersistKey ? shouldPersistKey(queryKey) : true);
228
256
 
@@ -313,6 +341,7 @@ export function createQueryPersistence({
313
341
  // Only the provider's restore reads: mounting this persistence again re-arms it.
314
342
  getItem: async (k) => {
315
343
  retired = false;
344
+ listen();
316
345
  return (await storage.getItem(k)) ?? null;
317
346
  },
318
347
  setItem: async (k, v) => {
@@ -329,7 +358,7 @@ export function createQueryPersistence({
329
358
  });
330
359
 
331
360
  const leave = async (queryClient: QueryClient) => {
332
- retired = true;
361
+ retire();
333
362
  try {
334
363
  queryClient.clear();
335
364
  } catch {
@@ -338,7 +367,10 @@ export function createQueryPersistence({
338
367
  };
339
368
 
340
369
  const clear = async (queryClient: QueryClient) => {
341
- retired = true;
370
+ // Retired first, so this tab's own channel is closed and does not hear its `clear`.
371
+ retire();
372
+ // Before this tab's delete: the other tabs stop writing as early as they can.
373
+ postOnce(across, "clear");
342
374
  try {
343
375
  queryClient.clear();
344
376
  await persister.removeClient();
@@ -791,7 +791,11 @@ function DateControl({
791
791
  );
792
792
  }
793
793
 
794
- /** The kit's {@link DatePicker}, holding an ISO `"YYYY-MM-DD"` string. */
794
+ /**
795
+ * The kit's {@link DatePicker}, holding an ISO `"YYYY-MM-DD"` string. `commit` and
796
+ * `disabledReason` are the picker's own (0.52, refactor audit 2026-10-30, API-3): a date
797
+ * that saves itself stays focusable under a lock and says why.
798
+ */
795
799
  export function RhfDateField<
796
800
  TFieldValues extends FieldValues = FieldValues,
797
801
  TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
@@ -893,9 +897,11 @@ export type RhfDateRangePickerProps<
893
897
  * pick, which would mark (and, with `mode: "onTouched"`, validate) an end the user
894
898
  * is still choosing.
895
899
  *
896
- * In `commit="immediate"` mode (the picker's default) the first calendar click is
897
- * reported on its own, so the form holds `(from, emptyValue)` until the second; with
898
- * `commit="apply"` nothing reaches the form until Apply.
900
+ * Without `apply` (the picker's default) the first calendar click is reported on its
901
+ * own, so the form holds `(from, emptyValue)` until the second; with `apply` nothing
902
+ * reaches the form until Apply. `apply` was `commit="apply"` until 0.52 (gone in 0.53):
903
+ * `commit` is now the write lock's, as on every other binding — with
904
+ * `disabledReason`, the picker's own (refactor audit 2026-10-30, API-3).
899
905
  *
900
906
  * The common props (`label`, `hint`, `required`, `disabled`, `excludeWhenDisabled`,
901
907
  * `control`, `className`) apply to the pair; `rules` are per field.
@@ -1460,6 +1466,7 @@ function MonthControl({
1460
1466
  * lookup until 0.34, API-3), so focus-on-error lands there; the form's error paints the
1461
1467
  * trigger and is the shell's message; the hint is the shell's description; neither is
1462
1468
  * handed to the picker, so its box never changes. Picking marks the field touched.
1469
+ * `commit` and `disabledReason` are the picker's own (0.52, API-3).
1463
1470
  *
1464
1471
  * ```tsx
1465
1472
  * <RhfMonthPicker name="fiscalYear" label="Fiscal year" mode="year" valueAsNumber
@@ -10,6 +10,8 @@ import { useFocusTrap } from "../hooks/use-focus-trap";
10
10
  import { useBodyScrollLock } from "../hooks/use-body-scroll-lock";
11
11
  import { takeEscape, useDismissLayer } from "../hooks/use-dismiss";
12
12
  import { isImeEnter } from "../lib/ime";
13
+ // A provider's answer is a promise: the only case with anything to wait for.
14
+ import { isThenable } from "../lib/thenable";
13
15
  import { useKitLabels, type LabelOverride } from "../i18n/kit-labels";
14
16
  import { DEFAULT_COMMAND_PALETTE_LABELS } from "./command-palette-labels";
15
17
 
@@ -106,10 +108,6 @@ export { DEFAULT_COMMAND_PALETTE_LABELS };
106
108
  */
107
109
  export type CommandPaletteDensity = "compact" | "comfortable";
108
110
 
109
- /** A provider's answer is a promise — the only case with anything to wait for. */
110
- const isThenable = (value: unknown): value is PromiseLike<unknown> =>
111
- typeof (value as PromiseLike<unknown> | null)?.then === "function";
112
-
113
111
  /** The defaults minus `dialog`, so a resolved `dialog` means someone SUPPLIED one —
114
112
  * see the dialog's `aria-label` for why that distinction matters. */
115
113
  const { dialog: DEFAULT_DIALOG_NAME, ...DEFAULTS_WITHOUT_DIALOG } = DEFAULT_COMMAND_PALETTE_LABELS;
@@ -1,13 +1,15 @@
1
- import { lazy, Suspense, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
2
- import type { ReactNode } from "react";
1
+ import { Suspense, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
2
+ import type { ComponentProps, ReactNode } from "react";
3
3
  import { useHref, useInRouterContext, useNavigate } from "react-router";
4
4
  import { AlertCircle, Loader2, Search } from "lucide-react";
5
5
  import { Tooltip } from "../components/tooltip";
6
6
  import { TOPBAR_TRIGGER_CLASS } from "../shell/topbar-controls";
7
7
  import { cn } from "../lib/cn";
8
+ import { lazyWithFallback, type LazyFallbackProps } from "../lib/lazy-with-fallback";
9
+ import { isApplePlatform } from "../hooks/use-hotkey";
8
10
  import { useKitLabels, type LabelOverride } from "../i18n/kit-labels";
9
11
  // Types only from ./command-palette, gone from the built module: the palette loads late.
10
- import type { CommandItem, CommandPaletteDensity, CommandPaletteLabels } from "./command-palette";
12
+ import type { CommandItem, CommandPalette, CommandPaletteDensity, CommandPaletteLabels } from "./command-palette";
11
13
  import { DEFAULT_COMMAND_PALETTE_LABELS } from "./command-palette-labels";
12
14
  import { useCommandKey } from "./use-command-key";
13
15
  import { createSearchIndex } from "./search-index";
@@ -20,11 +22,32 @@ import { DEFAULT_GLOBAL_SEARCH_LABELS } from "./global-search-labels";
20
22
  * entry chunks, for a dialog drawn only once somebody searches. A LITERAL relative
21
23
  * `import()`, as the catalogue loaders are (i18n/languages.ts), so the library build keeps
22
24
  * the module its own and an app's bundler splits it off.
25
+ *
26
+ * A fetch that fails — a tab left open across a deploy, offline — closes the search rather
27
+ * than throwing (0.52 code review, tour.tsx): the top bar sits above the routes' error
28
+ * boundaries in these apps, so the rejected import took the whole shell down on ⌘K.
23
29
  */
24
- const LazyCommandPalette = lazy(() =>
25
- import("./command-palette").then((module) => ({ default: module.CommandPalette })),
30
+ const LazyCommandPalette = lazyWithFallback(
31
+ () => import("./command-palette").then((module) => module.CommandPalette),
32
+ PaletteUnavailable,
26
33
  );
27
34
 
35
+ /**
36
+ * Stands in for a palette whose chunk did not arrive. Mounted open — this attempt's fetch
37
+ * failed — it closes the search, so the trigger and ⌘K work again; opened after that, it
38
+ * fetches again, and the palette appears if it comes. Never both in one open: offline, a
39
+ * retry on the open it closes would fetch in a loop.
40
+ */
41
+ function PaletteUnavailable({ open, onClose, retry }: ComponentProps<typeof CommandPalette> & LazyFallbackProps) {
42
+ const failedOpen = useRef(open);
43
+ useLayoutEffect(() => {
44
+ if (!open) failedOpen.current = false;
45
+ else if (failedOpen.current) onClose();
46
+ else retry();
47
+ }, [open, onClose, retry]);
48
+ return null;
49
+ }
50
+
28
51
  export interface GlobalSearchLabels {
29
52
  /** The trigger's accessible name, and the dialog's. */
30
53
  trigger: string;
@@ -193,12 +216,6 @@ export interface GlobalSearchProps {
193
216
  [key: `data-${string}`]: string | number | boolean | undefined;
194
217
  }
195
218
 
196
- function isApplePlatform(): boolean {
197
- if (typeof navigator === "undefined") return false;
198
- const nav = navigator as Navigator & { userAgentData?: { platform?: string } };
199
- return /mac|iphone|ipad|ipod/i.test(nav.userAgentData?.platform || nav.platform || nav.userAgent);
200
- }
201
-
202
219
  const pageLoad = (href: string) => window.location.assign(href);
203
220
  const identity = (href: string) => href;
204
221
 
@@ -347,7 +364,8 @@ function GlobalSearchImpl({
347
364
 
348
365
  const openSearch = useCallback(() => setOpen(true), [setOpen]);
349
366
  const noop = useCallback(() => {}, []);
350
- useCommandKey(shortcut ? openSearch : noop);
367
+ // Off, not a no-op: a no-op still cancelled every Mod+K (0.52 code review, global-search.tsx).
368
+ useCommandKey(openSearch, { enabled: shortcut });
351
369
 
352
370
  /* ── the static index ─────────────────────────────────────────────────── */
353
371
 
@@ -572,6 +590,7 @@ function GlobalSearchImpl({
572
590
  [l.trigger, l.placeholder, paletteLabels],
573
591
  );
574
592
 
593
+ // `useHotkey`'s own platform check, so the label and the chord it answers never disagree.
575
594
  const apple = useMemo(() => isApplePlatform(), []);
576
595
  const keys = apple ? "⌘K" : "Ctrl K";
577
596
  const triggerLabel = triggerName === "withShortcut" ? l.shortcut(keys) : l.trigger;
@@ -580,7 +599,10 @@ function GlobalSearchImpl({
580
599
  label: triggerLabel,
581
600
  tooltip: l.shortcut(keys),
582
601
  keys,
583
- ariaKeyShortcuts: "Meta+K Control+K",
602
+ // The one chord that opens it here: Mod+K since 0.52 (DUP-12). Both were announced,
603
+ // and on every platform one of them did nothing — Control+K is a Mac text field's
604
+ // kill-line (0.52 code review, global-search.tsx).
605
+ ariaKeyShortcuts: apple ? "Meta+K" : "Control+K",
584
606
  };
585
607
 
586
608
  return (
@@ -613,8 +635,7 @@ function GlobalSearchImpl({
613
635
  </Tooltip>
614
636
  )}
615
637
  {/* Nothing is drawn while the palette's module is on its way (a few milliseconds,
616
- once per page load). A failed load is a chunk-load error for the app's error
617
- boundary, as a lazy route's is. */}
638
+ once per page load). A failed load closes the search (`PaletteUnavailable`). */}
618
639
  {paletteMounted && (
619
640
  <Suspense fallback={null}>
620
641
  <LazyCommandPalette
@@ -281,18 +281,3 @@ function capByGroup<E extends SearchEntry>(hits: SearchHit<E>[], groupLimit: num
281
281
  }
282
282
  return out;
283
283
  }
284
-
285
- /**
286
- * One-shot form of {@link createSearchIndex}: the matching entries, best first. For a
287
- * list that changes on every call (a server response, a store selector); build an index
288
- * once when the entries are stable.
289
- */
290
- export function matchEntries<E extends SearchEntry>(
291
- entries: readonly E[],
292
- query: string,
293
- options?: SearchIndexOptions,
294
- ): E[] {
295
- return createSearchIndex(entries, options)
296
- .search(query)
297
- .map((hit) => hit.entry);
298
- }
@@ -1,4 +1,4 @@
1
- import { useEffect } from "react";
1
+ import { useHotkey, type UseHotkeyOptions } from "../hooks/use-hotkey";
2
2
 
3
3
  /**
4
4
  * Registers a global ⌘K / Ctrl-K shortcut that calls `onOpen`.
@@ -7,16 +7,17 @@ import { useEffect } from "react";
7
7
  * the shortcut from the moment the top bar mounts but loads the palette only when it is
8
8
  * first opened, and a hook imported from command-palette.tsx would have brought the
9
9
  * palette along with it. Still exported from command-palette.tsx, where it always was.
10
+ *
11
+ * `useHotkey("Mod+K")` since 0.52 (refactor audit 2026-10-30, DUP-12): Cmd+K on a Mac,
12
+ * Ctrl+K elsewhere, with no other modifier, once per press. Hand-rolled, it took Ctrl+K
13
+ * on a Mac as well — a text field's kill-line there — and Ctrl+Shift+K, Cmd+Alt+K and
14
+ * every repeat of a held key. It still fires from inside a text field: the palette is
15
+ * where a search starts, whatever has focus.
16
+ *
17
+ * `enabled: false` lets Mod+K go (0.52 code review, global-search.tsx): `GlobalSearch
18
+ * shortcut={false}` passed a no-op, and the listener still cancelled every Mod+K — the
19
+ * browser's own Ctrl+K and any other handler on the page that checks `defaultPrevented`.
10
20
  */
11
- export function useCommandKey(onOpen: () => void): void {
12
- useEffect(() => {
13
- const handler = (e: KeyboardEvent) => {
14
- if ((e.metaKey || e.ctrlKey) && (e.key === "k" || e.key === "K")) {
15
- e.preventDefault();
16
- onOpen();
17
- }
18
- };
19
- document.addEventListener("keydown", handler);
20
- return () => document.removeEventListener("keydown", handler);
21
- }, [onOpen]);
21
+ export function useCommandKey(onOpen: () => void, options: Pick<UseHotkeyOptions, "enabled"> = {}): void {
22
+ useHotkey("Mod+K", () => onOpen(), { allowInInputs: true, enabled: options.enabled });
22
23
  }
package/src/search.ts CHANGED
@@ -1,5 +1,14 @@
1
1
  // `@eifi1/ui-kit/search` — the command palette / global search.
2
2
  // A re-slicing of the main barrel, not a new API.
3
3
  export * from "./search/command-palette";
4
- export * from "./search/search-index";
4
+ // Named, not `*`: 0.53 took `SEARCH_TIER_POINTS` and `normalizeSearchText` off the surface
5
+ // (deprecated in 0.52, refactor audit 2026-10-30, API-4); the index keeps them.
6
+ export {
7
+ createSearchIndex,
8
+ type SearchEntry,
9
+ type SearchMatchField,
10
+ type SearchHit,
11
+ type SearchIndexOptions,
12
+ type SearchIndex,
13
+ } from "./search/search-index";
5
14
  export * from "./search/global-search";
@@ -59,6 +59,9 @@ export function useSettingsFocus(anchor?: string | null): void {
59
59
  if (ring.current) {
60
60
  window.clearTimeout(ring.current.timer);
61
61
  ring.current.el.classList.remove(...SETTINGS_FOCUS_RING);
62
+ // The mark goes with the ring, as the timer's own end takes both: left behind, the
63
+ // first card stayed "focused" for good (keksdose's matching-tab test reads it).
64
+ ring.current.el.removeAttribute("data-settings-focus");
62
65
  }
63
66
  el.classList.add(...SETTINGS_FOCUS_RING);
64
67
  el.setAttribute("data-settings-focus", "");