@eifi1/ui-kit 0.53.0 → 0.55.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 (902) hide show
  1. package/README.md +96 -82
  2. package/dist/account/delete-account-setting.d.ts +3 -2
  3. package/dist/account/delete-account-setting.js.map +1 -1
  4. package/dist/admin/plan-change.js +2 -2
  5. package/dist/admin/plan-change.js.map +1 -1
  6. package/dist/admin/user-roster.d.ts +6 -0
  7. package/dist/admin/user-roster.js +4 -0
  8. package/dist/admin/user-roster.js.map +1 -1
  9. package/dist/auth/auth-errors.d.ts +36 -0
  10. package/dist/auth/auth-errors.js +20 -0
  11. package/dist/auth/auth-errors.js.map +1 -1
  12. package/dist/billing/billing-labels.d.ts +26 -0
  13. package/dist/billing/billing-labels.js +9 -0
  14. package/dist/billing/billing-labels.js.map +1 -1
  15. package/dist/billing/subscription-standing.d.ts +61 -0
  16. package/dist/billing/subscription-standing.js +51 -0
  17. package/dist/billing/subscription-standing.js.map +1 -0
  18. package/dist/billing/usage-meter.d.ts +52 -0
  19. package/dist/billing/usage-meter.js +52 -0
  20. package/dist/billing/usage-meter.js.map +1 -0
  21. package/dist/chart.d.ts +1 -0
  22. package/dist/chart.js +4 -0
  23. package/dist/chart.js.map +1 -1
  24. package/dist/components/alert-banner.js +2 -3
  25. package/dist/components/alert-banner.js.map +1 -1
  26. package/dist/components/amount-input.d.ts +2 -1
  27. package/dist/components/amount-input.js +3 -15
  28. package/dist/components/amount-input.js.map +1 -1
  29. package/dist/components/autocomplete.js +2 -2
  30. package/dist/components/autocomplete.js.map +1 -1
  31. package/dist/components/bulk-action-bar.js +2 -1
  32. package/dist/components/bulk-action-bar.js.map +1 -1
  33. package/dist/components/button.d.ts +35 -1
  34. package/dist/components/button.js +5 -9
  35. package/dist/components/button.js.map +1 -1
  36. package/dist/components/calendar-heatmap.js +6 -4
  37. package/dist/components/calendar-heatmap.js.map +1 -1
  38. package/dist/components/camera-button.d.ts +89 -0
  39. package/dist/components/camera-button.js +122 -0
  40. package/dist/components/camera-button.js.map +1 -0
  41. package/dist/components/camera-capture.d.ts +66 -0
  42. package/dist/components/camera-capture.js +190 -0
  43. package/dist/components/camera-capture.js.map +1 -0
  44. package/dist/components/camera-labels.d.ts +20 -0
  45. package/dist/components/camera-labels.js +15 -0
  46. package/dist/components/camera-labels.js.map +1 -0
  47. package/dist/components/chart-edge.d.ts +9 -0
  48. package/dist/components/chart-edge.js +14 -3
  49. package/dist/components/chart-edge.js.map +1 -1
  50. package/dist/components/chart-zoom-core.js +34 -29
  51. package/dist/components/chart-zoom-core.js.map +1 -1
  52. package/dist/components/checkbox-group.js +1 -1
  53. package/dist/components/checkbox-group.js.map +1 -1
  54. package/dist/components/combobox-core.js +3 -2
  55. package/dist/components/combobox-core.js.map +1 -1
  56. package/dist/components/combobox.js +2 -2
  57. package/dist/components/combobox.js.map +1 -1
  58. package/dist/components/consent-again-notice-labels.d.ts +25 -0
  59. package/dist/components/consent-again-notice-labels.js +15 -0
  60. package/dist/components/consent-again-notice-labels.js.map +1 -0
  61. package/dist/components/consent-again-notice.d.ts +85 -0
  62. package/dist/components/consent-again-notice.js +94 -0
  63. package/dist/components/consent-again-notice.js.map +1 -0
  64. package/dist/components/country-select.js +2 -7
  65. package/dist/components/country-select.js.map +1 -1
  66. package/dist/components/currency-option-list.d.ts +2 -1
  67. package/dist/components/currency-option-list.js +10 -3
  68. package/dist/components/currency-option-list.js.map +1 -1
  69. package/dist/components/currency-select.d.ts +3 -1
  70. package/dist/components/currency-select.js +2 -2
  71. package/dist/components/currency-select.js.map +1 -1
  72. package/dist/components/danger-confirm.d.ts +22 -1
  73. package/dist/components/danger-confirm.js +52 -30
  74. package/dist/components/danger-confirm.js.map +1 -1
  75. package/dist/components/data-table-column-settings.js +2 -2
  76. package/dist/components/data-table-column-settings.js.map +1 -1
  77. package/dist/components/data-table-filter-popover.d.ts +2 -1
  78. package/dist/components/data-table-filter-popover.js +3 -2
  79. package/dist/components/data-table-filter-popover.js.map +1 -1
  80. package/dist/components/data-table-filters.d.ts +33 -3
  81. package/dist/components/data-table-filters.js +6 -4
  82. package/dist/components/data-table-filters.js.map +1 -1
  83. package/dist/components/data-table-fit.js.map +1 -1
  84. package/dist/components/data-table-virtual.js +13 -3
  85. package/dist/components/data-table-virtual.js.map +1 -1
  86. package/dist/components/date-field.js +2 -2
  87. package/dist/components/date-field.js.map +1 -1
  88. package/dist/components/date-picker.js +2 -2
  89. package/dist/components/date-picker.js.map +1 -1
  90. package/dist/components/date-range-picker.js +2 -6
  91. package/dist/components/date-range-picker.js.map +1 -1
  92. package/dist/components/dialog-frame.d.ts +5 -1
  93. package/dist/components/dialog-frame.js +6 -2
  94. package/dist/components/dialog-frame.js.map +1 -1
  95. package/dist/components/dropdown.js +7 -3
  96. package/dist/components/dropdown.js.map +1 -1
  97. package/dist/components/entity-combobox.js +2 -2
  98. package/dist/components/entity-combobox.js.map +1 -1
  99. package/dist/components/field-parts.d.ts +108 -6
  100. package/dist/components/field-parts.js +59 -6
  101. package/dist/components/field-parts.js.map +1 -1
  102. package/dist/components/field-suggestion-labels.d.ts +2 -0
  103. package/dist/components/field-suggestion-labels.js +10 -0
  104. package/dist/components/field-suggestion-labels.js.map +1 -0
  105. package/dist/components/field.js +1 -1
  106. package/dist/components/field.js.map +1 -1
  107. package/dist/components/file-button.d.ts +1 -1
  108. package/dist/components/file-button.js.map +1 -1
  109. package/dist/components/floating-panel.d.ts +7 -6
  110. package/dist/components/floating-panel.js +3 -17
  111. package/dist/components/floating-panel.js.map +1 -1
  112. package/dist/components/form-actions.d.ts +3 -2
  113. package/dist/components/form-actions.js.map +1 -1
  114. package/dist/components/hover-menu.js +3 -2
  115. package/dist/components/hover-menu.js.map +1 -1
  116. package/dist/components/icon-button.d.ts +23 -5
  117. package/dist/components/icon-button.js +6 -8
  118. package/dist/components/icon-button.js.map +1 -1
  119. package/dist/components/inline-confirm.js +2 -1
  120. package/dist/components/inline-confirm.js.map +1 -1
  121. package/dist/components/inline-entity-combobox.js +2 -2
  122. package/dist/components/inline-entity-combobox.js.map +1 -1
  123. package/dist/components/input.js +2 -2
  124. package/dist/components/input.js.map +1 -1
  125. package/dist/components/jump-calendar.js +3 -3
  126. package/dist/components/jump-calendar.js.map +1 -1
  127. package/dist/components/legal.d.ts +13 -1
  128. package/dist/components/legal.js +12 -0
  129. package/dist/components/legal.js.map +1 -1
  130. package/dist/components/legended-series-chart.d.ts +80 -0
  131. package/dist/components/legended-series-chart.js +70 -0
  132. package/dist/components/legended-series-chart.js.map +1 -0
  133. package/dist/components/lightbox.js +2 -1
  134. package/dist/components/lightbox.js.map +1 -1
  135. package/dist/components/mini-calendar.js +16 -7
  136. package/dist/components/mini-calendar.js.map +1 -1
  137. package/dist/components/modal.d.ts +48 -0
  138. package/dist/components/modal.js +26 -6
  139. package/dist/components/modal.js.map +1 -1
  140. package/dist/components/month-picker.d.ts +1 -1
  141. package/dist/components/month-picker.js +6 -6
  142. package/dist/components/month-picker.js.map +1 -1
  143. package/dist/components/multi-entity-combobox.js +2 -2
  144. package/dist/components/multi-entity-combobox.js.map +1 -1
  145. package/dist/components/multi-select.js +2 -6
  146. package/dist/components/multi-select.js.map +1 -1
  147. package/dist/components/number-input.js +2 -2
  148. package/dist/components/number-input.js.map +1 -1
  149. package/dist/components/page-header-layout.d.ts +18 -0
  150. package/dist/components/page-header-layout.js +23 -0
  151. package/dist/components/page-header-layout.js.map +1 -0
  152. package/dist/components/page-header-skeleton.d.ts +41 -0
  153. package/dist/components/page-header-skeleton.js +52 -0
  154. package/dist/components/page-header-skeleton.js.map +1 -0
  155. package/dist/components/page-header.js +4 -17
  156. package/dist/components/page-header.js.map +1 -1
  157. package/dist/components/popover.js +7 -2
  158. package/dist/components/popover.js.map +1 -1
  159. package/dist/components/release-notes-labels.d.ts +16 -0
  160. package/dist/components/release-notes-labels.js +18 -0
  161. package/dist/components/release-notes-labels.js.map +1 -0
  162. package/dist/components/release-notes.d.ts +65 -0
  163. package/dist/components/release-notes.js +65 -0
  164. package/dist/components/release-notes.js.map +1 -0
  165. package/dist/components/select.js +2 -2
  166. package/dist/components/select.js.map +1 -1
  167. package/dist/components/series-chart-labels.d.ts +9 -0
  168. package/dist/components/series-chart-labels.js +3 -1
  169. package/dist/components/series-chart-labels.js.map +1 -1
  170. package/dist/components/series-chart.d.ts +5 -0
  171. package/dist/components/series-chart.js +89 -36
  172. package/dist/components/series-chart.js.map +1 -1
  173. package/dist/components/stat-tile.d.ts +21 -2
  174. package/dist/components/stat-tile.js +4 -2
  175. package/dist/components/stat-tile.js.map +1 -1
  176. package/dist/components/toggle-group.js +3 -2
  177. package/dist/components/toggle-group.js.map +1 -1
  178. package/dist/components/treemap-layout.js.map +1 -1
  179. package/dist/components/treemap.d.ts +17 -15
  180. package/dist/components/treemap.js +167 -85
  181. package/dist/components/treemap.js.map +1 -1
  182. package/dist/currencies.d.ts +16 -2
  183. package/dist/currencies.js +2 -1
  184. package/dist/currencies.js.map +1 -1
  185. package/dist/eslint/index.js +1 -1
  186. package/dist/eslint/role-utilities.d.ts +2 -2
  187. package/dist/eslint/role-utilities.js.map +1 -1
  188. package/dist/feedback/feedback-context-labels.js +2 -1
  189. package/dist/feedback/feedback-context-labels.js.map +1 -1
  190. package/dist/feedback/feedback-context.d.ts +19 -3
  191. package/dist/feedback/feedback-context.js +12 -1
  192. package/dist/feedback/feedback-context.js.map +1 -1
  193. package/dist/feedback/feedback-diagnostics.d.ts +17 -0
  194. package/dist/feedback/feedback-diagnostics.js +27 -0
  195. package/dist/feedback/feedback-diagnostics.js.map +1 -0
  196. package/dist/feedback/feedback-dialog.d.ts +10 -0
  197. package/dist/feedback/feedback-dialog.js +6 -4
  198. package/dist/feedback/feedback-dialog.js.map +1 -1
  199. package/dist/feedback/feedback-inbox-page.d.ts +260 -0
  200. package/dist/feedback/feedback-inbox-page.js +207 -0
  201. package/dist/feedback/feedback-inbox-page.js.map +1 -0
  202. package/dist/feedback/feedback-record.d.ts +5 -0
  203. package/dist/feedback/feedback-record.js.map +1 -1
  204. package/dist/feedback/feedback-row-detail-labels.js +1 -0
  205. package/dist/feedback/feedback-row-detail-labels.js.map +1 -1
  206. package/dist/feedback/feedback-row-detail.d.ts +3 -0
  207. package/dist/feedback/feedback-row-detail.js +3 -0
  208. package/dist/feedback/feedback-row-detail.js.map +1 -1
  209. package/dist/feedback/feedback-submit-dialog.d.ts +5 -1
  210. package/dist/feedback/feedback-submit-dialog.js +15 -3
  211. package/dist/feedback/feedback-submit-dialog.js.map +1 -1
  212. package/dist/feedback/feedback-submit.d.ts +30 -2
  213. package/dist/feedback/feedback-submit.js +5 -2
  214. package/dist/feedback/feedback-submit.js.map +1 -1
  215. package/dist/feedback/feedback-vocabulary.js +2 -2
  216. package/dist/feedback/feedback-vocabulary.js.map +1 -1
  217. package/dist/feedback.d.ts +1 -0
  218. package/dist/feedback.js +6 -0
  219. package/dist/feedback.js.map +1 -1
  220. package/dist/hooks/use-debounce.d.ts +32 -5
  221. package/dist/hooks/use-debounce.js +6 -5
  222. package/dist/hooks/use-debounce.js.map +1 -1
  223. package/dist/hooks/use-file-picker.d.ts +3 -1
  224. package/dist/hooks/use-file-picker.js.map +1 -1
  225. package/dist/hooks/use-focus-trap.d.ts +19 -3
  226. package/dist/hooks/use-focus-trap.js +15 -5
  227. package/dist/hooks/use-focus-trap.js.map +1 -1
  228. package/dist/hooks/use-has-camera.d.ts +36 -0
  229. package/dist/hooks/use-has-camera.js +53 -0
  230. package/dist/hooks/use-has-camera.js.map +1 -0
  231. package/dist/hooks/use-hotkey.js +2 -1
  232. package/dist/hooks/use-hotkey.js.map +1 -1
  233. package/dist/hooks/use-overlay-history.d.ts +6 -1
  234. package/dist/hooks/use-overlay-history.js +10 -2
  235. package/dist/hooks/use-overlay-history.js.map +1 -1
  236. package/dist/i18n/defaults.js +11 -1
  237. package/dist/i18n/defaults.js.map +1 -1
  238. package/dist/i18n/german.js +78 -4
  239. package/dist/i18n/german.js.map +1 -1
  240. package/dist/i18n/kit-labels.d.ts +20 -0
  241. package/dist/i18n/kit-labels.js.map +1 -1
  242. package/dist/i18n/locales/en.js +95 -5
  243. package/dist/i18n/locales/en.js.map +3 -3
  244. package/dist/i18n/locales/es.js +74 -4
  245. package/dist/i18n/locales/es.js.map +1 -1
  246. package/dist/i18n/locales/fr.js +75 -3
  247. package/dist/i18n/locales/fr.js.map +1 -1
  248. package/dist/i18n/locales/hu.js +77 -4
  249. package/dist/i18n/locales/hu.js.map +1 -1
  250. package/dist/i18n/locales/it.js +77 -4
  251. package/dist/i18n/locales/it.js.map +1 -1
  252. package/dist/i18n/locales/zh.js +74 -4
  253. package/dist/i18n/locales/zh.js.map +1 -1
  254. package/dist/i18n/review.js +22 -1
  255. package/dist/i18n/review.js.map +1 -1
  256. package/dist/index.d.ts +27 -8
  257. package/dist/index.js +41 -2
  258. package/dist/index.js.map +1 -1
  259. package/dist/landing/landing-labels.d.ts +8 -0
  260. package/dist/landing/landing-labels.js +2 -1
  261. package/dist/landing/landing-labels.js.map +1 -1
  262. package/dist/lib/changelog.d.ts +130 -0
  263. package/dist/lib/changelog.js +137 -0
  264. package/dist/lib/changelog.js.map +1 -0
  265. package/dist/lib/clipboard-minus.js +2 -2
  266. package/dist/lib/clipboard-minus.js.map +1 -1
  267. package/dist/lib/dates.d.ts +36 -4
  268. package/dist/lib/dates.js +49 -13
  269. package/dist/lib/dates.js.map +1 -1
  270. package/dist/lib/editable-content.d.ts +33 -0
  271. package/dist/lib/editable-content.js +15 -0
  272. package/dist/lib/editable-content.js.map +1 -0
  273. package/dist/lib/focus-left.d.ts +47 -1
  274. package/dist/lib/focus-left.js +11 -2
  275. package/dist/lib/focus-left.js.map +1 -1
  276. package/dist/lib/format.d.ts +37 -2
  277. package/dist/lib/format.js +25 -1
  278. package/dist/lib/format.js.map +1 -1
  279. package/dist/lib/intl-cache.d.ts +4 -2
  280. package/dist/lib/intl-cache.js.map +1 -1
  281. package/dist/lib/server-wake.d.ts +49 -9
  282. package/dist/lib/server-wake.js +25 -14
  283. package/dist/lib/server-wake.js.map +1 -1
  284. package/dist/lib/session-refresh.d.ts +53 -0
  285. package/dist/lib/session-refresh.js +27 -1
  286. package/dist/lib/session-refresh.js.map +1 -1
  287. package/dist/lib/translation-review.d.ts +26 -0
  288. package/dist/lib/translation-review.js +14 -0
  289. package/dist/lib/translation-review.js.map +1 -1
  290. package/dist/rhf/fields.js +3 -3
  291. package/dist/rhf/fields.js.map +1 -1
  292. package/dist/shell/app-shell.d.ts +25 -1
  293. package/dist/shell/app-shell.js +3 -1
  294. package/dist/shell/app-shell.js.map +1 -1
  295. package/dist/shell/dev-menu.d.ts +3 -0
  296. package/dist/shell/dev-menu.js +4 -2
  297. package/dist/shell/dev-menu.js.map +1 -1
  298. package/dist/shell/held-changes-banner-labels.d.ts +23 -0
  299. package/dist/shell/held-changes-banner-labels.js +14 -0
  300. package/dist/shell/held-changes-banner-labels.js.map +1 -0
  301. package/dist/shell/held-changes-banner.d.ts +75 -0
  302. package/dist/shell/held-changes-banner.js +106 -0
  303. package/dist/shell/held-changes-banner.js.map +1 -0
  304. package/dist/shell/sync-status-indicator.d.ts +27 -12
  305. package/dist/shell/sync-status-indicator.js +13 -4
  306. package/dist/shell/sync-status-indicator.js.map +1 -1
  307. package/dist/shell/top-bar-parts-labels.d.ts +5 -0
  308. package/dist/shell/top-bar-parts-labels.js +3 -1
  309. package/dist/shell/top-bar-parts-labels.js.map +1 -1
  310. package/dist/shell.d.ts +2 -0
  311. package/dist/shell.js +3 -0
  312. package/dist/shell.js.map +1 -1
  313. package/dist/sync/engine.d.ts +11 -2
  314. package/dist/sync/engine.js +13 -5
  315. package/dist/sync/engine.js.map +1 -1
  316. package/dist/sync/hooks.d.ts +3 -2
  317. package/dist/sync/hooks.js +4 -2
  318. package/dist/sync/hooks.js.map +1 -1
  319. package/dist/testing/css-variables.generated.d.ts +2 -2
  320. package/dist/testing/css-variables.generated.js +1 -1
  321. package/dist/testing/css-variables.generated.js.map +1 -1
  322. package/dist/theme/chart-palette.d.ts +18 -0
  323. package/dist/theme/chart-palette.js +17 -1
  324. package/dist/theme/chart-palette.js.map +1 -1
  325. package/dist/theme/contrast-tokens.d.ts +3 -3
  326. package/dist/theme/contrast-tokens.js.map +1 -1
  327. package/dist/theme/contrast.d.ts +4 -4
  328. package/dist/theme/contrast.js.map +1 -1
  329. package/dist/tour/tour-overlay.js +2 -1
  330. package/dist/tour/tour-overlay.js.map +1 -1
  331. package/package.json +5 -9
  332. package/tokens.css +19 -54
  333. package/src/account/account-parts.ts +0 -47
  334. package/src/account/account-settings-labels.ts +0 -240
  335. package/src/account/account-settings.tsx +0 -698
  336. package/src/account/data-export-setting-labels.ts +0 -20
  337. package/src/account/data-export-setting.tsx +0 -200
  338. package/src/account/delete-account-setting-labels.ts +0 -33
  339. package/src/account/delete-account-setting.tsx +0 -341
  340. package/src/account/email-change-setting-labels.ts +0 -31
  341. package/src/account/email-change-setting.tsx +0 -443
  342. package/src/account/passkeys-setting.tsx +0 -445
  343. package/src/account/sessions-setting-labels.ts +0 -24
  344. package/src/account/sessions-setting.tsx +0 -283
  345. package/src/admin/admin-action-confirm-labels.ts +0 -19
  346. package/src/admin/admin-action-confirm.tsx +0 -509
  347. package/src/admin/admin-action-log-labels.ts +0 -83
  348. package/src/admin/admin-action-log.tsx +0 -464
  349. package/src/admin/admin-area-labels.ts +0 -114
  350. package/src/admin/admin-forbidden.tsx +0 -75
  351. package/src/admin/admin-overview.tsx +0 -258
  352. package/src/admin/admin-parts.ts +0 -68
  353. package/src/admin/admin-sections.tsx +0 -208
  354. package/src/admin/admin-tool-links.tsx +0 -90
  355. package/src/admin/invitations-panel-labels.ts +0 -31
  356. package/src/admin/invitations-panel.tsx +0 -516
  357. package/src/admin/mail-test-card-labels.ts +0 -19
  358. package/src/admin/mail-test-card.tsx +0 -189
  359. package/src/admin/plan-change-labels.ts +0 -5
  360. package/src/admin/plan-change.tsx +0 -474
  361. package/src/admin/reviewer-scope-editor-labels.ts +0 -16
  362. package/src/admin/reviewer-scope-editor.tsx +0 -224
  363. package/src/admin/role-select-labels.ts +0 -16
  364. package/src/admin/role-select.tsx +0 -282
  365. package/src/admin/transfer-ownership-dialog-labels.ts +0 -20
  366. package/src/admin/transfer-ownership-dialog.tsx +0 -162
  367. package/src/admin/user-roster-labels.ts +0 -17
  368. package/src/admin/user-roster.tsx +0 -559
  369. package/src/auth/accept-invitation-labels.ts +0 -21
  370. package/src/auth/accept-invitation.tsx +0 -210
  371. package/src/auth/auth-errors-labels.ts +0 -23
  372. package/src/auth/auth-errors.ts +0 -370
  373. package/src/auth/complete-name-dialog-labels.ts +0 -16
  374. package/src/auth/complete-name-dialog.tsx +0 -253
  375. package/src/auth/email-tag.ts +0 -75
  376. package/src/auth/forgot-password-form-labels.ts +0 -20
  377. package/src/auth/forgot-password-form.tsx +0 -221
  378. package/src/auth/form-rules.ts +0 -40
  379. package/src/auth/new-password-fields.tsx +0 -85
  380. package/src/auth/not-found-page-labels.ts +0 -12
  381. package/src/auth/not-found-page.tsx +0 -95
  382. package/src/auth/register-form-labels.ts +0 -31
  383. package/src/auth/register-form.tsx +0 -456
  384. package/src/auth/reset-password-form-labels.ts +0 -27
  385. package/src/auth/reset-password-form.tsx +0 -341
  386. package/src/auth/sign-in-form-labels.ts +0 -44
  387. package/src/auth/sign-in-form.tsx +0 -858
  388. package/src/auth/status-parts.tsx +0 -132
  389. package/src/auth/verify-email-labels.ts +0 -26
  390. package/src/auth/verify-email.tsx +0 -445
  391. package/src/billing/billing-banner.tsx +0 -258
  392. package/src/billing/billing-labels.ts +0 -381
  393. package/src/billing/billing-lock.ts +0 -108
  394. package/src/billing/billing-overview.ts +0 -157
  395. package/src/billing/billing-standing.ts +0 -80
  396. package/src/billing/cancel-request.ts +0 -97
  397. package/src/billing/cancel-subscription.tsx +0 -98
  398. package/src/billing/checkout-processing.ts +0 -394
  399. package/src/billing/checkout-return.ts +0 -90
  400. package/src/billing/pay-page.ts +0 -330
  401. package/src/billing/plan-card.tsx +0 -562
  402. package/src/billing/plan-change-labels.ts +0 -66
  403. package/src/billing/plan-limit-notice.tsx +0 -148
  404. package/src/billing/plan-limit-toast.tsx +0 -90
  405. package/src/billing/plan-limit.ts +0 -52
  406. package/src/billing/plan-price.ts +0 -98
  407. package/src/billing/plan-switch.tsx +0 -216
  408. package/src/billing/provider-page.ts +0 -84
  409. package/src/billing/subscription-actions.tsx +0 -247
  410. package/src/billing/subscription-status-chip.tsx +0 -95
  411. package/src/chart.ts +0 -103
  412. package/src/components/account-chips-labels.ts +0 -16
  413. package/src/components/account-chips.tsx +0 -474
  414. package/src/components/alert-banner.tsx +0 -670
  415. package/src/components/amount-input.tsx +0 -815
  416. package/src/components/appearance-labels.ts +0 -63
  417. package/src/components/authed-image-labels.ts +0 -11
  418. package/src/components/authed-image.tsx +0 -209
  419. package/src/components/autocomplete.tsx +0 -645
  420. package/src/components/breadcrumbs-labels.ts +0 -10
  421. package/src/components/breadcrumbs.tsx +0 -205
  422. package/src/components/bulk-action-bar-labels.ts +0 -11
  423. package/src/components/bulk-action-bar.tsx +0 -443
  424. package/src/components/button-group.tsx +0 -223
  425. package/src/components/button-look.ts +0 -256
  426. package/src/components/button.tsx +0 -339
  427. package/src/components/calculator.tsx +0 -341
  428. package/src/components/calendar-heatmap-labels.ts +0 -15
  429. package/src/components/calendar-heatmap.tsx +0 -594
  430. package/src/components/card.tsx +0 -377
  431. package/src/components/chart-edge.ts +0 -106
  432. package/src/components/chart-figure.tsx +0 -35
  433. package/src/components/chart-height.ts +0 -30
  434. package/src/components/chart-zoom-core.tsx +0 -627
  435. package/src/components/chart-zoom.tsx +0 -22
  436. package/src/components/chart.tsx +0 -554
  437. package/src/components/checkbox-group.tsx +0 -247
  438. package/src/components/checkbox.tsx +0 -346
  439. package/src/components/chip-input.tsx +0 -370
  440. package/src/components/chip-labels.ts +0 -13
  441. package/src/components/chip.tsx +0 -843
  442. package/src/components/choice-card.tsx +0 -544
  443. package/src/components/column-mapper-labels.ts +0 -38
  444. package/src/components/column-mapper.tsx +0 -655
  445. package/src/components/combobox-core.tsx +0 -1321
  446. package/src/components/combobox-suggestions.tsx +0 -131
  447. package/src/components/combobox.tsx +0 -716
  448. package/src/components/confirm-context.ts +0 -7
  449. package/src/components/confirm-dialog-labels.ts +0 -13
  450. package/src/components/confirm-dialog.tsx +0 -341
  451. package/src/components/copy-button-labels.ts +0 -13
  452. package/src/components/copy-button.tsx +0 -192
  453. package/src/components/country-select-labels.ts +0 -11
  454. package/src/components/country-select.tsx +0 -539
  455. package/src/components/currency-option-list.tsx +0 -180
  456. package/src/components/currency-select.tsx +0 -341
  457. package/src/components/danger-confirm-labels.ts +0 -20
  458. package/src/components/danger-confirm.tsx +0 -934
  459. package/src/components/data-table-cells.tsx +0 -118
  460. package/src/components/data-table-column-settings.tsx +0 -198
  461. package/src/components/data-table-filter-popover.tsx +0 -281
  462. package/src/components/data-table-filters.ts +0 -287
  463. package/src/components/data-table-fit.ts +0 -301
  464. package/src/components/data-table-groups.tsx +0 -288
  465. package/src/components/data-table-head.tsx +0 -280
  466. package/src/components/data-table-labels.ts +0 -198
  467. package/src/components/data-table-leading-row.tsx +0 -58
  468. package/src/components/data-table-mobile.tsx +0 -1117
  469. package/src/components/data-table-pagination.tsx +0 -202
  470. package/src/components/data-table-resize.ts +0 -188
  471. package/src/components/data-table-row-actions.tsx +0 -302
  472. package/src/components/data-table-row.tsx +0 -496
  473. package/src/components/data-table-selection.ts +0 -71
  474. package/src/components/data-table-shared.tsx +0 -287
  475. package/src/components/data-table-sort.ts +0 -163
  476. package/src/components/data-table-virtual.ts +0 -576
  477. package/src/components/data-table.tsx +0 -2598
  478. package/src/components/date-field.tsx +0 -684
  479. package/src/components/date-format-setting.tsx +0 -64
  480. package/src/components/date-picker.tsx +0 -404
  481. package/src/components/date-range-picker.tsx +0 -518
  482. package/src/components/description-list-labels.ts +0 -9
  483. package/src/components/description-list.tsx +0 -386
  484. package/src/components/dialog-frame-labels.ts +0 -7
  485. package/src/components/dialog-frame.tsx +0 -276
  486. package/src/components/disclosure.tsx +0 -476
  487. package/src/components/dropdown.tsx +0 -461
  488. package/src/components/empty-state.tsx +0 -136
  489. package/src/components/entity-combobox.tsx +0 -422
  490. package/src/components/error-boundary-labels.ts +0 -20
  491. package/src/components/error-boundary.tsx +0 -675
  492. package/src/components/facing-pair.ts +0 -93
  493. package/src/components/field-base-labels.ts +0 -20
  494. package/src/components/field-base.tsx +0 -656
  495. package/src/components/field-parts.tsx +0 -825
  496. package/src/components/field-strip.tsx +0 -129
  497. package/src/components/field.tsx +0 -206
  498. package/src/components/file-button.tsx +0 -295
  499. package/src/components/file-dropzone.tsx +0 -559
  500. package/src/components/floating-panel-labels.ts +0 -10
  501. package/src/components/floating-panel.tsx +0 -828
  502. package/src/components/focus-ring.ts +0 -16
  503. package/src/components/form-actions-labels.ts +0 -11
  504. package/src/components/form-actions.tsx +0 -578
  505. package/src/components/full-bleed-dialog.tsx +0 -294
  506. package/src/components/grouped-picker.tsx +0 -184
  507. package/src/components/help-popover.tsx +0 -117
  508. package/src/components/hover-menu.tsx +0 -343
  509. package/src/components/iban-input-labels.ts +0 -14
  510. package/src/components/iban-input.tsx +0 -236
  511. package/src/components/icon-button.tsx +0 -959
  512. package/src/components/icon-picker-labels.ts +0 -13
  513. package/src/components/icon-picker.tsx +0 -232
  514. package/src/components/image-grid-labels.ts +0 -11
  515. package/src/components/image-grid.tsx +0 -202
  516. package/src/components/inline-confirm.tsx +0 -116
  517. package/src/components/inline-edit-field-labels.ts +0 -11
  518. package/src/components/inline-edit-field.tsx +0 -552
  519. package/src/components/inline-entity-combobox.tsx +0 -849
  520. package/src/components/input.tsx +0 -394
  521. package/src/components/jump-calendar.tsx +0 -265
  522. package/src/components/language-select.tsx +0 -93
  523. package/src/components/legal-labels.ts +0 -105
  524. package/src/components/legal-page.tsx +0 -711
  525. package/src/components/legal.tsx +0 -668
  526. package/src/components/lightbox-labels.ts +0 -18
  527. package/src/components/lightbox.tsx +0 -399
  528. package/src/components/line-items-labels.ts +0 -14
  529. package/src/components/line-items.tsx +0 -602
  530. package/src/components/list-labels.ts +0 -12
  531. package/src/components/list.tsx +0 -870
  532. package/src/components/loading-state.tsx +0 -78
  533. package/src/components/measured-grid-labels.ts +0 -25
  534. package/src/components/measured-grid.tsx +0 -837
  535. package/src/components/menu-item.tsx +0 -241
  536. package/src/components/mini-calendar-labels.ts +0 -17
  537. package/src/components/mini-calendar.tsx +0 -789
  538. package/src/components/modal.tsx +0 -505
  539. package/src/components/money-draft.ts +0 -83
  540. package/src/components/money-field.tsx +0 -76
  541. package/src/components/month-grid.tsx +0 -229
  542. package/src/components/month-picker-labels.ts +0 -22
  543. package/src/components/month-picker.tsx +0 -1087
  544. package/src/components/multi-entity-combobox.tsx +0 -369
  545. package/src/components/multi-select.tsx +0 -376
  546. package/src/components/nav-pills.tsx +0 -198
  547. package/src/components/number-field.tsx +0 -327
  548. package/src/components/number-input.tsx +0 -558
  549. package/src/components/numeric-field.ts +0 -169
  550. package/src/components/numpad-sheet.tsx +0 -285
  551. package/src/components/one-time-code-input.tsx +0 -270
  552. package/src/components/page-contents-labels.ts +0 -9
  553. package/src/components/page-contents.tsx +0 -282
  554. package/src/components/page-header-context.ts +0 -8
  555. package/src/components/page-header.tsx +0 -298
  556. package/src/components/password-strength-labels.ts +0 -22
  557. package/src/components/password-strength.tsx +0 -241
  558. package/src/components/phone-input-labels.ts +0 -10
  559. package/src/components/phone-input.tsx +0 -282
  560. package/src/components/picker-sheet.tsx +0 -330
  561. package/src/components/pie-chart-labels.ts +0 -40
  562. package/src/components/pie-chart.tsx +0 -480
  563. package/src/components/popover-labels.ts +0 -14
  564. package/src/components/popover.tsx +0 -268
  565. package/src/components/progress-bar-labels.ts +0 -10
  566. package/src/components/progress-bar.tsx +0 -479
  567. package/src/components/qr-code.tsx +0 -76
  568. package/src/components/reauth-dialog-labels.ts +0 -13
  569. package/src/components/reauth-dialog.tsx +0 -265
  570. package/src/components/row-actions-labels.ts +0 -12
  571. package/src/components/row-actions.tsx +0 -795
  572. package/src/components/scroll-area.tsx +0 -123
  573. package/src/components/search-field.tsx +0 -192
  574. package/src/components/select.tsx +0 -294
  575. package/src/components/separator.tsx +0 -33
  576. package/src/components/series-chart-axes.ts +0 -342
  577. package/src/components/series-chart-budget.ts +0 -150
  578. package/src/components/series-chart-labels.ts +0 -33
  579. package/src/components/series-chart-thin.ts +0 -180
  580. package/src/components/series-chart-ticks.ts +0 -194
  581. package/src/components/series-chart.tsx +0 -1906
  582. package/src/components/server-wake-labels.ts +0 -11
  583. package/src/components/server-wake.tsx +0 -129
  584. package/src/components/settings-fields.tsx +0 -237
  585. package/src/components/share-card-labels.ts +0 -30
  586. package/src/components/share-card.tsx +0 -675
  587. package/src/components/signature-pad-labels.ts +0 -24
  588. package/src/components/signature-pad.tsx +0 -747
  589. package/src/components/signed-amount-labels.ts +0 -10
  590. package/src/components/signed-amount.tsx +0 -336
  591. package/src/components/skeleton.tsx +0 -80
  592. package/src/components/slider.tsx +0 -338
  593. package/src/components/sparkline-labels.ts +0 -14
  594. package/src/components/sparkline.tsx +0 -322
  595. package/src/components/spinner.tsx +0 -81
  596. package/src/components/stat-tile-labels.ts +0 -15
  597. package/src/components/stat-tile.tsx +0 -725
  598. package/src/components/status-dot.tsx +0 -170
  599. package/src/components/swatch-picker-labels.ts +0 -10
  600. package/src/components/swatch-picker.tsx +0 -283
  601. package/src/components/swipeable-row.tsx +0 -386
  602. package/src/components/switch.tsx +0 -273
  603. package/src/components/table-skeleton.tsx +0 -136
  604. package/src/components/table.tsx +0 -647
  605. package/src/components/tabs-labels.ts +0 -13
  606. package/src/components/tabs.tsx +0 -693
  607. package/src/components/text-link.tsx +0 -359
  608. package/src/components/text.tsx +0 -150
  609. package/src/components/textarea.tsx +0 -330
  610. package/src/components/tile-radio.tsx +0 -332
  611. package/src/components/time-input.tsx +0 -121
  612. package/src/components/toast-labels.ts +0 -12
  613. package/src/components/toast.tsx +0 -519
  614. package/src/components/toggle-group.tsx +0 -752
  615. package/src/components/toggle-legend.tsx +0 -394
  616. package/src/components/tooltip.tsx +0 -1149
  617. package/src/components/translation-review-editor.tsx +0 -290
  618. package/src/components/translation-review-labels.ts +0 -181
  619. package/src/components/translation-review-parts.tsx +0 -272
  620. package/src/components/translation-review.tsx +0 -1240
  621. package/src/components/tree-view.tsx +0 -589
  622. package/src/components/treemap-labels.ts +0 -47
  623. package/src/components/treemap-layout.ts +0 -210
  624. package/src/components/treemap.tsx +0 -1184
  625. package/src/components/trigger-aria.ts +0 -44
  626. package/src/components/use-anchor-dir.ts +0 -20
  627. package/src/components/use-mobile-reveal.ts +0 -62
  628. package/src/components/use-table-state.ts +0 -753
  629. package/src/components/user-avatar.tsx +0 -116
  630. package/src/components/wizard-stepper.tsx +0 -42
  631. package/src/components/write-lock-labels.ts +0 -10
  632. package/src/components/write-lock.tsx +0 -253
  633. package/src/currencies.ts +0 -70
  634. package/src/data-table.ts +0 -42
  635. package/src/demo/demo-banner.tsx +0 -153
  636. package/src/demo/demo-countdown.ts +0 -136
  637. package/src/demo/demo-ended.tsx +0 -90
  638. package/src/demo/demo-labels.ts +0 -78
  639. package/src/demo/demo-session.ts +0 -32
  640. package/src/demo/demo-start.tsx +0 -156
  641. package/src/eslint/index.ts +0 -161
  642. package/src/eslint/role-utilities.generated.ts +0 -146
  643. package/src/eslint/role-utilities.ts +0 -233
  644. package/src/feedback/feedback-attachment-download.tsx +0 -77
  645. package/src/feedback/feedback-attachment-labels.ts +0 -37
  646. package/src/feedback/feedback-attachment.tsx +0 -1239
  647. package/src/feedback/feedback-capture.ts +0 -287
  648. package/src/feedback/feedback-comment-thread.tsx +0 -353
  649. package/src/feedback/feedback-context-labels.ts +0 -10
  650. package/src/feedback/feedback-context.tsx +0 -213
  651. package/src/feedback/feedback-crash.ts +0 -547
  652. package/src/feedback/feedback-dialog-labels.ts +0 -29
  653. package/src/feedback/feedback-dialog.tsx +0 -440
  654. package/src/feedback/feedback-entry-file.tsx +0 -150
  655. package/src/feedback/feedback-inbox.tsx +0 -552
  656. package/src/feedback/feedback-labels.ts +0 -144
  657. package/src/feedback/feedback-menu-labels.ts +0 -12
  658. package/src/feedback/feedback-menu.tsx +0 -177
  659. package/src/feedback/feedback-mutation.ts +0 -20
  660. package/src/feedback/feedback-page-link.ts +0 -90
  661. package/src/feedback/feedback-public-labels.ts +0 -81
  662. package/src/feedback/feedback-public-list.tsx +0 -524
  663. package/src/feedback/feedback-public-switch.tsx +0 -102
  664. package/src/feedback/feedback-public-types.ts +0 -101
  665. package/src/feedback/feedback-record.ts +0 -423
  666. package/src/feedback/feedback-replies-context.ts +0 -9
  667. package/src/feedback/feedback-replies.tsx +0 -343
  668. package/src/feedback/feedback-rework-entry-chip.tsx +0 -18
  669. package/src/feedback/feedback-row-detail-labels.ts +0 -30
  670. package/src/feedback/feedback-row-detail.tsx +0 -858
  671. package/src/feedback/feedback-status-undo.ts +0 -109
  672. package/src/feedback/feedback-submit-dialog.tsx +0 -118
  673. package/src/feedback/feedback-submit.tsx +0 -237
  674. package/src/feedback/feedback-swipe.tsx +0 -192
  675. package/src/feedback/feedback-table-labels.ts +0 -27
  676. package/src/feedback/feedback-table.tsx +0 -912
  677. package/src/feedback/feedback-thread-labels.ts +0 -22
  678. package/src/feedback/feedback-thread-parts-labels.ts +0 -41
  679. package/src/feedback/feedback-thread.tsx +0 -905
  680. package/src/feedback/feedback-vocabulary.ts +0 -263
  681. package/src/feedback/feedback-vote-button.tsx +0 -124
  682. package/src/feedback.ts +0 -116
  683. package/src/hooks/use-anchored-panel.ts +0 -396
  684. package/src/hooks/use-anchored-rect.ts +0 -77
  685. package/src/hooks/use-announce.ts +0 -85
  686. package/src/hooks/use-authed-src.ts +0 -167
  687. package/src/hooks/use-body-scroll-lock.ts +0 -119
  688. package/src/hooks/use-breakpoint.ts +0 -133
  689. package/src/hooks/use-close-transition.ts +0 -107
  690. package/src/hooks/use-copy-to-clipboard.ts +0 -122
  691. package/src/hooks/use-debounce.ts +0 -114
  692. package/src/hooks/use-dismiss.ts +0 -371
  693. package/src/hooks/use-document-title.ts +0 -33
  694. package/src/hooks/use-file-drop.ts +0 -121
  695. package/src/hooks/use-file-picker-labels.ts +0 -28
  696. package/src/hooks/use-file-picker.tsx +0 -359
  697. package/src/hooks/use-focus-trap.ts +0 -199
  698. package/src/hooks/use-hotkey.ts +0 -189
  699. package/src/hooks/use-large-text.ts +0 -41
  700. package/src/hooks/use-long-press.ts +0 -140
  701. package/src/hooks/use-media-query.ts +0 -39
  702. package/src/hooks/use-mounted.ts +0 -39
  703. package/src/hooks/use-noindex.ts +0 -37
  704. package/src/hooks/use-overlay-history.ts +0 -714
  705. package/src/hooks/use-row-swipe.ts +0 -207
  706. package/src/hooks/use-search-param-state.ts +0 -434
  707. package/src/hooks/use-windowed-rows.ts +0 -225
  708. package/src/i18n/defaults.ts +0 -267
  709. package/src/i18n/german.ts +0 -2057
  710. package/src/i18n/kit-labels.tsx +0 -1003
  711. package/src/i18n/languages.ts +0 -349
  712. package/src/i18n/locales/de-CH.ts +0 -24
  713. package/src/i18n/locales/en.ts +0 -267
  714. package/src/i18n/locales/es.ts +0 -1997
  715. package/src/i18n/locales/fr.ts +0 -2035
  716. package/src/i18n/locales/hu.ts +0 -2002
  717. package/src/i18n/locales/it.ts +0 -2012
  718. package/src/i18n/locales/zh.ts +0 -1910
  719. package/src/i18n/plurals.ts +0 -86
  720. package/src/i18n/review.ts +0 -475
  721. package/src/i18n/shared-labels.ts +0 -138
  722. package/src/i18n/swiss.ts +0 -29
  723. package/src/index.ts +0 -1179
  724. package/src/landing/access.ts +0 -93
  725. package/src/landing/landing-actions.tsx +0 -97
  726. package/src/landing/landing-labels.ts +0 -59
  727. package/src/landing/landing-sections.tsx +0 -341
  728. package/src/landing/page-seo.ts +0 -261
  729. package/src/landing/public-header.tsx +0 -121
  730. package/src/landing/public-page.tsx +0 -106
  731. package/src/landing/routing.tsx +0 -225
  732. package/src/lib/broadcast.ts +0 -52
  733. package/src/lib/calc.ts +0 -323
  734. package/src/lib/chip-edge-snap.ts +0 -212
  735. package/src/lib/clipboard-minus.ts +0 -107
  736. package/src/lib/clipping.ts +0 -34
  737. package/src/lib/cn.ts +0 -28
  738. package/src/lib/column-mapping.ts +0 -234
  739. package/src/lib/countries.ts +0 -91
  740. package/src/lib/data-attributes.ts +0 -14
  741. package/src/lib/date-format.ts +0 -227
  742. package/src/lib/dates.ts +0 -266
  743. package/src/lib/day-format.ts +0 -96
  744. package/src/lib/decimal-marks.ts +0 -108
  745. package/src/lib/describe-failure.ts +0 -18
  746. package/src/lib/device-settings.ts +0 -147
  747. package/src/lib/direction.ts +0 -74
  748. package/src/lib/document-navigation.ts +0 -17
  749. package/src/lib/document-title.ts +0 -41
  750. package/src/lib/download.ts +0 -172
  751. package/src/lib/float-clearance.ts +0 -70
  752. package/src/lib/floating-stack.ts +0 -70
  753. package/src/lib/focus-left.ts +0 -156
  754. package/src/lib/format.ts +0 -522
  755. package/src/lib/http-status.ts +0 -38
  756. package/src/lib/iban.ts +0 -251
  757. package/src/lib/ime.ts +0 -24
  758. package/src/lib/intl-cache.ts +0 -25
  759. package/src/lib/iso-day.ts +0 -13
  760. package/src/lib/lazy-with-fallback.ts +0 -82
  761. package/src/lib/logger.ts +0 -69
  762. package/src/lib/minus.ts +0 -50
  763. package/src/lib/path-pattern.ts +0 -55
  764. package/src/lib/person-name.ts +0 -155
  765. package/src/lib/phone.ts +0 -208
  766. package/src/lib/plain-text.ts +0 -22
  767. package/src/lib/presence.ts +0 -18
  768. package/src/lib/qr-encode.ts +0 -444
  769. package/src/lib/quiet-focus.ts +0 -32
  770. package/src/lib/reduced-motion.ts +0 -22
  771. package/src/lib/refs.ts +0 -22
  772. package/src/lib/round.ts +0 -43
  773. package/src/lib/safe-storage.ts +0 -119
  774. package/src/lib/server-wake.ts +0 -315
  775. package/src/lib/session-refresh.ts +0 -472
  776. package/src/lib/share-format.ts +0 -18
  777. package/src/lib/status-tone.ts +0 -43
  778. package/src/lib/strip-fade.ts +0 -287
  779. package/src/lib/table-text.ts +0 -554
  780. package/src/lib/text-case.ts +0 -19
  781. package/src/lib/text-match.ts +0 -169
  782. package/src/lib/thenable.ts +0 -19
  783. package/src/lib/translation-review.ts +0 -717
  784. package/src/notifications/device-zone-report.ts +0 -67
  785. package/src/notifications/feedback-notification-types-labels.ts +0 -33
  786. package/src/notifications/feedback-notification-types.ts +0 -71
  787. package/src/notifications/notification-bell.tsx +0 -119
  788. package/src/notifications/notification-inbox.tsx +0 -309
  789. package/src/notifications/notification-parts.tsx +0 -81
  790. package/src/notifications/notification-types-setting.tsx +0 -406
  791. package/src/notifications/notification-types.ts +0 -89
  792. package/src/notifications/notifications-labels.ts +0 -287
  793. package/src/notifications/push-device-setting.tsx +0 -230
  794. package/src/notifications/push-devices-setting.tsx +0 -374
  795. package/src/notifications/push-worker.ts +0 -168
  796. package/src/notifications/quiet-hours-setting.tsx +0 -238
  797. package/src/notifications/unsubscribe-panel.tsx +0 -200
  798. package/src/notifications/use-push-device.ts +0 -448
  799. package/src/offline/local-first.ts +0 -100
  800. package/src/offline/pwa-update-prompt-labels.ts +0 -9
  801. package/src/offline/pwa-update-prompt.tsx +0 -78
  802. package/src/offline/query-persistence.ts +0 -391
  803. package/src/offline/service-worker-updates.ts +0 -81
  804. package/src/offline/shared-computer-labels.ts +0 -8
  805. package/src/offline/shared-computer.tsx +0 -87
  806. package/src/offline/sign-out-pending-dialog-labels.ts +0 -21
  807. package/src/offline/sign-out-pending-dialog.tsx +0 -135
  808. package/src/offline.ts +0 -37
  809. package/src/push-worker.ts +0 -25
  810. package/src/rhf/fields.tsx +0 -2187
  811. package/src/rhf/form-core.tsx +0 -324
  812. package/src/rhf/form.tsx +0 -12
  813. package/src/rhf/line-items.tsx +0 -135
  814. package/src/rhf/use-rhf-wizard-step.ts +0 -104
  815. package/src/rhf.ts +0 -21
  816. package/src/search/command-palette-labels.ts +0 -16
  817. package/src/search/command-palette.tsx +0 -624
  818. package/src/search/global-search-labels.ts +0 -13
  819. package/src/search/global-search.tsx +0 -660
  820. package/src/search/search-index.ts +0 -283
  821. package/src/search/use-command-key.ts +0 -23
  822. package/src/search.ts +0 -14
  823. package/src/settings/device-reset-setting-labels.ts +0 -20
  824. package/src/settings/device-reset-setting.tsx +0 -116
  825. package/src/settings/settings-card-classes.ts +0 -11
  826. package/src/settings/settings-catalogue.ts +0 -150
  827. package/src/settings/settings-context.ts +0 -33
  828. package/src/settings/settings-heading.tsx +0 -67
  829. package/src/settings/settings-labels.ts +0 -87
  830. package/src/settings/settings-layout.tsx +0 -471
  831. package/src/settings/settings-section.tsx +0 -108
  832. package/src/settings/use-account-appearance.ts +0 -148
  833. package/src/settings/use-account-language.ts +0 -146
  834. package/src/settings/use-settings-focus.ts +0 -128
  835. package/src/settings/use-settings-route.ts +0 -258
  836. package/src/shell/account-menu-language.tsx +0 -130
  837. package/src/shell/account-menu-text-size.tsx +0 -80
  838. package/src/shell/admin-menu.tsx +0 -183
  839. package/src/shell/app-shell-labels.ts +0 -11
  840. package/src/shell/app-shell.tsx +0 -1560
  841. package/src/shell/auth-layout.tsx +0 -186
  842. package/src/shell/company-switcher-labels.ts +0 -13
  843. package/src/shell/company-switcher.tsx +0 -136
  844. package/src/shell/dev-menu.tsx +0 -257
  845. package/src/shell/fill-height.tsx +0 -45
  846. package/src/shell/language-code.ts +0 -5
  847. package/src/shell/option-switcher-menu.tsx +0 -84
  848. package/src/shell/sync-status-indicator.tsx +0 -303
  849. package/src/shell/top-bar-actions.tsx +0 -87
  850. package/src/shell/top-bar-brand.tsx +0 -78
  851. package/src/shell/top-bar-parts-labels.ts +0 -148
  852. package/src/shell/top-bar.tsx +0 -40
  853. package/src/shell/topbar-action-menu.tsx +0 -567
  854. package/src/shell/topbar-controls.tsx +0 -269
  855. package/src/shell/tour-menu.tsx +0 -187
  856. package/src/shell.ts +0 -108
  857. package/src/sync/db.ts +0 -129
  858. package/src/sync/engine.ts +0 -1011
  859. package/src/sync/hooks.ts +0 -98
  860. package/src/sync/testing.ts +0 -73
  861. package/src/sync/wire.ts +0 -61
  862. package/src/sync.ts +0 -37
  863. package/src/table-text.ts +0 -8
  864. package/src/testing/css-variables.generated.ts +0 -112
  865. package/src/testing/css-variables.ts +0 -189
  866. package/src/testing/index.ts +0 -29
  867. package/src/testing/react.ts +0 -58
  868. package/src/testing/tour.ts +0 -41
  869. package/src/testing/viewport.ts +0 -357
  870. package/src/theme/chart-palette.ts +0 -232
  871. package/src/theme/color.ts +0 -272
  872. package/src/theme/contrast-tokens.ts +0 -268
  873. package/src/theme/contrast.ts +0 -250
  874. package/src/theme/palette-derive.ts +0 -825
  875. package/src/theme/palette-presets.ts +0 -544
  876. package/src/theme/palette-store.ts +0 -108
  877. package/src/theme/text-size.ts +0 -289
  878. package/src/theme/theme-store.ts +0 -157
  879. package/src/tour/tour-catalogue-labels.ts +0 -75
  880. package/src/tour/tour-catalogue.tsx +0 -335
  881. package/src/tour/tour-dock.ts +0 -134
  882. package/src/tour/tour-engine.ts +0 -37
  883. package/src/tour/tour-gates.ts +0 -218
  884. package/src/tour/tour-guide-button.tsx +0 -64
  885. package/src/tour/tour-guide-labels.ts +0 -15
  886. package/src/tour/tour-info.ts +0 -144
  887. package/src/tour/tour-labels.ts +0 -23
  888. package/src/tour/tour-overlay.tsx +0 -1170
  889. package/src/tour/tour.tsx +0 -750
  890. package/src/tour/use-tour-progress.ts +0 -671
  891. package/src/tour/use-tour-url-sync.ts +0 -266
  892. package/src/tour.ts +0 -21
  893. package/src/vite-env.d.ts +0 -23
  894. package/src/wizard/stepper-nav.tsx +0 -360
  895. package/src/wizard/types.ts +0 -275
  896. package/src/wizard/use-wizard.ts +0 -560
  897. package/src/wizard/validation.ts +0 -39
  898. package/src/wizard/wizard-context.tsx +0 -74
  899. package/src/wizard/wizard-labels.ts +0 -29
  900. package/src/wizard/wizard-step.tsx +0 -76
  901. package/src/wizard/wizard-summary.tsx +0 -84
  902. package/src/wizard.ts +0 -15
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/shell/dev-menu.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { FlaskConical, Undo2 } from \"lucide-react\";\nimport { DEFAULT_TOP_BAR_LABELS, useKitLabels, type LabelOverride } from \"../i18n/kit-labels\";\nimport { TopBarActionMenu, type TopBarMenuEntry } from \"./topbar-action-menu\";\nimport { useTopBarPartsLabels, type DevSwitchersLabels } from \"./top-bar-parts-labels\";\n// The kit's one storage guard, which 0.51 wrote here again by hand (DUP-8, refactor audit\n// 2026-10-30). Storage blocked, \"Back to\" lasts as long as this page.\nimport { readSessionStored, writeSessionStored } from \"../lib/safe-storage\";\n\n/** Where the role the session started with is kept, unless the app names its own key. */\nexport const DEV_ORIGINAL_ROLE_KEY = \"ui-kit:dev-original-role\";\n\n/**\n * One of the app's own groups in the {@link DevMenu} (docs/top-bar-harmonization.md\n * §2.7.1): keksdose's budget role, its \"today\" override, its resets.\n *\n * The rows are plain {@link TopBarMenuEntry}s, and there is no dev item type beside them\n * (the contract with keksdose, 0.51): a choice of one is radio rows (`checked`), a toggle\n * is a `checkable: \"checkbox\"` row, an action is a plain row. No form fields — a menu is\n * a `role=\"menu\"`, where a text or date input has no place in the arrow keys; a free\n * value is a \"Custom…\" row that opens the app's own dialog.\n */\nexport interface DevMenuSection {\n /** Unique among the sections. The rows' keys need be unique only within theirs: the\n * menu prefixes them with this. */\n key: string;\n /** The group's heading, drawn above its rows and announced as the group's name. */\n title: ReactNode;\n /** The rows, as `TopBarActionMenu` takes them. A `divider` ends the group, as there. */\n entries: readonly TopBarMenuEntry[];\n}\n\nexport interface DevMenuProps<R extends string, P extends string> {\n /**\n * Draw anything at all. The app passes `import.meta.env.DEV` AND its probe of the dev\n * route (§2.7.1): the constant keeps the menu off a production build's screen, the\n * probe off a dev build whose server has the route shut (§6.1) — flasks with nothing\n * behind them. Neither takes code out of the bundle; see `sections`.\n */\n enabled: boolean;\n roles?: readonly R[];\n /** The role the server enforces now. Named `currentRole`, not `role`, because `role` on\n * a JSX element is the ARIA role (as `RoleChip`'s `value`). */\n currentRole?: R;\n /** Apply a role — the app calls server-kit's `POST /dev/switch {role}` and re-reads\n * `/auth/me` (§2.7, §6.2). */\n onRole?: (role: R) => void;\n plans?: readonly P[];\n /** The plan the server enforces now. Left out (not known yet — kastlan's overview is a\n * 404 while billing is off), the plan group still renders, with no row checked; a\n * `GET /dev/switch` answers it. */\n currentPlan?: P;\n /** Apply a plan — `POST /dev/switch {plan}`, then the overview (§6.3). */\n onPlan?: (plan: P) => void;\n /**\n * The role to offer \"Back to {role}\" for. Left out, the kit keeps it itself: the role\n * before the first switch of this browser session, in `sessionStorage` under\n * `storageKey` — the server answers with the NEW role after a switch, so only the\n * browser still knows the old one.\n */\n originalRole?: R;\n /** The `sessionStorage` key for the kept role. Default {@link DEV_ORIGINAL_ROLE_KEY};\n * an app with several accounts on one browser may scope it per user. */\n storageKey?: string;\n /** Billing is off locally: the plan group says plans change nothing, and stays usable\n * (§6.3). */\n billingOff?: boolean;\n /** A role's or a plan's words in the menu. Default: the value itself. */\n roleLabel?: (role: R) => ReactNode;\n planLabel?: (plan: P) => ReactNode;\n /** Prop > `<UiKitProvider labels={{ topBarParts: { dev } }}>` > English. */\n labels?: LabelOverride<DevSwitchersLabels>;\n /** 0.51: the app's own dev groups, after the kit's role and plan, in this order. Their\n * DEFINITIONS belong in a module only a dev build imports (§2.7.1): `enabled={false}`\n * hides the menu, but the code of a section built inline stays in the bundle. */\n sections?: readonly DevMenuSection[];\n /**\n * 0.51: an override is on that the developer could forget — a \"today\" that is not\n * today, a simulated slow server. The flask wears a dot, and its name says so (\"Dev\n * tools · Role: ADMIN · Plan: free Overrides on\"), since a dot alone tells a screen\n * reader nothing. The app decides what counts; the kit cannot see into its sections.\n */\n active?: boolean;\n}\n\nconst flask = <FlaskConical className=\"size-5 text-warning\" />;\n\n/** A label's text for a sentence, or the raw value when the label is not text. */\nconst textOf = (label: ReactNode, value: string) =>\n typeof label === \"string\" || typeof label === \"number\" ? String(label) : value;\n\n/** A row someone can pick — what makes a menu worth drawing. */\nconst isRow = (entry: TopBarMenuEntry) => entry.kind !== \"heading\" && entry.kind !== \"divider\";\n\n/**\n * The top bar's local-dev menu (docs/top-bar-harmonization.md §2.7, §2.7.1, §6.2, §6.3):\n * ONE amber flask over the kit's Account section and the app's own.\n *\n * 0.42's `DevSwitchers` drew a flask per switcher and had no place for anything else,\n * and an app has more dev controls than a role and a plan — keksdose's budget role, a\n * \"today\" override, resets (agreed with keksdose for 0.51). So the kit's part is now the\n * menu, and the app's rows go in it.\n *\n * - **The Account section** comes first: the role group, ending in \"Back to {role}\" once\n * the role differs from the one the session started with (the dev route needs only a\n * signed-in user, so a switch to a lesser role can always switch back); then the plan\n * group, which with `billingOff` notes that plans change nothing. Each renders only\n * with its list and handler.\n * - **The app's `sections`** follow, each a group under its own heading.\n * - **`active`** puts a dot on the flask for an override that is easy to forget.\n *\n * Nothing renders unless `enabled`, and nothing with no row to show. The panel scrolls\n * (`TopBarActionMenu`'s `scrollable`): some 25 rows ran off a 390×844 phone.\n *\n * From `md` up the groups stand side by side, one column each, in that order (0.51.4,\n * `TopBarActionMenu`'s `columns`; keksdose dev #640, Marcel: \"Each group gets a column\").\n * \"Back to {role}\" stays under the roles. A desktop too narrow for all of them wraps\n * the columns onto a second row; a phone keeps the one scrolling column.\n */\nexport function DevMenu<R extends string, P extends string>({\n enabled,\n roles,\n currentRole: role,\n onRole,\n plans,\n currentPlan: plan,\n onPlan,\n originalRole,\n storageKey = DEV_ORIGINAL_ROLE_KEY,\n billingOff = false,\n roleLabel = (r) => r,\n planLabel = (p) => p,\n labels: labelsProp,\n sections = [],\n active = false,\n}: DevMenuProps<R, P>) {\n const bar = useKitLabels(\"topBar\", DEFAULT_TOP_BAR_LABELS);\n const words = useTopBarPartsLabels(labelsProp ? { dev: labelsProp } : undefined).dev;\n const [kept, setKept] = useState<string | null>(() => (enabled ? readSessionStored(storageKey) : null));\n // 0.43 (keksdose): `enabled` may start false and turn true — an app's GET probe of the\n // dev route — so the kept role is read again then, or \"Back to {role}\" is lost after a\n // reload. Adjusted during render, React's pattern for state that follows a prop.\n const [wasEnabled, setWasEnabled] = useState(enabled);\n if (wasEnabled !== enabled) {\n setWasEnabled(enabled);\n if (enabled) setKept(readSessionStored(storageKey));\n }\n if (!enabled) return null;\n\n const original = (originalRole ?? kept ?? undefined) as R | undefined;\n\n const pickRole = (next: R) => {\n if (originalRole === undefined) {\n if (next === original) {\n // Back where the session began: nothing left to go back to.\n writeSessionStored(storageKey, null);\n setKept(null);\n } else if (kept === null && role !== undefined && next !== role) {\n writeSessionStored(storageKey, role);\n setKept(role);\n }\n }\n onRole?.(next);\n };\n\n const roleGroup: TopBarMenuEntry[] =\n roles && roles.length > 0 && role !== undefined && onRole\n ? [\n { kind: \"heading\", key: \"head-role\", label: words.roleHeading },\n ...roles.map((r): TopBarMenuEntry => ({\n key: `role-${r}`,\n label: roleLabel(r),\n checked: r === role,\n onSelect: () => pickRole(r),\n })),\n // Behind a rule, which ends the role group: \"Back to\" is a way out, not a role.\n ...(original !== undefined && original !== role\n ? ([\n { kind: \"divider\", key: \"sep-back\" },\n {\n key: \"back\",\n icon: <Undo2 className=\"size-4\" />,\n label: words.backTo(textOf(roleLabel(original), original)),\n onSelect: () => pickRole(original),\n },\n ] satisfies TopBarMenuEntry[])\n : []),\n ]\n : [];\n\n const planGroup: TopBarMenuEntry[] =\n plans && plans.length > 0 && onPlan\n ? [\n {\n kind: \"heading\",\n key: \"head-plan\",\n // The billing-off note is part of the group's NAME, under the heading. In 0.42's\n // own plan menu it was a trailing `<li>`: not a row, so the arrow keys passed\n // over it and a screen reader moving through the menu never reached it. Named,\n // it is read on the way into the plans. And in one menu a trailing note would\n // sit under the app's sections, not under the plans.\n label: billingOff ? (\n <>\n {words.planHeading}{\" \"}\n <span\n data-dev-billing-off=\"\"\n className=\"mt-0.5 block font-normal normal-case tracking-normal text-xs text-muted\"\n >\n {words.billingOff}\n </span>\n </>\n ) : (\n words.planHeading\n ),\n },\n ...plans.map((p): TopBarMenuEntry => ({\n key: `plan-${p}`,\n label: planLabel(p),\n checked: p === plan,\n onSelect: () => onPlan(p),\n })),\n ]\n : [];\n\n // The app's rows keyed under their section: two sections may well both have a \"reset\".\n const appGroups = sections.flatMap((section): TopBarMenuEntry[] => [\n { kind: \"heading\", key: `section:${section.key}`, label: section.title },\n ...section.entries.map((entry): TopBarMenuEntry => ({ ...entry, key: `${section.key}:${entry.key}` })),\n ]);\n\n const entries = [...roleGroup, ...planGroup, ...appGroups];\n if (!entries.some(isRow)) return null;\n\n // The menu, then the role and the plan, as 0.42's two triggers named them: the bar\n // shows the flask only, so its name (and tooltip) is where they are read.\n const name = [\n words.menu,\n roleGroup.length > 0 && role !== undefined ? bar.role(role) : null,\n planGroup.length > 0 && plan !== undefined ? words.plan(plan) : null,\n ]\n .filter((part): part is string => part !== null)\n .join(\" · \");\n\n return (\n <TopBarActionMenu\n icon={flask}\n // The badge's words join the trigger's name, as the tours compass's NEW dot does.\n // `danger`, the badge's default: an amber dot would vanish into the amber flask.\n iconBadge={active ? { label: words.overridesOn } : null}\n aria-label={name}\n entries={entries}\n scrollable\n columns\n />\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/shell/dev-menu.tsx"],"sourcesContent":["import { useState, useSyncExternalStore } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { FlaskConical, Undo2 } from \"lucide-react\";\nimport { DEFAULT_TOP_BAR_LABELS, useKitLabels, type LabelOverride } from \"../i18n/kit-labels\";\nimport { TopBarActionMenu, type TopBarMenuEntry } from \"./topbar-action-menu\";\nimport { useTopBarPartsLabels, type DevSwitchersLabels } from \"./top-bar-parts-labels\";\n// The kit's one storage guard, which 0.51 wrote here again by hand (DUP-8, refactor audit\n// 2026-10-30). Storage blocked, \"Back to\" lasts as long as this page.\nimport { readSessionStored, writeSessionStored } from \"../lib/safe-storage\";\n// The kit's one \"today\" (0.55): a day moved there is an override on, whatever `active` says.\nimport { subscribeToday, todayOverride } from \"../lib/dates\";\n\n/** Where the role the session started with is kept, unless the app names its own key. */\nexport const DEV_ORIGINAL_ROLE_KEY = \"ui-kit:dev-original-role\";\n\n/**\n * One of the app's own groups in the {@link DevMenu} (docs/top-bar-harmonization.md\n * §2.7.1): keksdose's budget role, its \"today\" override, its resets.\n *\n * The rows are plain {@link TopBarMenuEntry}s, and there is no dev item type beside them\n * (the contract with keksdose, 0.51): a choice of one is radio rows (`checked`), a toggle\n * is a `checkable: \"checkbox\"` row, an action is a plain row. No form fields — a menu is\n * a `role=\"menu\"`, where a text or date input has no place in the arrow keys; a free\n * value is a \"Custom…\" row that opens the app's own dialog.\n */\nexport interface DevMenuSection {\n /** Unique among the sections. The rows' keys need be unique only within theirs: the\n * menu prefixes them with this. */\n key: string;\n /** The group's heading, drawn above its rows and announced as the group's name. */\n title: ReactNode;\n /** The rows, as `TopBarActionMenu` takes them. A `divider` ends the group, as there. */\n entries: readonly TopBarMenuEntry[];\n}\n\nexport interface DevMenuProps<R extends string, P extends string> {\n /**\n * Draw anything at all. The app passes `import.meta.env.DEV` AND its probe of the dev\n * route (§2.7.1): the constant keeps the menu off a production build's screen, the\n * probe off a dev build whose server has the route shut (§6.1) — flasks with nothing\n * behind them. Neither takes code out of the bundle; see `sections`.\n */\n enabled: boolean;\n roles?: readonly R[];\n /** The role the server enforces now. Named `currentRole`, not `role`, because `role` on\n * a JSX element is the ARIA role (as `RoleChip`'s `value`). */\n currentRole?: R;\n /** Apply a role — the app calls server-kit's `POST /dev/switch {role}` and re-reads\n * `/auth/me` (§2.7, §6.2). */\n onRole?: (role: R) => void;\n plans?: readonly P[];\n /** The plan the server enforces now. Left out (not known yet — kastlan's overview is a\n * 404 while billing is off), the plan group still renders, with no row checked; a\n * `GET /dev/switch` answers it. */\n currentPlan?: P;\n /** Apply a plan — `POST /dev/switch {plan}`, then the overview (§6.3). */\n onPlan?: (plan: P) => void;\n /**\n * The role to offer \"Back to {role}\" for. Left out, the kit keeps it itself: the role\n * before the first switch of this browser session, in `sessionStorage` under\n * `storageKey` — the server answers with the NEW role after a switch, so only the\n * browser still knows the old one.\n */\n originalRole?: R;\n /** The `sessionStorage` key for the kept role. Default {@link DEV_ORIGINAL_ROLE_KEY};\n * an app with several accounts on one browser may scope it per user. */\n storageKey?: string;\n /** Billing is off locally: the plan group says plans change nothing, and stays usable\n * (§6.3). */\n billingOff?: boolean;\n /** A role's or a plan's words in the menu. Default: the value itself. */\n roleLabel?: (role: R) => ReactNode;\n planLabel?: (plan: P) => ReactNode;\n /** Prop > `<UiKitProvider labels={{ topBarParts: { dev } }}>` > English. */\n labels?: LabelOverride<DevSwitchersLabels>;\n /** 0.51: the app's own dev groups, after the kit's role and plan, in this order. Their\n * DEFINITIONS belong in a module only a dev build imports (§2.7.1): `enabled={false}`\n * hides the menu, but the code of a section built inline stays in the bundle. */\n sections?: readonly DevMenuSection[];\n /**\n * 0.51: an override is on that the developer could forget — a \"today\" that is not\n * today, a simulated slow server. The flask wears a dot, and its name says so (\"Dev\n * tools · Role: ADMIN · Plan: free Overrides on\"), since a dot alone tells a screen\n * reader nothing. The app decides what counts; the kit cannot see into its sections.\n *\n * 0.55: a \"today\" the app moved with `setToday` (`@eifi1/ui-kit/dates`, the dev clock's\n * day) counts by itself — the dot is on while one is set, `active` or not.\n */\n active?: boolean;\n}\n\nconst flask = <FlaskConical className=\"size-5 text-warning\" />;\n\n/** A label's text for a sentence, or the raw value when the label is not text. */\nconst textOf = (label: ReactNode, value: string) =>\n typeof label === \"string\" || typeof label === \"number\" ? String(label) : value;\n\n/** A row someone can pick — what makes a menu worth drawing. */\nconst isRow = (entry: TopBarMenuEntry) => entry.kind !== \"heading\" && entry.kind !== \"divider\";\n\n/**\n * The top bar's local-dev menu (docs/top-bar-harmonization.md §2.7, §2.7.1, §6.2, §6.3):\n * ONE amber flask over the kit's Account section and the app's own.\n *\n * 0.42's `DevSwitchers` drew a flask per switcher and had no place for anything else,\n * and an app has more dev controls than a role and a plan — keksdose's budget role, a\n * \"today\" override, resets (agreed with keksdose for 0.51). So the kit's part is now the\n * menu, and the app's rows go in it.\n *\n * - **The Account section** comes first: the role group, ending in \"Back to {role}\" once\n * the role differs from the one the session started with (the dev route needs only a\n * signed-in user, so a switch to a lesser role can always switch back); then the plan\n * group, which with `billingOff` notes that plans change nothing. Each renders only\n * with its list and handler.\n * - **The app's `sections`** follow, each a group under its own heading.\n * - **`active`** puts a dot on the flask for an override that is easy to forget.\n *\n * Nothing renders unless `enabled`, and nothing with no row to show. The panel scrolls\n * (`TopBarActionMenu`'s `scrollable`): some 25 rows ran off a 390×844 phone.\n *\n * From `md` up the groups stand side by side, one column each, in that order (0.51.4,\n * `TopBarActionMenu`'s `columns`; keksdose dev #640, Marcel: \"Each group gets a column\").\n * \"Back to {role}\" stays under the roles. A desktop too narrow for all of them wraps\n * the columns onto a second row; a phone keeps the one scrolling column.\n */\nexport function DevMenu<R extends string, P extends string>({\n enabled,\n roles,\n currentRole: role,\n onRole,\n plans,\n currentPlan: plan,\n onPlan,\n originalRole,\n storageKey = DEV_ORIGINAL_ROLE_KEY,\n billingOff = false,\n roleLabel = (r) => r,\n planLabel = (p) => p,\n labels: labelsProp,\n sections = [],\n active = false,\n}: DevMenuProps<R, P>) {\n const bar = useKitLabels(\"topBar\", DEFAULT_TOP_BAR_LABELS);\n // Server render: no day is moved there.\n const movedToday = useSyncExternalStore(subscribeToday, todayOverride, () => null);\n const words = useTopBarPartsLabels(labelsProp ? { dev: labelsProp } : undefined).dev;\n const [kept, setKept] = useState<string | null>(() => (enabled ? readSessionStored(storageKey) : null));\n // 0.43 (keksdose): `enabled` may start false and turn true — an app's GET probe of the\n // dev route — so the kept role is read again then, or \"Back to {role}\" is lost after a\n // reload. Adjusted during render, React's pattern for state that follows a prop.\n const [wasEnabled, setWasEnabled] = useState(enabled);\n if (wasEnabled !== enabled) {\n setWasEnabled(enabled);\n if (enabled) setKept(readSessionStored(storageKey));\n }\n if (!enabled) return null;\n\n const original = (originalRole ?? kept ?? undefined) as R | undefined;\n\n const pickRole = (next: R) => {\n if (originalRole === undefined) {\n if (next === original) {\n // Back where the session began: nothing left to go back to.\n writeSessionStored(storageKey, null);\n setKept(null);\n } else if (kept === null && role !== undefined && next !== role) {\n writeSessionStored(storageKey, role);\n setKept(role);\n }\n }\n onRole?.(next);\n };\n\n const roleGroup: TopBarMenuEntry[] =\n roles && roles.length > 0 && role !== undefined && onRole\n ? [\n { kind: \"heading\", key: \"head-role\", label: words.roleHeading },\n ...roles.map((r): TopBarMenuEntry => ({\n key: `role-${r}`,\n label: roleLabel(r),\n checked: r === role,\n onSelect: () => pickRole(r),\n })),\n // Behind a rule, which ends the role group: \"Back to\" is a way out, not a role.\n ...(original !== undefined && original !== role\n ? ([\n { kind: \"divider\", key: \"sep-back\" },\n {\n key: \"back\",\n icon: <Undo2 className=\"size-4\" />,\n label: words.backTo(textOf(roleLabel(original), original)),\n onSelect: () => pickRole(original),\n },\n ] satisfies TopBarMenuEntry[])\n : []),\n ]\n : [];\n\n const planGroup: TopBarMenuEntry[] =\n plans && plans.length > 0 && onPlan\n ? [\n {\n kind: \"heading\",\n key: \"head-plan\",\n // The billing-off note is part of the group's NAME, under the heading. In 0.42's\n // own plan menu it was a trailing `<li>`: not a row, so the arrow keys passed\n // over it and a screen reader moving through the menu never reached it. Named,\n // it is read on the way into the plans. And in one menu a trailing note would\n // sit under the app's sections, not under the plans.\n label: billingOff ? (\n <>\n {words.planHeading}{\" \"}\n <span\n data-dev-billing-off=\"\"\n className=\"mt-0.5 block font-normal normal-case tracking-normal text-xs text-muted\"\n >\n {words.billingOff}\n </span>\n </>\n ) : (\n words.planHeading\n ),\n },\n ...plans.map((p): TopBarMenuEntry => ({\n key: `plan-${p}`,\n label: planLabel(p),\n checked: p === plan,\n onSelect: () => onPlan(p),\n })),\n ]\n : [];\n\n // The app's rows keyed under their section: two sections may well both have a \"reset\".\n const appGroups = sections.flatMap((section): TopBarMenuEntry[] => [\n { kind: \"heading\", key: `section:${section.key}`, label: section.title },\n ...section.entries.map((entry): TopBarMenuEntry => ({ ...entry, key: `${section.key}:${entry.key}` })),\n ]);\n\n const entries = [...roleGroup, ...planGroup, ...appGroups];\n if (!entries.some(isRow)) return null;\n\n // The menu, then the role and the plan, as 0.42's two triggers named them: the bar\n // shows the flask only, so its name (and tooltip) is where they are read.\n const name = [\n words.menu,\n roleGroup.length > 0 && role !== undefined ? bar.role(role) : null,\n planGroup.length > 0 && plan !== undefined ? words.plan(plan) : null,\n ]\n .filter((part): part is string => part !== null)\n .join(\" · \");\n\n return (\n <TopBarActionMenu\n icon={flask}\n // The badge's words join the trigger's name, as the tours compass's NEW dot does.\n // `danger`, the badge's default: an amber dot would vanish into the amber flask.\n iconBadge={active || movedToday !== null ? { label: words.overridesOn } : null}\n aria-label={name}\n entries={entries}\n scrollable\n columns\n />\n );\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * `HeldChangesBanner`'s words — the `heldChanges` namespace of `<UiKitProvider labels>`
3
+ * (0.55, docs/top-bar-harmonization.md §2.6.1). The confirm's Cancel is
4
+ * `confirmDialog.cancel`.
5
+ */
6
+ export interface HeldChangesBannerLabels {
7
+ /** The banner's sentence when the app passes none: how many wait, that they stay on the
8
+ * device, and the two ways out. Says nothing of a way back, which the app may not have. */
9
+ message: (count: number) => string;
10
+ /** The way back — keksdose's switch to the budget the changes were made in. */
11
+ switchBack: string;
12
+ /** The button that asks first; the ellipsis says a question follows. */
13
+ discard: string;
14
+ /** The question, with the count that would be lost. */
15
+ confirmTitle: (count: number) => string;
16
+ /** The consequence under it: they are gone, and the device takes the server's state. */
17
+ confirmBody: (count: number) => string;
18
+ /** The button that answers it. */
19
+ confirmAction: string;
20
+ /** A switch back or a discard rejected and the app's `formatError` says nothing better. */
21
+ failed: string;
22
+ }
23
+ export declare const DEFAULT_HELD_CHANGES_BANNER_LABELS: HeldChangesBannerLabels;
@@ -0,0 +1,14 @@
1
+ "use client";
2
+ const DEFAULT_HELD_CHANGES_BANNER_LABELS = {
3
+ message: (count) => count === 1 ? "1 change cannot be sent from here. It stays on this device until it can be sent, or until you discard it." : `${count} changes cannot be sent from here. They stay on this device until they can be sent, or until you discard them.`,
4
+ switchBack: "Switch back",
5
+ discard: "Discard\u2026",
6
+ confirmTitle: (count) => count === 1 ? "Discard 1 change that cannot be sent from here?" : `Discard ${count} changes that cannot be sent from here?`,
7
+ confirmBody: (count) => count === 1 ? "It cannot be recovered. This device then syncs with what the server has." : "They cannot be recovered. This device then syncs with what the server has.",
8
+ confirmAction: "Discard",
9
+ failed: "That didn\u2019t work. Please try again."
10
+ };
11
+ export {
12
+ DEFAULT_HELD_CHANGES_BANNER_LABELS
13
+ };
14
+ //# sourceMappingURL=held-changes-banner-labels.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/shell/held-changes-banner-labels.ts"],"sourcesContent":["// The English words of ./held-changes-banner, in a module with no component code: the\n// English catalogue (src/i18n/defaults.ts) imports them without the component (0.34, KS U8).\n// keksdose's reviewed words (sync.held_*, sync.discard_held_*, sync.switch_back_held) are\n// the canon, without its budgets: the kit cannot know why a change waits, only that it does.\n\n/**\n * `HeldChangesBanner`'s words — the `heldChanges` namespace of `<UiKitProvider labels>`\n * (0.55, docs/top-bar-harmonization.md §2.6.1). The confirm's Cancel is\n * `confirmDialog.cancel`.\n */\nexport interface HeldChangesBannerLabels {\n /** The banner's sentence when the app passes none: how many wait, that they stay on the\n * device, and the two ways out. Says nothing of a way back, which the app may not have. */\n message: (count: number) => string;\n /** The way back — keksdose's switch to the budget the changes were made in. */\n switchBack: string;\n /** The button that asks first; the ellipsis says a question follows. */\n discard: string;\n /** The question, with the count that would be lost. */\n confirmTitle: (count: number) => string;\n /** The consequence under it: they are gone, and the device takes the server's state. */\n confirmBody: (count: number) => string;\n /** The button that answers it. */\n confirmAction: string;\n /** A switch back or a discard rejected and the app's `formatError` says nothing better. */\n failed: string;\n}\n\nexport const DEFAULT_HELD_CHANGES_BANNER_LABELS: HeldChangesBannerLabels = {\n message: (count) =>\n count === 1\n ? \"1 change cannot be sent from here. It stays on this device until it can be sent, or until you discard it.\"\n : `${count} changes cannot be sent from here. They stay on this device until they can be sent, or until you discard them.`,\n switchBack: \"Switch back\",\n discard: \"Discard…\",\n confirmTitle: (count) =>\n count === 1\n ? \"Discard 1 change that cannot be sent from here?\"\n : `Discard ${count} changes that cannot be sent from here?`,\n confirmBody: (count) =>\n count === 1\n ? \"It cannot be recovered. This device then syncs with what the server has.\"\n : \"They cannot be recovered. This device then syncs with what the server has.\",\n confirmAction: \"Discard\",\n failed: \"That didn’t work. Please try again.\",\n};\n"],"mappings":";AA4BO,MAAM,qCAA8D;AAAA,EACzE,SAAS,CAAC,UACR,UAAU,IACN,8GACA,GAAG,KAAK;AAAA,EACd,YAAY;AAAA,EACZ,SAAS;AAAA,EACT,cAAc,CAAC,UACb,UAAU,IACN,oDACA,WAAW,KAAK;AAAA,EACtB,aAAa,CAAC,UACZ,UAAU,IACN,6EACA;AAAA,EACN,eAAe;AAAA,EACf,QAAQ;AACV;","names":[]}
@@ -0,0 +1,75 @@
1
+ import type { ReactNode } from "react";
2
+ import { type LabelOverride } from "../i18n/kit-labels.js";
3
+ import { DEFAULT_HELD_CHANGES_BANNER_LABELS, type HeldChangesBannerLabels } from "./held-changes-banner-labels.js";
4
+ export { DEFAULT_HELD_CHANGES_BANNER_LABELS };
5
+ export type { HeldChangesBannerLabels };
6
+ export interface HeldChangesBannerProps {
7
+ /**
8
+ * Changes kept on this device that cannot be sent from where the user is now — the
9
+ * indicator's `blocked`, or the part of it this banner is about (kastlan: the ones the
10
+ * server refused). 0 renders nothing.
11
+ */
12
+ count: number;
13
+ /**
14
+ * The app's sentence: why they wait and what would send them — keksdose's "2 changes
15
+ * were made in “Holiday”, which is no longer the active budget…". Default
16
+ * `heldChanges.message(count)`, which names no reason and no way back.
17
+ */
18
+ message?: ReactNode;
19
+ /**
20
+ * Room under the sentence — kastlan's refusal reasons, as a list. For the box: a strip
21
+ * holds its message to one line on a phone, and a list behind "More" is a list nobody
22
+ * opens, so a strip with `extra` is not clamped.
23
+ */
24
+ extra?: ReactNode;
25
+ /**
26
+ * The way back, where there is one — keksdose's switch to the budget the changes were
27
+ * made in, which sends them. Without it Discard is the one way out. A returned promise
28
+ * keeps the button busy until it settles; a rejection is shown as Discard's is.
29
+ */
30
+ onSwitchBack?: () => Promise<unknown> | void;
31
+ /**
32
+ * Lets them go, once the user has said yes to the kit's confirm: the app drops what is
33
+ * held and syncs (keksdose `discardWaitingChanges`, kastlan `discardRejected` then
34
+ * `onSync`). Busy until the promise settles. A rejection leaves the banner where it is,
35
+ * with the error under the sentence; the count going to 0 is what removes it.
36
+ */
37
+ onDiscard: () => Promise<unknown> | void;
38
+ /** Turns a rejection into words. Default `heldChanges.failed`. */
39
+ formatError?: (error: unknown) => ReactNode;
40
+ /**
41
+ * `box` (default): a framed callout above a page (kastlan's handover pages); on a phone
42
+ * its actions take a line of their own under the sentence. `strip`: a band in the
43
+ * shell beside the app's other strips (keksdose), its message held to one line on a
44
+ * phone with "More" (`clampMobile`, keksdose dev#611), so the actions keep theirs.
45
+ */
46
+ variant?: "box" | "strip";
47
+ /** Prop > `<UiKitProvider labels={{ heldChanges }}>` > English. */
48
+ labels?: LabelOverride<HeldChangesBannerLabels>;
49
+ className?: string;
50
+ /** `data-*` attributes go on the banner — a tour's anchor (`data-tour="…"`). */
51
+ [key: `data-${string}`]: string | number | boolean | undefined;
52
+ }
53
+ /**
54
+ * Changes this device keeps and cannot send from where the user is now, with the two ways
55
+ * out: back to where they can be sent, or let them go (docs/top-bar-harmonization.md
56
+ * §2.6.1). `SyncStatusIndicator` shows the state (`blocked`, ranked first); this is the
57
+ * room for the action that one control in the top bar does not have.
58
+ *
59
+ * Two apps built it by hand:
60
+ * - **keksdose budget switch** (app/held-changes-banner.tsx): the active budget moved
61
+ * and the queue waits for the budget it was made in. A strip in the shell with "Switch
62
+ * back" and "Discard…", which asks first with the count, then discards and syncs.
63
+ * - **kastlan handover-sync-banner** (:57-68): the changes the server refused, a danger
64
+ * box with their reasons and a Discard that asked nothing.
65
+ *
66
+ * The kit's part is the banner and the question; what is held, how to discard it and the
67
+ * sync after are the app's (`onDiscard`). Warning, not danger: nothing is lost until the
68
+ * user says so, and the indicator in the bar already draws the danger. The safe action
69
+ * comes first, so the destructive one is never the only button somebody in a hurry finds
70
+ * (keksdose); Discard is then the quiet one. The confirm is `useConfirm`'s, in the danger
71
+ * tone (focus starts on Cancel), so the app needs a `ConfirmProvider` — all three mount
72
+ * one. Not behind the write lock: the changes are not the page's, and a viewer of what is
73
+ * on screen may still let go of what is held elsewhere (keksdose).
74
+ */
75
+ export declare function HeldChangesBanner(props: HeldChangesBannerProps): import("react").JSX.Element | null;
@@ -0,0 +1,106 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useRef, useState } from "react";
4
+ import { AlertBanner } from "../components/alert-banner.js";
5
+ import { Button } from "../components/button.js";
6
+ import { useConfirm } from "../components/confirm-dialog.js";
7
+ import { useKitLabels } from "../i18n/kit-labels.js";
8
+ import { hasContent } from "../lib/presence.js";
9
+ import { DEFAULT_HELD_CHANGES_BANNER_LABELS } from "./held-changes-banner-labels.js";
10
+ function HeldChangesBanner(props) {
11
+ if (!(props.count > 0)) return null;
12
+ return /* @__PURE__ */ jsx(HeldChangesBody, { ...props });
13
+ }
14
+ function HeldChangesBody({
15
+ count,
16
+ message,
17
+ extra,
18
+ onSwitchBack,
19
+ onDiscard,
20
+ formatError,
21
+ variant = "box",
22
+ labels: labelsProp,
23
+ className,
24
+ ...rest
25
+ }) {
26
+ const labels = useKitLabels("heldChanges", DEFAULT_HELD_CHANGES_BANNER_LABELS, labelsProp);
27
+ const confirm = useConfirm();
28
+ const [busy, setBusy] = useState(null);
29
+ const [failure, setFailure] = useState(null);
30
+ const mounted = useRef(true);
31
+ useEffect(() => {
32
+ mounted.current = true;
33
+ return () => {
34
+ mounted.current = false;
35
+ };
36
+ }, []);
37
+ const run = async (key, action) => {
38
+ setBusy(key);
39
+ setFailure(null);
40
+ try {
41
+ await action();
42
+ } catch (caught) {
43
+ if (!mounted.current) return;
44
+ const said = formatError?.(caught);
45
+ setFailure(hasContent(said) ? said : labels.failed);
46
+ } finally {
47
+ if (mounted.current) setBusy(null);
48
+ }
49
+ };
50
+ const discard = async () => {
51
+ const yes = await confirm({
52
+ title: labels.confirmTitle(count),
53
+ body: labels.confirmBody(count),
54
+ confirmLabel: labels.confirmAction,
55
+ tone: "danger"
56
+ });
57
+ if (yes && mounted.current) await run("discard", onDiscard);
58
+ };
59
+ const strip = variant === "strip";
60
+ return /* @__PURE__ */ jsxs(
61
+ AlertBanner,
62
+ {
63
+ ...rest,
64
+ tone: "warning",
65
+ variant: strip ? "strip" : "box",
66
+ actionPlacement: "own-line",
67
+ clampMobile: strip && !failure && !hasContent(extra),
68
+ className,
69
+ action: /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
70
+ onSwitchBack && /* @__PURE__ */ jsx(
71
+ Button,
72
+ {
73
+ variant: "secondary",
74
+ size: "sm",
75
+ pending: busy === "switchBack",
76
+ disabled: busy === "discard",
77
+ onClick: () => void run("switchBack", onSwitchBack),
78
+ children: labels.switchBack
79
+ }
80
+ ),
81
+ /* @__PURE__ */ jsx(
82
+ Button,
83
+ {
84
+ variant: onSwitchBack ? "ghost" : "secondary",
85
+ tone: "danger",
86
+ size: "sm",
87
+ pending: busy === "discard",
88
+ disabled: busy === "switchBack",
89
+ onClick: () => void discard(),
90
+ children: labels.discard
91
+ }
92
+ )
93
+ ] }),
94
+ children: [
95
+ message ?? labels.message(count),
96
+ hasContent(extra) && /* @__PURE__ */ jsx("div", { className: "mt-1", children: extra }),
97
+ hasContent(failure) && /* @__PURE__ */ jsx(AlertBanner, { variant: "inline", tone: "danger", size: "sm", block: true, className: "mt-1", children: failure })
98
+ ]
99
+ }
100
+ );
101
+ }
102
+ export {
103
+ DEFAULT_HELD_CHANGES_BANNER_LABELS,
104
+ HeldChangesBanner
105
+ };
106
+ //# sourceMappingURL=held-changes-banner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/shell/held-changes-banner.tsx"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { AlertBanner } from \"../components/alert-banner\";\nimport { Button } from \"../components/button\";\nimport { useConfirm } from \"../components/confirm-dialog\";\nimport { useKitLabels, type LabelOverride } from \"../i18n/kit-labels\";\nimport { hasContent } from \"../lib/presence\";\nimport { DEFAULT_HELD_CHANGES_BANNER_LABELS, type HeldChangesBannerLabels } from \"./held-changes-banner-labels\";\n\n// Defined in ./held-changes-banner-labels, which holds no component code (0.34, KS U8).\nexport { DEFAULT_HELD_CHANGES_BANNER_LABELS };\nexport type { HeldChangesBannerLabels };\n\nexport interface HeldChangesBannerProps {\n /**\n * Changes kept on this device that cannot be sent from where the user is now — the\n * indicator's `blocked`, or the part of it this banner is about (kastlan: the ones the\n * server refused). 0 renders nothing.\n */\n count: number;\n /**\n * The app's sentence: why they wait and what would send them — keksdose's \"2 changes\n * were made in “Holiday”, which is no longer the active budget…\". Default\n * `heldChanges.message(count)`, which names no reason and no way back.\n */\n message?: ReactNode;\n /**\n * Room under the sentence — kastlan's refusal reasons, as a list. For the box: a strip\n * holds its message to one line on a phone, and a list behind \"More\" is a list nobody\n * opens, so a strip with `extra` is not clamped.\n */\n extra?: ReactNode;\n /**\n * The way back, where there is one — keksdose's switch to the budget the changes were\n * made in, which sends them. Without it Discard is the one way out. A returned promise\n * keeps the button busy until it settles; a rejection is shown as Discard's is.\n */\n onSwitchBack?: () => Promise<unknown> | void;\n /**\n * Lets them go, once the user has said yes to the kit's confirm: the app drops what is\n * held and syncs (keksdose `discardWaitingChanges`, kastlan `discardRejected` then\n * `onSync`). Busy until the promise settles. A rejection leaves the banner where it is,\n * with the error under the sentence; the count going to 0 is what removes it.\n */\n onDiscard: () => Promise<unknown> | void;\n /** Turns a rejection into words. Default `heldChanges.failed`. */\n formatError?: (error: unknown) => ReactNode;\n /**\n * `box` (default): a framed callout above a page (kastlan's handover pages); on a phone\n * its actions take a line of their own under the sentence. `strip`: a band in the\n * shell beside the app's other strips (keksdose), its message held to one line on a\n * phone with \"More\" (`clampMobile`, keksdose dev#611), so the actions keep theirs.\n */\n variant?: \"box\" | \"strip\";\n /** Prop > `<UiKitProvider labels={{ heldChanges }}>` > English. */\n labels?: LabelOverride<HeldChangesBannerLabels>;\n className?: string;\n /** `data-*` attributes go on the banner — a tour's anchor (`data-tour=\"…\"`). */\n [key: `data-${string}`]: string | number | boolean | undefined;\n}\n\n/**\n * Changes this device keeps and cannot send from where the user is now, with the two ways\n * out: back to where they can be sent, or let them go (docs/top-bar-harmonization.md\n * §2.6.1). `SyncStatusIndicator` shows the state (`blocked`, ranked first); this is the\n * room for the action that one control in the top bar does not have.\n *\n * Two apps built it by hand:\n * - **keksdose budget switch** (app/held-changes-banner.tsx): the active budget moved\n * and the queue waits for the budget it was made in. A strip in the shell with \"Switch\n * back\" and \"Discard…\", which asks first with the count, then discards and syncs.\n * - **kastlan handover-sync-banner** (:57-68): the changes the server refused, a danger\n * box with their reasons and a Discard that asked nothing.\n *\n * The kit's part is the banner and the question; what is held, how to discard it and the\n * sync after are the app's (`onDiscard`). Warning, not danger: nothing is lost until the\n * user says so, and the indicator in the bar already draws the danger. The safe action\n * comes first, so the destructive one is never the only button somebody in a hurry finds\n * (keksdose); Discard is then the quiet one. The confirm is `useConfirm`'s, in the danger\n * tone (focus starts on Cancel), so the app needs a `ConfirmProvider` — all three mount\n * one. Not behind the write lock: the changes are not the page's, and a viewer of what is\n * on screen may still let go of what is held elsewhere (keksdose).\n */\nexport function HeldChangesBanner(props: HeldChangesBannerProps) {\n // Nothing held: nothing to say. The body's state (a running action, an error) goes with\n // it, so a later hold starts clean.\n if (!(props.count > 0)) return null;\n return <HeldChangesBody {...props} />;\n}\n\nfunction HeldChangesBody({\n count,\n message,\n extra,\n onSwitchBack,\n onDiscard,\n formatError,\n variant = \"box\",\n labels: labelsProp,\n className,\n ...rest\n}: HeldChangesBannerProps) {\n const labels = useKitLabels(\"heldChanges\", DEFAULT_HELD_CHANGES_BANNER_LABELS, labelsProp);\n const confirm = useConfirm();\n // One action at a time: a switch back racing a discard would send what is being dropped.\n const [busy, setBusy] = useState<\"switchBack\" | \"discard\" | null>(null);\n const [failure, setFailure] = useState<ReactNode>(null);\n // A discard that worked usually takes the banner with it (the count reaches 0) before\n // its promise settles here.\n const mounted = useRef(true);\n useEffect(() => {\n mounted.current = true;\n return () => {\n mounted.current = false;\n };\n }, []);\n\n const run = async (key: \"switchBack\" | \"discard\", action: () => Promise<unknown> | void) => {\n setBusy(key);\n setFailure(null);\n try {\n await action();\n } catch (caught) {\n if (!mounted.current) return;\n const said = formatError?.(caught);\n setFailure(hasContent(said) ? said : labels.failed);\n } finally {\n if (mounted.current) setBusy(null);\n }\n };\n\n const discard = async () => {\n const yes = await confirm({\n title: labels.confirmTitle(count),\n body: labels.confirmBody(count),\n confirmLabel: labels.confirmAction,\n tone: \"danger\",\n });\n if (yes && mounted.current) await run(\"discard\", onDiscard);\n };\n\n const strip = variant === \"strip\";\n return (\n <AlertBanner\n {...rest}\n tone=\"warning\"\n variant={strip ? \"strip\" : \"box\"}\n // A long sentence beside two buttons squeezed into a column on a phone (kastlan 53):\n // the actions get a line of their own under it. AlertBanner's default, said here.\n actionPlacement=\"own-line\"\n // Not while an error shows or `extra` is there: the clamp would hide them behind \"More\".\n clampMobile={strip && !failure && !hasContent(extra)}\n className={className}\n action={\n <div className=\"flex flex-wrap items-center gap-2\">\n {onSwitchBack && (\n <Button\n variant=\"secondary\"\n size=\"sm\"\n pending={busy === \"switchBack\"}\n disabled={busy === \"discard\"}\n onClick={() => void run(\"switchBack\", onSwitchBack)}\n >\n {labels.switchBack}\n </Button>\n )}\n <Button\n variant={onSwitchBack ? \"ghost\" : \"secondary\"}\n tone=\"danger\"\n size=\"sm\"\n pending={busy === \"discard\"}\n disabled={busy === \"switchBack\"}\n onClick={() => void discard()}\n >\n {labels.discard}\n </Button>\n </div>\n }\n >\n {message ?? labels.message(count)}\n {hasContent(extra) && <div className=\"mt-1\">{extra}</div>}\n {/* Inline, so it is a live region: the answer to the button just pressed is heard. */}\n {hasContent(failure) && (\n <AlertBanner variant=\"inline\" tone=\"danger\" size=\"sm\" block className=\"mt-1\">\n {failure}\n </AlertBanner>\n )}\n </AlertBanner>\n );\n}\n"],"mappings":";AAuFS,cAmED,YAnEC;AAvFT,SAAS,WAAW,QAAQ,gBAAgB;AAE5C,SAAS,mBAAmB;AAC5B,SAAS,cAAc;AACvB,SAAS,kBAAkB;AAC3B,SAAS,oBAAwC;AACjD,SAAS,kBAAkB;AAC3B,SAAS,0CAAwE;AA4E1E,SAAS,kBAAkB,OAA+B;AAG/D,MAAI,EAAE,MAAM,QAAQ,GAAI,QAAO;AAC/B,SAAO,oBAAC,mBAAiB,GAAG,OAAO;AACrC;AAEA,SAAS,gBAAgB;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV,QAAQ;AAAA,EACR;AAAA,EACA,GAAG;AACL,GAA2B;AACzB,QAAM,SAAS,aAAa,eAAe,oCAAoC,UAAU;AACzF,QAAM,UAAU,WAAW;AAE3B,QAAM,CAAC,MAAM,OAAO,IAAI,SAA0C,IAAI;AACtE,QAAM,CAAC,SAAS,UAAU,IAAI,SAAoB,IAAI;AAGtD,QAAM,UAAU,OAAO,IAAI;AAC3B,YAAU,MAAM;AACd,YAAQ,UAAU;AAClB,WAAO,MAAM;AACX,cAAQ,UAAU;AAAA,IACpB;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,QAAM,MAAM,OAAO,KAA+B,WAA0C;AAC1F,YAAQ,GAAG;AACX,eAAW,IAAI;AACf,QAAI;AACF,YAAM,OAAO;AAAA,IACf,SAAS,QAAQ;AACf,UAAI,CAAC,QAAQ,QAAS;AACtB,YAAM,OAAO,cAAc,MAAM;AACjC,iBAAW,WAAW,IAAI,IAAI,OAAO,OAAO,MAAM;AAAA,IACpD,UAAE;AACA,UAAI,QAAQ,QAAS,SAAQ,IAAI;AAAA,IACnC;AAAA,EACF;AAEA,QAAM,UAAU,YAAY;AAC1B,UAAM,MAAM,MAAM,QAAQ;AAAA,MACxB,OAAO,OAAO,aAAa,KAAK;AAAA,MAChC,MAAM,OAAO,YAAY,KAAK;AAAA,MAC9B,cAAc,OAAO;AAAA,MACrB,MAAM;AAAA,IACR,CAAC;AACD,QAAI,OAAO,QAAQ,QAAS,OAAM,IAAI,WAAW,SAAS;AAAA,EAC5D;AAEA,QAAM,QAAQ,YAAY;AAC1B,SACE;AAAA,IAAC;AAAA;AAAA,MACE,GAAG;AAAA,MACJ,MAAK;AAAA,MACL,SAAS,QAAQ,UAAU;AAAA,MAG3B,iBAAgB;AAAA,MAEhB,aAAa,SAAS,CAAC,WAAW,CAAC,WAAW,KAAK;AAAA,MACnD;AAAA,MACA,QACE,qBAAC,SAAI,WAAU,qCACZ;AAAA,wBACC;AAAA,UAAC;AAAA;AAAA,YACC,SAAQ;AAAA,YACR,MAAK;AAAA,YACL,SAAS,SAAS;AAAA,YAClB,UAAU,SAAS;AAAA,YACnB,SAAS,MAAM,KAAK,IAAI,cAAc,YAAY;AAAA,YAEjD,iBAAO;AAAA;AAAA,QACV;AAAA,QAEF;AAAA,UAAC;AAAA;AAAA,YACC,SAAS,eAAe,UAAU;AAAA,YAClC,MAAK;AAAA,YACL,MAAK;AAAA,YACL,SAAS,SAAS;AAAA,YAClB,UAAU,SAAS;AAAA,YACnB,SAAS,MAAM,KAAK,QAAQ;AAAA,YAE3B,iBAAO;AAAA;AAAA,QACV;AAAA,SACF;AAAA,MAGD;AAAA,mBAAW,OAAO,QAAQ,KAAK;AAAA,QAC/B,WAAW,KAAK,KAAK,oBAAC,SAAI,WAAU,QAAQ,iBAAM;AAAA,QAElD,WAAW,OAAO,KACjB,oBAAC,eAAY,SAAQ,UAAS,MAAK,UAAS,MAAK,MAAK,OAAK,MAAC,WAAU,QACnE,mBACH;AAAA;AAAA;AAAA,EAEJ;AAEJ;","names":[]}
@@ -19,15 +19,27 @@ export interface SyncStatus {
19
19
  waitingForPlan?: number;
20
20
  /** When the last good round ended — epoch ms, an ISO string or a `Date`. */
21
21
  lastSyncedAt?: number | string | Date | null;
22
+ /**
23
+ * 0.55 (held back from 0.53): a round of this tab waits for another tab's on the same
24
+ * device copy — the sync engine's Web Lock (0.52) lets one tab's round run at a time — so
25
+ * nothing goes out from here until that one ends. `useSyncStatus` fills it from the
26
+ * engine's `waitingForTab`.
27
+ */
28
+ waitingForTab?: boolean;
22
29
  }
23
- /** The seven states, in their ranking: the first that holds wins. */
24
- export type SyncState = "blocked" | "waitingForPlan" | "offline" | "error" | "syncing" | "pending" | "synced";
30
+ /** The eight states, in their ranking: the first that holds wins. */
31
+ export type SyncState = "blocked" | "waitingForPlan" | "offline" | "waitingForTab" | "error" | "syncing" | "pending" | "synced";
25
32
  /**
26
- * Which of the seven states a {@link SyncStatus} is in — keksdose's ranking
33
+ * Which of the eight states a {@link SyncStatus} is in — keksdose's ranking
27
34
  * (sync-status-indicator.tsx): a write that cannot be sent at all ranks above everything,
28
35
  * because waiting cannot help it; then one waiting for a plan; then offline; then a refused
29
36
  * round; then a round under way; then writes waiting; else in sync. `syncing` also holds
30
37
  * while a tap's own round runs (`refreshing`), before the engine publishes its phase.
38
+ *
39
+ * 0.55 (held back from 0.53): a round waiting for another tab's (`waitingForTab`) ranks
40
+ * after offline, which it cannot help either, and before the rest: it is the start of a
41
+ * round, which replaces an error as `syncing` does once it runs, and the engine never says
42
+ * it beside `syncing` — so it is what a tap's own round shows while it waits its turn.
31
43
  */
32
44
  export declare function syncState(status: SyncStatus, refreshing?: boolean): SyncState;
33
45
  /** The scope words of an indicator that covers only part of the app (§6.10). */
@@ -46,7 +58,8 @@ export interface SyncStatusIndicatorProps {
46
58
  * Run a round now (push, then pull, then re-read what is on screen). With it the
47
59
  * indicator is a "sync now" button, disabled offline and mid-round but keeping its
48
60
  * colour; a returned promise keeps the spin until it settles. Without it, a status only
49
- * (§6.9).
61
+ * (§6.9). Disabled too while a round waits for another tab's (0.55): a tap would only
62
+ * queue one more behind it.
50
63
  */
51
64
  onSync?: () => void | Promise<unknown>;
52
65
  /** Render nothing while in sync — kastlan and Kurvenschmiede show it only while offline,
@@ -59,11 +72,12 @@ export interface SyncStatusIndicatorProps {
59
72
  * offline-first app (keksdose, kastlan's handovers) — offline means "kept here, sent
60
73
  * later". `"none"`: an online-only app that keeps nothing on the device (Kurvenschmiede,
61
74
  * build review B1) — offline reads "changes can't be saved until you reconnect", a
62
- * round reads "Saving…", and `pending`, `blocked` and `waitingForPlan` are ignored,
63
- * since there is nowhere for a change to wait. "Up to date" when settled.
75
+ * round reads "Saving…", and `pending`, `blocked`, `waitingForPlan` and `waitingForTab`
76
+ * are ignored, since there is nowhere for a change to wait. "Up to date" when settled.
64
77
  */
65
78
  storage?: "device" | "none";
66
- /** Short words beside the cloud ("Offline", "Syncing…", "3 waiting") while not in sync. */
79
+ /** Short words beside the cloud ("Offline", "Syncing…", "3 waiting", "Waiting for tab")
80
+ * while not in sync. */
67
81
  showLabel?: boolean;
68
82
  /** Prop > `<UiKitProvider labels={{ topBarParts: { sync } }}>` > English. */
69
83
  labels?: LabelOverride<SyncStatusLabels>;
@@ -75,12 +89,13 @@ export interface SyncStatusIndicatorProps {
75
89
  }
76
90
  /**
77
91
  * The top bar's sync status (docs/top-bar-harmonization.md §2.6), lifted from keksdose's
78
- * `SyncStatusIndicator`: the seven states and their ranking ({@link syncState}), one cloud
79
- * per state, a ✓ badge when in sync and a spin while syncing. With `onSync` it is also the
80
- * "sync now" button, and its name says both what it is and what a tap does.
92
+ * `SyncStatusIndicator`: its seven states and their ranking ({@link syncState}; 0.55 adds an
93
+ * eighth, "Waiting for another tab", an hourglass), one cloud per state, a ✓ badge when in
94
+ * sync and a spin while syncing. With `onSync` it is also the "sync now" button, and its
95
+ * name says both what it is and what a tap does.
81
96
  *
82
97
  * Colours are the kit's roles — danger for a write that cannot be sent, warning for
83
- * anything waiting, info while syncing, success when in sync — where keksdose had raw
84
- * rose/amber/sky/emerald pairs.
98
+ * anything waiting, info while syncing (or waiting for another tab's round), success when
99
+ * in sync — where keksdose had raw rose/amber/sky/emerald pairs.
85
100
  */
86
101
  export declare function SyncStatusIndicator({ status, onSync, hideWhenIdle, scope, showLabel, labels: labelsProp, locale: localeProp, className, storage, ...rest }: SyncStatusIndicatorProps): import("react").JSX.Element | null;
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useEffect, useRef, useState } from "react";
4
- import { Check, Cloud, CloudAlert, CloudOff, RefreshCw, UploadCloud } from "lucide-react";
4
+ import { Check, Cloud, CloudAlert, CloudOff, Hourglass, RefreshCw, UploadCloud } from "lucide-react";
5
5
  import { cn } from "../lib/cn.js";
6
6
  import { IconButton } from "../components/icon-button.js";
7
7
  import { Tooltip } from "../components/tooltip.js";
@@ -12,6 +12,7 @@ function syncState(status, refreshing = false) {
12
12
  if ((status.blocked ?? 0) > 0) return "blocked";
13
13
  if ((status.waitingForPlan ?? 0) > 0) return "waitingForPlan";
14
14
  if (!status.online || status.phase === "offline") return "offline";
15
+ if (status.waitingForTab) return "waitingForTab";
15
16
  if (status.phase === "error") return "error";
16
17
  if (status.phase === "syncing" || refreshing) return "syncing";
17
18
  if (status.pending > 0) return "pending";
@@ -23,6 +24,8 @@ const LOOK = {
23
24
  // Safe, and a payment sends them: amber, the kit's warning.
24
25
  waitingForPlan: { Icon: CloudAlert, tone: "warning" },
25
26
  offline: { Icon: CloudOff, tone: "warning" },
27
+ // A round under way, in another tab: info, as syncing, without the spin.
28
+ waitingForTab: { Icon: Hourglass, tone: "info" },
26
29
  // Usually resolves by itself (a deploy, a read-only view): amber, not danger.
27
30
  error: { Icon: CloudAlert, tone: "warning" },
28
31
  syncing: { Icon: RefreshCw, tone: "info" },
@@ -62,7 +65,10 @@ function SyncStatusIndicator({
62
65
  };
63
66
  }, []);
64
67
  const onlineOnly = storage === "none";
65
- const state = syncState(onlineOnly ? { ...status, pending: 0, blocked: 0, waitingForPlan: 0 } : status, refreshing);
68
+ const state = syncState(
69
+ onlineOnly ? { ...status, pending: 0, blocked: 0, waitingForPlan: 0, waitingForTab: false } : status,
70
+ refreshing
71
+ );
66
72
  const data = dataAttributes(rest);
67
73
  if (hideWhenIdle && state === "synced") return null;
68
74
  const { Icon, tone } = LOOK[state];
@@ -78,6 +84,9 @@ function SyncStatusIndicator({
78
84
  case "offline":
79
85
  text = onlineOnly ? words.offlineUnsaved : words.offline;
80
86
  break;
87
+ case "waitingForTab":
88
+ text = words.waitingForTab;
89
+ break;
81
90
  case "error":
82
91
  text = words.refused;
83
92
  break;
@@ -96,11 +105,11 @@ function SyncStatusIndicator({
96
105
  if (scope) {
97
106
  text = state === "offline" && scope.offline ? scope.offline : words.scoped(scope.name, text);
98
107
  }
99
- const short = state === "offline" ? words.offlineShort : state === "syncing" ? onlineOnly ? words.saving : words.syncingShort : state === "error" ? words.refusedShort : state === "pending" || state === "blocked" || state === "waitingForPlan" ? words.waitingShort(
108
+ const short = state === "offline" ? words.offlineShort : state === "syncing" ? onlineOnly ? words.saving : words.syncingShort : state === "error" ? words.refusedShort : state === "waitingForTab" ? words.waitingForTabShort : state === "pending" || state === "blocked" || state === "waitingForPlan" ? words.waitingShort(
100
109
  state === "pending" ? status.pending : state === "blocked" ? status.blocked ?? 0 : status.waitingForPlan ?? 0
101
110
  ) : null;
102
111
  const offline = state === "offline";
103
- const canSync = !!onSync && status.online && status.phase !== "offline" && !spin;
112
+ const canSync = !!onSync && status.online && status.phase !== "offline" && !spin && state !== "waitingForTab";
104
113
  const name = onSync ? canSync ? words.withHint(text, words.syncHint) : offline ? words.withHint(text, words.offlineHint) : text : text;
105
114
  const badge = state === "synced" ? /* @__PURE__ */ jsx(Check, { className: "stroke-[3]" }) : void 0;
106
115
  const glyph = /* @__PURE__ */ jsx(Icon, { "aria-hidden": true, className: spin ? "animate-spin" : void 0 });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/shell/sync-status-indicator.tsx"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport type { ComponentType } from \"react\";\nimport { Check, Cloud, CloudAlert, CloudOff, RefreshCw, UploadCloud } from \"lucide-react\";\nimport type { LucideProps } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { IconButton } from \"../components/icon-button\";\nimport { Tooltip } from \"../components/tooltip\";\nimport { useBillingLabels } from \"../billing/billing-labels\";\nimport { useKitLocale, type LabelOverride } from \"../i18n/kit-labels\";\nimport { useTopBarPartsLabels, type SyncStatusLabels } from \"./top-bar-parts-labels\";\n\n/**\n * What the indicator shows, fed by the app's sync engine (docs/top-bar-harmonization.md\n * §2.6). Small on purpose: keksdose's sync-status store, kastlan's handover controller and\n * Kurvenschmiede's online state + `useIsMutating` all fill it.\n */\nexport interface SyncStatus {\n /** What the engine is doing. `offline` is the engine's own verdict; `online: false` is\n * the browser's — either shows the offline state. */\n phase: \"idle\" | \"syncing\" | \"offline\" | \"error\";\n /** The browser's `navigator.onLine` (or the app's better guess). */\n online: boolean;\n /** Writes kept on this device and not yet sent. */\n pending: number;\n /** Writes this device cannot send at all (keksdose: no key for the budget). Ranked first. */\n blocked?: number;\n /** Writes the server refused as read-only and keeps until a plan (billing §12.5). */\n waitingForPlan?: number;\n /** When the last good round ended — epoch ms, an ISO string or a `Date`. */\n lastSyncedAt?: number | string | Date | null;\n}\n\n/** The seven states, in their ranking: the first that holds wins. */\nexport type SyncState = \"blocked\" | \"waitingForPlan\" | \"offline\" | \"error\" | \"syncing\" | \"pending\" | \"synced\";\n\n/**\n * Which of the seven states a {@link SyncStatus} is in — keksdose's ranking\n * (sync-status-indicator.tsx): a write that cannot be sent at all ranks above everything,\n * because waiting cannot help it; then one waiting for a plan; then offline; then a refused\n * round; then a round under way; then writes waiting; else in sync. `syncing` also holds\n * while a tap's own round runs (`refreshing`), before the engine publishes its phase.\n */\nexport function syncState(status: SyncStatus, refreshing = false): SyncState {\n if ((status.blocked ?? 0) > 0) return \"blocked\";\n if ((status.waitingForPlan ?? 0) > 0) return \"waitingForPlan\";\n if (!status.online || status.phase === \"offline\") return \"offline\";\n if (status.phase === \"error\") return \"error\";\n if (status.phase === \"syncing\" || refreshing) return \"syncing\";\n if (status.pending > 0) return \"pending\";\n return \"synced\";\n}\n\ntype Tone = \"danger\" | \"warning\" | \"info\" | \"success\";\n\nconst LOOK: Record<SyncState, { Icon: ComponentType<LucideProps>; tone: Tone }> = {\n // The one the user must act on: the kit's danger, where keksdose had rose.\n blocked: { Icon: CloudAlert, tone: \"danger\" },\n // Safe, and a payment sends them: amber, the kit's warning.\n waitingForPlan: { Icon: CloudAlert, tone: \"warning\" },\n offline: { Icon: CloudOff, tone: \"warning\" },\n // Usually resolves by itself (a deploy, a read-only view): amber, not danger.\n error: { Icon: CloudAlert, tone: \"warning\" },\n syncing: { Icon: RefreshCw, tone: \"info\" },\n pending: { Icon: UploadCloud, tone: \"warning\" },\n synced: { Icon: Cloud, tone: \"success\" },\n};\n\nconst TEXT_TONE: Record<Tone, string> = {\n danger: \"text-danger\",\n warning: \"text-warning\",\n info: \"text-info\",\n success: \"text-success\",\n};\n\n/** The scope words of an indicator that covers only part of the app (§6.10). */\nexport interface SyncStatusScope {\n /** Put before every state in the tooltip: \"Handovers on this device\" →\n * \"Handovers on this device: 3 changes waiting to sync\". */\n name: string;\n /** The whole offline sentence instead of the kit's, when offline means something\n * narrower: \"Offline: handovers keep working on this device; everything else needs a\n * connection\". */\n offline?: string;\n}\n\nexport interface SyncStatusIndicatorProps {\n status: SyncStatus;\n /**\n * Run a round now (push, then pull, then re-read what is on screen). With it the\n * indicator is a \"sync now\" button, disabled offline and mid-round but keeping its\n * colour; a returned promise keeps the spin until it settles. Without it, a status only\n * (§6.9).\n */\n onSync?: () => void | Promise<unknown>;\n /** Render nothing while in sync — kastlan and Kurvenschmiede show it only while offline,\n * syncing or with changes waiting, held or refused (§2.6). */\n hideWhenIdle?: boolean;\n /** The app's words for its part of the app (§6.10). */\n scope?: SyncStatusScope;\n /**\n * Where the app keeps a change before the server has it. `\"device\"` (the default): an\n * offline-first app (keksdose, kastlan's handovers) — offline means \"kept here, sent\n * later\". `\"none\"`: an online-only app that keeps nothing on the device (Kurvenschmiede,\n * build review B1) — offline reads \"changes can't be saved until you reconnect\", a\n * round reads \"Saving…\", and `pending`, `blocked` and `waitingForPlan` are ignored,\n * since there is nowhere for a change to wait. \"Up to date\" when settled.\n */\n storage?: \"device\" | \"none\";\n /** Short words beside the cloud (\"Offline\", \"Syncing…\", \"3 waiting\") while not in sync. */\n showLabel?: boolean;\n /** Prop > `<UiKitProvider labels={{ topBarParts: { sync } }}>` > English. */\n labels?: LabelOverride<SyncStatusLabels>;\n /** The clock's language for \"Synced at 14:05\". Default: the provider's locale. */\n locale?: string;\n className?: string;\n /** `data-*` attributes go on the trigger — a tour's anchor (`data-tour=\"sync-status\"`). */\n [key: `data-${string}`]: string | number | boolean | undefined;\n}\n\n/** The `data-*` attributes of a rest object, for a trigger. */\nfunction dataAttributes(rest: Record<string, unknown>): Record<string, unknown> {\n return Object.fromEntries(Object.entries(rest).filter(([k]) => k.startsWith(\"data-\")));\n}\n\n/**\n * The top bar's sync status (docs/top-bar-harmonization.md §2.6), lifted from keksdose's\n * `SyncStatusIndicator`: the seven states and their ranking ({@link syncState}), one cloud\n * per state, a ✓ badge when in sync and a spin while syncing. With `onSync` it is also the\n * \"sync now\" button, and its name says both what it is and what a tap does.\n *\n * Colours are the kit's roles — danger for a write that cannot be sent, warning for\n * anything waiting, info while syncing, success when in sync — where keksdose had raw\n * rose/amber/sky/emerald pairs.\n */\nexport function SyncStatusIndicator({\n status,\n onSync,\n hideWhenIdle = false,\n scope,\n showLabel = false,\n labels: labelsProp,\n locale: localeProp,\n className,\n storage = \"device\",\n ...rest\n}: SyncStatusIndicatorProps) {\n const words = useTopBarPartsLabels(labelsProp ? { sync: labelsProp } : undefined).sync;\n const billing = useBillingLabels();\n const locale = useKitLocale(localeProp);\n // The tap's own round, local rather than in the app's store: the store's phase belongs\n // to the sync engine, and a second writer would race the round itself.\n const [refreshing, setRefreshing] = useState(false);\n const mounted = useRef(true);\n useEffect(() => {\n mounted.current = true;\n return () => {\n mounted.current = false;\n };\n }, []);\n\n const onlineOnly = storage === \"none\";\n // Online-only: nothing waits on the device, so only offline, error, saving and settled.\n const state = syncState(onlineOnly ? { ...status, pending: 0, blocked: 0, waitingForPlan: 0 } : status, refreshing);\n const data = dataAttributes(rest);\n if (hideWhenIdle && state === \"synced\") return null;\n\n const { Icon, tone } = LOOK[state];\n const spin = state === \"syncing\";\n let text: string;\n switch (state) {\n case \"blocked\":\n text = words.blocked(status.blocked ?? 0);\n break;\n case \"waitingForPlan\":\n text = billing.waitingChanges(status.waitingForPlan ?? 0);\n break;\n case \"offline\":\n text = onlineOnly ? words.offlineUnsaved : words.offline;\n break;\n case \"error\":\n text = words.refused;\n break;\n case \"syncing\":\n text = onlineOnly ? words.saving : words.syncing;\n break;\n case \"pending\":\n text = words.pending(status.pending);\n break;\n default:\n text =\n !onlineOnly && status.lastSyncedAt\n ? words.syncedAt(\n // A clock time in the UI's language, never the browser's (keksdose live #246).\n new Date(status.lastSyncedAt).toLocaleTimeString(locale, { hour: \"2-digit\", minute: \"2-digit\" }),\n )\n : words.idle;\n }\n if (scope) {\n text = state === \"offline\" && scope.offline ? scope.offline : words.scoped(scope.name, text);\n }\n\n const short =\n state === \"offline\"\n ? words.offlineShort\n : state === \"syncing\"\n ? onlineOnly\n ? words.saving\n : words.syncingShort\n : state === \"error\"\n ? words.refusedShort\n : state === \"pending\" || state === \"blocked\" || state === \"waitingForPlan\"\n ? words.waitingShort(\n state === \"pending\"\n ? status.pending\n : state === \"blocked\"\n ? (status.blocked ?? 0)\n : (status.waitingForPlan ?? 0),\n )\n : null;\n\n const offline = state === \"offline\";\n const canSync = !!onSync && status.online && status.phase !== \"offline\" && !spin;\n const name = onSync\n ? canSync\n ? words.withHint(text, words.syncHint)\n : offline\n ? words.withHint(text, words.offlineHint)\n : text\n : text;\n const badge = state === \"synced\" ? <Check className=\"stroke-[3]\" /> : undefined;\n const glyph = <Icon aria-hidden className={spin ? \"animate-spin\" : undefined} />;\n\n const sync = async () => {\n if (!canSync || !onSync) return;\n setRefreshing(true);\n try {\n await onSync();\n } finally {\n if (mounted.current) setRefreshing(false);\n }\n };\n\n const control = onSync ? (\n <IconButton\n {...data}\n data-sync-state={state}\n size=\"2xs\"\n glyphSize={20}\n shape=\"round\"\n // A status that is also an action: disabled (offline, mid-round) is exactly when\n // the state matters most, so it keeps its colour.\n disabledStyle=\"keep\"\n tone={tone}\n quiet={false}\n label={name}\n labelVisible={false}\n // Below: the bar is at the top edge, where a bubble above has no room.\n tooltipSide=\"bottom\"\n onClick={() => void sync()}\n disabled={!canSync}\n // It announces itself as a status: a reader hears the state change.\n aria-live=\"polite\"\n badge={badge}\n >\n {glyph}\n </IconButton>\n ) : (\n <Tooltip label={name} side=\"bottom\">\n <span\n {...data}\n role=\"status\"\n data-sync-state={state}\n className={cn(\n \"relative inline-flex size-9 shrink-0 items-center justify-center [&>svg]:size-5\",\n TEXT_TONE[tone],\n )}\n >\n {glyph}\n <span className=\"sr-only\">{name}</span>\n {badge && (\n <span\n aria-hidden\n className=\"pointer-events-none absolute bottom-1.5 end-1.5 inline-flex rounded-full bg-surface [&_svg]:size-3\"\n >\n {badge}\n </span>\n )}\n </span>\n </Tooltip>\n );\n\n if (!showLabel || !short)\n return className ? <span className={cn(\"inline-flex\", className)}>{control}</span> : control;\n return (\n <span className={cn(\"inline-flex items-center gap-1\", className)}>\n {control}\n {/* The eye's short words; the name above already says it in full. */}\n <span aria-hidden className={cn(\"whitespace-nowrap text-xs font-medium\", TEXT_TONE[tone])}>\n {short}\n </span>\n </span>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/shell/sync-status-indicator.tsx"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport type { ComponentType } from \"react\";\nimport { Check, Cloud, CloudAlert, CloudOff, Hourglass, RefreshCw, UploadCloud } from \"lucide-react\";\nimport type { LucideProps } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { IconButton } from \"../components/icon-button\";\nimport { Tooltip } from \"../components/tooltip\";\nimport { useBillingLabels } from \"../billing/billing-labels\";\nimport { useKitLocale, type LabelOverride } from \"../i18n/kit-labels\";\nimport { useTopBarPartsLabels, type SyncStatusLabels } from \"./top-bar-parts-labels\";\n\n/**\n * What the indicator shows, fed by the app's sync engine (docs/top-bar-harmonization.md\n * §2.6). Small on purpose: keksdose's sync-status store, kastlan's handover controller and\n * Kurvenschmiede's online state + `useIsMutating` all fill it.\n */\nexport interface SyncStatus {\n /** What the engine is doing. `offline` is the engine's own verdict; `online: false` is\n * the browser's — either shows the offline state. */\n phase: \"idle\" | \"syncing\" | \"offline\" | \"error\";\n /** The browser's `navigator.onLine` (or the app's better guess). */\n online: boolean;\n /** Writes kept on this device and not yet sent. */\n pending: number;\n /** Writes this device cannot send at all (keksdose: no key for the budget). Ranked first. */\n blocked?: number;\n /** Writes the server refused as read-only and keeps until a plan (billing §12.5). */\n waitingForPlan?: number;\n /** When the last good round ended — epoch ms, an ISO string or a `Date`. */\n lastSyncedAt?: number | string | Date | null;\n /**\n * 0.55 (held back from 0.53): a round of this tab waits for another tab's on the same\n * device copy — the sync engine's Web Lock (0.52) lets one tab's round run at a time — so\n * nothing goes out from here until that one ends. `useSyncStatus` fills it from the\n * engine's `waitingForTab`.\n */\n waitingForTab?: boolean;\n}\n\n/** The eight states, in their ranking: the first that holds wins. */\nexport type SyncState =\n \"blocked\" | \"waitingForPlan\" | \"offline\" | \"waitingForTab\" | \"error\" | \"syncing\" | \"pending\" | \"synced\";\n\n/**\n * Which of the eight states a {@link SyncStatus} is in — keksdose's ranking\n * (sync-status-indicator.tsx): a write that cannot be sent at all ranks above everything,\n * because waiting cannot help it; then one waiting for a plan; then offline; then a refused\n * round; then a round under way; then writes waiting; else in sync. `syncing` also holds\n * while a tap's own round runs (`refreshing`), before the engine publishes its phase.\n *\n * 0.55 (held back from 0.53): a round waiting for another tab's (`waitingForTab`) ranks\n * after offline, which it cannot help either, and before the rest: it is the start of a\n * round, which replaces an error as `syncing` does once it runs, and the engine never says\n * it beside `syncing` — so it is what a tap's own round shows while it waits its turn.\n */\nexport function syncState(status: SyncStatus, refreshing = false): SyncState {\n if ((status.blocked ?? 0) > 0) return \"blocked\";\n if ((status.waitingForPlan ?? 0) > 0) return \"waitingForPlan\";\n if (!status.online || status.phase === \"offline\") return \"offline\";\n if (status.waitingForTab) return \"waitingForTab\";\n if (status.phase === \"error\") return \"error\";\n if (status.phase === \"syncing\" || refreshing) return \"syncing\";\n if (status.pending > 0) return \"pending\";\n return \"synced\";\n}\n\ntype Tone = \"danger\" | \"warning\" | \"info\" | \"success\";\n\nconst LOOK: Record<SyncState, { Icon: ComponentType<LucideProps>; tone: Tone }> = {\n // The one the user must act on: the kit's danger, where keksdose had rose.\n blocked: { Icon: CloudAlert, tone: \"danger\" },\n // Safe, and a payment sends them: amber, the kit's warning.\n waitingForPlan: { Icon: CloudAlert, tone: \"warning\" },\n offline: { Icon: CloudOff, tone: \"warning\" },\n // A round under way, in another tab: info, as syncing, without the spin.\n waitingForTab: { Icon: Hourglass, tone: \"info\" },\n // Usually resolves by itself (a deploy, a read-only view): amber, not danger.\n error: { Icon: CloudAlert, tone: \"warning\" },\n syncing: { Icon: RefreshCw, tone: \"info\" },\n pending: { Icon: UploadCloud, tone: \"warning\" },\n synced: { Icon: Cloud, tone: \"success\" },\n};\n\nconst TEXT_TONE: Record<Tone, string> = {\n danger: \"text-danger\",\n warning: \"text-warning\",\n info: \"text-info\",\n success: \"text-success\",\n};\n\n/** The scope words of an indicator that covers only part of the app (§6.10). */\nexport interface SyncStatusScope {\n /** Put before every state in the tooltip: \"Handovers on this device\" →\n * \"Handovers on this device: 3 changes waiting to sync\". */\n name: string;\n /** The whole offline sentence instead of the kit's, when offline means something\n * narrower: \"Offline: handovers keep working on this device; everything else needs a\n * connection\". */\n offline?: string;\n}\n\nexport interface SyncStatusIndicatorProps {\n status: SyncStatus;\n /**\n * Run a round now (push, then pull, then re-read what is on screen). With it the\n * indicator is a \"sync now\" button, disabled offline and mid-round but keeping its\n * colour; a returned promise keeps the spin until it settles. Without it, a status only\n * (§6.9). Disabled too while a round waits for another tab's (0.55): a tap would only\n * queue one more behind it.\n */\n onSync?: () => void | Promise<unknown>;\n /** Render nothing while in sync — kastlan and Kurvenschmiede show it only while offline,\n * syncing or with changes waiting, held or refused (§2.6). */\n hideWhenIdle?: boolean;\n /** The app's words for its part of the app (§6.10). */\n scope?: SyncStatusScope;\n /**\n * Where the app keeps a change before the server has it. `\"device\"` (the default): an\n * offline-first app (keksdose, kastlan's handovers) — offline means \"kept here, sent\n * later\". `\"none\"`: an online-only app that keeps nothing on the device (Kurvenschmiede,\n * build review B1) — offline reads \"changes can't be saved until you reconnect\", a\n * round reads \"Saving…\", and `pending`, `blocked`, `waitingForPlan` and `waitingForTab`\n * are ignored, since there is nowhere for a change to wait. \"Up to date\" when settled.\n */\n storage?: \"device\" | \"none\";\n /** Short words beside the cloud (\"Offline\", \"Syncing…\", \"3 waiting\", \"Waiting for tab\")\n * while not in sync. */\n showLabel?: boolean;\n /** Prop > `<UiKitProvider labels={{ topBarParts: { sync } }}>` > English. */\n labels?: LabelOverride<SyncStatusLabels>;\n /** The clock's language for \"Synced at 14:05\". Default: the provider's locale. */\n locale?: string;\n className?: string;\n /** `data-*` attributes go on the trigger — a tour's anchor (`data-tour=\"sync-status\"`). */\n [key: `data-${string}`]: string | number | boolean | undefined;\n}\n\n/** The `data-*` attributes of a rest object, for a trigger. */\nfunction dataAttributes(rest: Record<string, unknown>): Record<string, unknown> {\n return Object.fromEntries(Object.entries(rest).filter(([k]) => k.startsWith(\"data-\")));\n}\n\n/**\n * The top bar's sync status (docs/top-bar-harmonization.md §2.6), lifted from keksdose's\n * `SyncStatusIndicator`: its seven states and their ranking ({@link syncState}; 0.55 adds an\n * eighth, \"Waiting for another tab\", an hourglass), one cloud per state, a ✓ badge when in\n * sync and a spin while syncing. With `onSync` it is also the \"sync now\" button, and its\n * name says both what it is and what a tap does.\n *\n * Colours are the kit's roles — danger for a write that cannot be sent, warning for\n * anything waiting, info while syncing (or waiting for another tab's round), success when\n * in sync — where keksdose had raw rose/amber/sky/emerald pairs.\n */\nexport function SyncStatusIndicator({\n status,\n onSync,\n hideWhenIdle = false,\n scope,\n showLabel = false,\n labels: labelsProp,\n locale: localeProp,\n className,\n storage = \"device\",\n ...rest\n}: SyncStatusIndicatorProps) {\n const words = useTopBarPartsLabels(labelsProp ? { sync: labelsProp } : undefined).sync;\n const billing = useBillingLabels();\n const locale = useKitLocale(localeProp);\n // The tap's own round, local rather than in the app's store: the store's phase belongs\n // to the sync engine, and a second writer would race the round itself.\n const [refreshing, setRefreshing] = useState(false);\n const mounted = useRef(true);\n useEffect(() => {\n mounted.current = true;\n return () => {\n mounted.current = false;\n };\n }, []);\n\n const onlineOnly = storage === \"none\";\n // Online-only: nothing waits on the device, so only offline, error, saving and settled.\n const state = syncState(\n onlineOnly ? { ...status, pending: 0, blocked: 0, waitingForPlan: 0, waitingForTab: false } : status,\n refreshing,\n );\n const data = dataAttributes(rest);\n if (hideWhenIdle && state === \"synced\") return null;\n\n const { Icon, tone } = LOOK[state];\n const spin = state === \"syncing\";\n let text: string;\n switch (state) {\n case \"blocked\":\n text = words.blocked(status.blocked ?? 0);\n break;\n case \"waitingForPlan\":\n text = billing.waitingChanges(status.waitingForPlan ?? 0);\n break;\n case \"offline\":\n text = onlineOnly ? words.offlineUnsaved : words.offline;\n break;\n case \"waitingForTab\":\n text = words.waitingForTab;\n break;\n case \"error\":\n text = words.refused;\n break;\n case \"syncing\":\n text = onlineOnly ? words.saving : words.syncing;\n break;\n case \"pending\":\n text = words.pending(status.pending);\n break;\n default:\n text =\n !onlineOnly && status.lastSyncedAt\n ? words.syncedAt(\n // A clock time in the UI's language, never the browser's (keksdose live #246).\n new Date(status.lastSyncedAt).toLocaleTimeString(locale, { hour: \"2-digit\", minute: \"2-digit\" }),\n )\n : words.idle;\n }\n if (scope) {\n text = state === \"offline\" && scope.offline ? scope.offline : words.scoped(scope.name, text);\n }\n\n const short =\n state === \"offline\"\n ? words.offlineShort\n : state === \"syncing\"\n ? onlineOnly\n ? words.saving\n : words.syncingShort\n : state === \"error\"\n ? words.refusedShort\n : state === \"waitingForTab\"\n ? words.waitingForTabShort\n : state === \"pending\" || state === \"blocked\" || state === \"waitingForPlan\"\n ? words.waitingShort(\n state === \"pending\"\n ? status.pending\n : state === \"blocked\"\n ? (status.blocked ?? 0)\n : (status.waitingForPlan ?? 0),\n )\n : null;\n\n const offline = state === \"offline\";\n // Not while a round waits for another tab's either: a tap would queue one more behind it.\n const canSync = !!onSync && status.online && status.phase !== \"offline\" && !spin && state !== \"waitingForTab\";\n const name = onSync\n ? canSync\n ? words.withHint(text, words.syncHint)\n : offline\n ? words.withHint(text, words.offlineHint)\n : text\n : text;\n const badge = state === \"synced\" ? <Check className=\"stroke-[3]\" /> : undefined;\n const glyph = <Icon aria-hidden className={spin ? \"animate-spin\" : undefined} />;\n\n const sync = async () => {\n if (!canSync || !onSync) return;\n setRefreshing(true);\n try {\n await onSync();\n } finally {\n if (mounted.current) setRefreshing(false);\n }\n };\n\n const control = onSync ? (\n <IconButton\n {...data}\n data-sync-state={state}\n size=\"2xs\"\n glyphSize={20}\n shape=\"round\"\n // A status that is also an action: disabled (offline, mid-round) is exactly when\n // the state matters most, so it keeps its colour.\n disabledStyle=\"keep\"\n tone={tone}\n quiet={false}\n label={name}\n labelVisible={false}\n // Below: the bar is at the top edge, where a bubble above has no room.\n tooltipSide=\"bottom\"\n onClick={() => void sync()}\n disabled={!canSync}\n // It announces itself as a status: a reader hears the state change.\n aria-live=\"polite\"\n badge={badge}\n >\n {glyph}\n </IconButton>\n ) : (\n <Tooltip label={name} side=\"bottom\">\n <span\n {...data}\n role=\"status\"\n data-sync-state={state}\n className={cn(\n \"relative inline-flex size-9 shrink-0 items-center justify-center [&>svg]:size-5\",\n TEXT_TONE[tone],\n )}\n >\n {glyph}\n <span className=\"sr-only\">{name}</span>\n {badge && (\n <span\n aria-hidden\n className=\"pointer-events-none absolute bottom-1.5 end-1.5 inline-flex rounded-full bg-surface [&_svg]:size-3\"\n >\n {badge}\n </span>\n )}\n </span>\n </Tooltip>\n );\n\n if (!showLabel || !short)\n return className ? <span className={cn(\"inline-flex\", className)}>{control}</span> : control;\n return (\n <span className={cn(\"inline-flex items-center gap-1\", className)}>\n {control}\n {/* The eye's short words; the name above already says it in full. */}\n <span aria-hidden className={cn(\"whitespace-nowrap text-xs font-medium\", TEXT_TONE[tone])}>\n {short}\n </span>\n </span>\n );\n}\n"],"mappings":"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","names":[]}
@@ -65,6 +65,11 @@ export interface SyncStatusLabels {
65
65
  /** A round under way in an online-only app — it saves, it doesn't sync. Also its short
66
66
  * words beside the cloud. */
67
67
  saving: string;
68
+ /** 0.55 (held back from 0.53): a round waits for another tab's on the same device copy
69
+ * (the sync engine's Web Lock lets one tab sync at a time). */
70
+ waitingForTab: string;
71
+ /** Its short words beside the cloud. */
72
+ waitingForTabShort: string;
68
73
  }
69
74
  /**
70
75
  * `DevMenu`'s words (§2.7, §2.7.1, §6.2, §6.3). Still named for 0.42's `DevSwitchers`: the
@@ -30,7 +30,9 @@ const DEFAULT_TOP_BAR_PARTS_LABELS = {
30
30
  waitingShort: (count) => `${count} waiting`,
31
31
  refusedShort: "Not synced",
32
32
  offlineUnsaved: "Offline \u2014 changes can't be saved until you reconnect",
33
- saving: "Saving\u2026"
33
+ saving: "Saving\u2026",
34
+ waitingForTab: "Waiting for another tab to finish syncing",
35
+ waitingForTabShort: "Waiting for tab"
34
36
  },
35
37
  dev: {
36
38
  menu: "Dev tools",