@stackcraftmx/calcita 0.23.0 → 0.25.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 (359) hide show
  1. package/README.md +111 -22
  2. package/cli/projectFiles.js +5 -3
  3. package/cli/templates/project/README.md.tpl +28 -0
  4. package/cli/templates/project/public/manifest.webmanifest.tpl +6 -6
  5. package/cli/templates/project/src/app/AppProviders.jsx.tpl +9 -4
  6. package/cli/templates/project/src/app/appRouter.jsx.tpl +4 -2
  7. package/cli/templates/project/src/app/appSettings.js.tpl +14 -0
  8. package/cli/templates/project/src/core/AppLogo.jsx.tpl +3 -1
  9. package/cli/templates/project/src/core/index.js.tpl +1 -0
  10. package/cli/templates/project/src/core/publicPaths.js.tpl +24 -0
  11. package/cli/templates/project/vite.config.js.tpl +27 -1
  12. package/dist/DataTable/AuditTrailTable.js +5 -3
  13. package/dist/DataTable/AuditTrailTable.js.map +1 -1
  14. package/dist/DataTable/ColumnFilters/MultiSelectColumnFilter.js +2 -1
  15. package/dist/DataTable/ColumnFilters/MultiSelectColumnFilter.js.map +1 -1
  16. package/dist/DataTable/EmptyState.js +2 -2
  17. package/dist/DataTable/EmptyState.js.map +1 -1
  18. package/dist/DataTable/FilterChips.js +2 -2
  19. package/dist/DataTable/FilterChips.js.map +1 -1
  20. package/dist/DataTable/GoToPage.js +4 -3
  21. package/dist/DataTable/GoToPage.js.map +1 -1
  22. package/dist/DataTable/ServerTable.js +153 -83
  23. package/dist/DataTable/ServerTable.js.map +1 -1
  24. package/dist/DataTable/rowSelection.js +61 -0
  25. package/dist/DataTable/rowSelection.js.map +1 -0
  26. package/dist/access/PermissionMatrix.js +3 -2
  27. package/dist/access/PermissionMatrix.js.map +1 -1
  28. package/dist/account/AccountSecuritySection.js +2 -2
  29. package/dist/account/AccountSecuritySection.js.map +1 -1
  30. package/dist/account/ForgotPasswordScreen.js +5 -4
  31. package/dist/account/ForgotPasswordScreen.js.map +1 -1
  32. package/dist/account/ProfileScreen.js +39 -13
  33. package/dist/account/ProfileScreen.js.map +1 -1
  34. package/dist/account/ResetPasswordScreen.js +3 -2
  35. package/dist/account/ResetPasswordScreen.js.map +1 -1
  36. package/dist/account/SignInScreen.js +7 -6
  37. package/dist/account/SignInScreen.js.map +1 -1
  38. package/dist/account/accountConnector.js.map +1 -1
  39. package/dist/account/restAccountConnector.js +2 -1
  40. package/dist/account/restAccountConnector.js.map +1 -1
  41. package/dist/adapters/reactRouterRoutes.js +7 -6
  42. package/dist/adapters/reactRouterRoutes.js.map +1 -1
  43. package/dist/api/createApiClient.js +35 -5
  44. package/dist/api/createApiClient.js.map +1 -1
  45. package/dist/appShell/AppLayout.js +27 -7
  46. package/dist/appShell/AppLayout.js.map +1 -1
  47. package/dist/appShell/AppearanceSettings.js +29 -6
  48. package/dist/appShell/AppearanceSettings.js.map +1 -1
  49. package/dist/appShell/AuthLayout.js +2 -0
  50. package/dist/appShell/AuthLayout.js.map +1 -1
  51. package/dist/appShell/BarActionButton.js +3 -3
  52. package/dist/appShell/BarActionButton.js.map +1 -1
  53. package/dist/appShell/BarActions.js +19 -8
  54. package/dist/appShell/BarActions.js.map +1 -1
  55. package/dist/appShell/BarCapsule.js +19 -4
  56. package/dist/appShell/BarCapsule.js.map +1 -1
  57. package/dist/appShell/BarSearch.js +92 -0
  58. package/dist/appShell/BarSearch.js.map +1 -0
  59. package/dist/appShell/MobileTabBar.js +3 -1
  60. package/dist/appShell/MobileTabBar.js.map +1 -1
  61. package/dist/appShell/shellSizes.js +2 -0
  62. package/dist/appShell/shellSizes.js.map +1 -1
  63. package/dist/auth/PasswordChecklist.js +2 -2
  64. package/dist/auth/PasswordChecklist.js.map +1 -1
  65. package/dist/calendar/EventCalendar.js +16 -5
  66. package/dist/calendar/EventCalendar.js.map +1 -1
  67. package/dist/calendar/EventDetails.js +74 -0
  68. package/dist/calendar/EventDetails.js.map +1 -0
  69. package/dist/calendar/calendarEvents.js +31 -0
  70. package/dist/calendar/calendarEvents.js.map +1 -0
  71. package/dist/calendar.js +8 -1
  72. package/dist/calendar.js.map +1 -1
  73. package/dist/charts.js +10 -1
  74. package/dist/charts.js.map +1 -1
  75. package/dist/datePickers/DateInput.js +3 -2
  76. package/dist/datePickers/DateInput.js.map +1 -1
  77. package/dist/downloads/DownloadManager.js +14 -20
  78. package/dist/downloads/DownloadManager.js.map +1 -1
  79. package/dist/downloads/downloadQueue.js +4 -2
  80. package/dist/downloads/downloadQueue.js.map +1 -1
  81. package/dist/downloads/downloadsMachine.js +4 -1
  82. package/dist/downloads/downloadsMachine.js.map +1 -1
  83. package/dist/downloads/fileTypes.js +22 -0
  84. package/dist/downloads/fileTypes.js.map +1 -0
  85. package/dist/feedback/ConfirmDialog.js +3 -2
  86. package/dist/feedback/ConfirmDialog.js.map +1 -1
  87. package/dist/feedback/ModalWaiting.js +2 -2
  88. package/dist/feedback/ModalWaiting.js.map +1 -1
  89. package/dist/feedback/NewBuildNotice.js +2 -2
  90. package/dist/feedback/NewBuildNotice.js.map +1 -1
  91. package/dist/filepond/FileDropzone.js +5 -3
  92. package/dist/filepond/FileDropzone.js.map +1 -1
  93. package/dist/formatting.js +171 -143
  94. package/dist/formatting.js.map +1 -1
  95. package/dist/forms/FormWizard.js +18 -45
  96. package/dist/forms/FormWizard.js.map +1 -1
  97. package/dist/forms/auth/ChangePasswordForm.js +5 -4
  98. package/dist/forms/auth/ChangePasswordForm.js.map +1 -1
  99. package/dist/forms/auth/ForgotPasswordForm.js +2 -2
  100. package/dist/forms/auth/ForgotPasswordForm.js.map +1 -1
  101. package/dist/forms/auth/SignInForm.js +2 -2
  102. package/dist/forms/auth/SignInForm.js.map +1 -1
  103. package/dist/forms/security/SecondStepForm.js +2 -2
  104. package/dist/forms/security/SecondStepForm.js.map +1 -1
  105. package/dist/forms/security/TwoStepVerificationCard.js +8 -6
  106. package/dist/forms/security/TwoStepVerificationCard.js.map +1 -1
  107. package/dist/hookForm/FormWizard.js +59 -0
  108. package/dist/hookForm/FormWizard.js.map +1 -0
  109. package/dist/inbox/InboxLayout.js +44 -8
  110. package/dist/inbox/InboxLayout.js.map +1 -1
  111. package/dist/inbox/MessageList.js +71 -21
  112. package/dist/inbox/MessageList.js.map +1 -1
  113. package/dist/inbox/MessageView.js +80 -29
  114. package/dist/inbox/MessageView.js.map +1 -1
  115. package/dist/inbox/inboxDates.js +4 -3
  116. package/dist/inbox/inboxDates.js.map +1 -1
  117. package/dist/index.js +157 -133
  118. package/dist/index.js.map +1 -1
  119. package/dist/inputs/CatalogInput.js +8 -4
  120. package/dist/inputs/CatalogInput.js.map +1 -1
  121. package/dist/inputs/ColorInput.js +3 -2
  122. package/dist/inputs/ColorInput.js.map +1 -1
  123. package/dist/inputs/FormSection.js +3 -3
  124. package/dist/inputs/FormSection.js.map +1 -1
  125. package/dist/inputs/NumberInput.js +3 -2
  126. package/dist/inputs/NumberInput.js.map +1 -1
  127. package/dist/inputs/SelectInput.js +29 -11
  128. package/dist/inputs/SelectInput.js.map +1 -1
  129. package/dist/inputs/TextInput.js +6 -2
  130. package/dist/inputs/TextInput.js.map +1 -1
  131. package/dist/inputs/WizardLayout.js +54 -0
  132. package/dist/inputs/WizardLayout.js.map +1 -0
  133. package/dist/kanban/KanbanBoard.js +239 -14
  134. package/dist/kanban/KanbanBoard.js.map +1 -1
  135. package/dist/kanban/kanbanCards.js +19 -0
  136. package/dist/kanban/kanbanCards.js.map +1 -0
  137. package/dist/kanban.js +7 -1
  138. package/dist/kanban.js.map +1 -1
  139. package/dist/locales/en.js +74 -1
  140. package/dist/locales/en.js.map +1 -1
  141. package/dist/locales/es.js +74 -1
  142. package/dist/locales/es.js.map +1 -1
  143. package/dist/maps/MapView.js +28 -8
  144. package/dist/maps/MapView.js.map +1 -1
  145. package/dist/observability/usePublishedBuildWatch.js +4 -3
  146. package/dist/observability/usePublishedBuildWatch.js.map +1 -1
  147. package/dist/pwa/InstallAppSetting.js +6 -5
  148. package/dist/pwa/InstallAppSetting.js.map +1 -1
  149. package/dist/pwa/InstallInstructionsDialog.js +2 -2
  150. package/dist/pwa/InstallInstructionsDialog.js.map +1 -1
  151. package/dist/pwa/InstallPromptBanner.js +15 -9
  152. package/dist/pwa/InstallPromptBanner.js.map +1 -1
  153. package/dist/pwa/useInstallPrompt.js +2 -1
  154. package/dist/pwa/useInstallPrompt.js.map +1 -1
  155. package/dist/react-hook-form.js +6 -4
  156. package/dist/react-hook-form.js.map +1 -1
  157. package/dist/security/ActivityLog.js +4 -3
  158. package/dist/security/ActivityLog.js.map +1 -1
  159. package/dist/security/RecoveryCodesDialog.js +4 -3
  160. package/dist/security/RecoveryCodesDialog.js.map +1 -1
  161. package/dist/security/SessionsCard.js +8 -6
  162. package/dist/security/SessionsCard.js.map +1 -1
  163. package/dist/table.js +2 -0
  164. package/dist/table.js.map +1 -1
  165. package/dist/theme/components/MuiDialog.js +13 -0
  166. package/dist/theme/components/MuiDialog.js.map +1 -0
  167. package/dist/theme/createToolkitTheme.js.map +1 -1
  168. package/dist/theme/toolkitComponents.js +2 -0
  169. package/dist/theme/toolkitComponents.js.map +1 -1
  170. package/dist/toolkit/ComponentTexts.js +13 -0
  171. package/dist/toolkit/ComponentTexts.js.map +1 -0
  172. package/dist/toolkit/ToolkitProvider.js +36 -28
  173. package/dist/toolkit/ToolkitProvider.js.map +1 -1
  174. package/dist/toolkit/browserNavigation.js +19 -20
  175. package/dist/toolkit/browserNavigation.js.map +1 -1
  176. package/dist/toolkit/componentTextsContext.js +15 -0
  177. package/dist/toolkit/componentTextsContext.js.map +1 -0
  178. package/dist/toolkit/hashNavigation.js +40 -0
  179. package/dist/toolkit/hashNavigation.js.map +1 -0
  180. package/dist/toolkit/navigationPaths.js +49 -0
  181. package/dist/toolkit/navigationPaths.js.map +1 -0
  182. package/dist/toolkit/toolkitSettings.js +24 -4
  183. package/dist/toolkit/toolkitSettings.js.map +1 -1
  184. package/dist/toolkit/useFormatters.js +12 -0
  185. package/dist/toolkit/useFormatters.js.map +1 -0
  186. package/dist/toolkit/useToolkitTranslation.js +11 -7
  187. package/dist/toolkit/useToolkitTranslation.js.map +1 -1
  188. package/dist/types/DataTable/AuditTrailTable.d.ts +3 -1
  189. package/dist/types/DataTable/EmptyState.d.ts +7 -2
  190. package/dist/types/DataTable/FilterChips.d.ts +3 -1
  191. package/dist/types/DataTable/GoToPage.d.ts +7 -2
  192. package/dist/types/DataTable/SelectionBar.d.ts +32 -0
  193. package/dist/types/DataTable/ServerTable.d.ts +64 -3
  194. package/dist/types/DataTable/index.d.ts +1 -1
  195. package/dist/types/DataTable/rowSelection.d.ts +113 -0
  196. package/dist/types/access/PermissionMatrix.d.ts +3 -1
  197. package/dist/types/account/AccountSecuritySection.d.ts +3 -1
  198. package/dist/types/account/ForgotPasswordScreen.d.ts +3 -1
  199. package/dist/types/account/ProfileScreen.d.ts +31 -1
  200. package/dist/types/account/ResetPasswordScreen.d.ts +3 -1
  201. package/dist/types/account/SignInScreen.d.ts +3 -1
  202. package/dist/types/account/accountConnector.d.ts +7 -2
  203. package/dist/types/adapters/reactRouterRoutes.d.ts +14 -2
  204. package/dist/types/api/createApiClient.d.ts +29 -1
  205. package/dist/types/appShell/AppLayout.d.ts +11 -1
  206. package/dist/types/appShell/AppearanceSettings.d.ts +8 -1
  207. package/dist/types/appShell/BarActionButton.d.ts +2 -2
  208. package/dist/types/appShell/BarActions.d.ts +6 -1
  209. package/dist/types/appShell/BarCapsule.d.ts +8 -0
  210. package/dist/types/appShell/BarSearch.d.ts +24 -0
  211. package/dist/types/appShell/MobileTabBar.d.ts +4 -1
  212. package/dist/types/appShell/shellSizes.d.ts +1 -0
  213. package/dist/types/auth/PasswordChecklist.d.ts +3 -1
  214. package/dist/types/calendar/EventCalendar.d.ts +18 -1
  215. package/dist/types/calendar/EventDetails.d.ts +36 -0
  216. package/dist/types/calendar/calendarEvents.d.ts +27 -0
  217. package/dist/types/calendar.d.ts +2 -0
  218. package/dist/types/charts.d.ts +4 -0
  219. package/dist/types/datePickers/DateInput.d.ts +3 -1
  220. package/dist/types/downloads/DownloadManager.d.ts +7 -1
  221. package/dist/types/downloads/downloadQueue.d.ts +8 -3
  222. package/dist/types/downloads/fileTypes.d.ts +39 -0
  223. package/dist/types/downloads/index.d.ts +1 -0
  224. package/dist/types/feedback/ConfirmDialog.d.ts +3 -1
  225. package/dist/types/feedback/ModalWaiting.d.ts +7 -2
  226. package/dist/types/feedback/NewBuildNotice.d.ts +3 -1
  227. package/dist/types/filepond/FileDropzone.d.ts +3 -1
  228. package/dist/types/formatting.d.ts +45 -90
  229. package/dist/types/forms/FormWizard.d.ts +3 -1
  230. package/dist/types/forms/auth/ChangePasswordForm.d.ts +3 -1
  231. package/dist/types/forms/auth/ForgotPasswordForm.d.ts +3 -1
  232. package/dist/types/forms/auth/SignInForm.d.ts +3 -1
  233. package/dist/types/forms/security/SecondStepForm.d.ts +3 -1
  234. package/dist/types/forms/security/TwoStepVerificationCard.d.ts +3 -1
  235. package/dist/types/hookForm/FormWizard.d.ts +22 -0
  236. package/dist/types/inbox/InboxLayout.d.ts +14 -2
  237. package/dist/types/inbox/MessageList.d.ts +30 -5
  238. package/dist/types/inbox/MessageView.d.ts +35 -3
  239. package/dist/types/inbox/inboxDates.d.ts +6 -1
  240. package/dist/types/index.d.ts +12 -5
  241. package/dist/types/inputs/CatalogInput.d.ts +9 -5
  242. package/dist/types/inputs/ColorInput.d.ts +3 -1
  243. package/dist/types/inputs/FormSection.d.ts +2 -2
  244. package/dist/types/inputs/NumberInput.d.ts +3 -1
  245. package/dist/types/inputs/SelectInput.d.ts +9 -5
  246. package/dist/types/inputs/TextInput.d.ts +3 -1
  247. package/dist/types/inputs/WizardLayout.d.ts +34 -0
  248. package/dist/types/kanban/KanbanBoard.d.ts +61 -6
  249. package/dist/types/kanban/kanbanCards.d.ts +22 -0
  250. package/dist/types/kanban.d.ts +1 -0
  251. package/dist/types/locales/en.d.ts +77 -0
  252. package/dist/types/locales/es.d.ts +77 -0
  253. package/dist/types/maps/MapView.d.ts +12 -3
  254. package/dist/types/observability/usePublishedBuildWatch.d.ts +3 -1
  255. package/dist/types/pwa/InstallAppSetting.d.ts +7 -1
  256. package/dist/types/pwa/InstallInstructionsDialog.d.ts +3 -1
  257. package/dist/types/pwa/InstallPromptBanner.d.ts +10 -1
  258. package/dist/types/pwa/useInstallPrompt.d.ts +3 -2
  259. package/dist/types/react-hook-form.d.ts +1 -0
  260. package/dist/types/security/ActivityLog.d.ts +3 -1
  261. package/dist/types/security/RecoveryCodesDialog.d.ts +6 -1
  262. package/dist/types/security/SessionsCard.d.ts +3 -1
  263. package/dist/types/table.d.ts +3 -1
  264. package/dist/types/theme/components/MuiDialog.d.ts +9 -0
  265. package/dist/types/toolkit/ComponentTexts.d.ts +11 -0
  266. package/dist/types/toolkit/ToolkitProvider.d.ts +18 -2
  267. package/dist/types/toolkit/browserNavigation.d.ts +23 -28
  268. package/dist/types/toolkit/componentTextsContext.d.ts +20 -0
  269. package/dist/types/toolkit/hashNavigation.d.ts +12 -0
  270. package/dist/types/toolkit/navigationPaths.d.ts +43 -0
  271. package/dist/types/toolkit/toolkitSettings.d.ts +49 -4
  272. package/dist/types/toolkit/useFormatters.d.ts +6 -0
  273. package/dist/types/toolkit/useToolkitTranslation.d.ts +2 -1
  274. package/dist/types/ui/actions/ActionsMenu.d.ts +20 -0
  275. package/dist/types/ui/actions/CopyButton.d.ts +3 -1
  276. package/dist/types/ui/actions/DownloadMenu.d.ts +8 -3
  277. package/dist/types/ui/actions/SecretField.d.ts +5 -1
  278. package/dist/types/ui/actions/SelectionActions.d.ts +39 -0
  279. package/dist/types/ui/filters/DateRangeFilter.d.ts +3 -1
  280. package/dist/types/ui/filters/QuickFilters.d.ts +3 -1
  281. package/dist/types/ui/index.d.ts +1 -1
  282. package/dist/types/ui/labels/tagOf.d.ts +13 -0
  283. package/dist/types/ui/layout/CollapsibleSection.d.ts +4 -1
  284. package/dist/types/ui/layout/ContentDialog.d.ts +3 -1
  285. package/dist/types/ui/layout/ErrorPage.d.ts +14 -1
  286. package/dist/types/ui/layout/FullPageLoader.d.ts +7 -1
  287. package/dist/types/ui/layout/PageHeading.d.ts +3 -1
  288. package/dist/types/ui/metrics/BarList.d.ts +7 -1
  289. package/dist/types/ui/metrics/CalendarHeatmap.d.ts +7 -1
  290. package/dist/types/ui/metrics/ChartTooltip.d.ts +9 -3
  291. package/dist/types/ui/metrics/ColumnChart.d.ts +23 -11
  292. package/dist/types/ui/metrics/LineChart.d.ts +41 -0
  293. package/dist/types/ui/metrics/MetricCard.d.ts +9 -1
  294. package/dist/types/ui/metrics/TableToggle.d.ts +9 -2
  295. package/dist/types/ui/metrics/chartParts.d.ts +112 -0
  296. package/dist/types/ui/navigation/BackButton.d.ts +3 -1
  297. package/dist/types/ui/navigation/ColorSchemeToggle.d.ts +8 -2
  298. package/dist/types/ui/navigation/FontSizeToggle.d.ts +7 -2
  299. package/dist/types/ui/navigation/LanguageToggle.d.ts +8 -3
  300. package/dist/types/ui/navigation/PageBreadcrumbs.d.ts +5 -2
  301. package/dist/types/ui/timeline/GroupedTimeline.d.ts +4 -1
  302. package/dist/types/upload/FileDropzone.d.ts +3 -1
  303. package/dist/ui/actions/ActionsMenu.js +51 -0
  304. package/dist/ui/actions/ActionsMenu.js.map +1 -0
  305. package/dist/ui/actions/CopyButton.js +2 -2
  306. package/dist/ui/actions/CopyButton.js.map +1 -1
  307. package/dist/ui/actions/DownloadMenu.js +11 -12
  308. package/dist/ui/actions/DownloadMenu.js.map +1 -1
  309. package/dist/ui/actions/SecretField.js +8 -4
  310. package/dist/ui/actions/SecretField.js.map +1 -1
  311. package/dist/ui/actions/SelectionActions.js +142 -0
  312. package/dist/ui/actions/SelectionActions.js.map +1 -0
  313. package/dist/ui/filters/DateRangeFilter.js +4 -3
  314. package/dist/ui/filters/DateRangeFilter.js.map +1 -1
  315. package/dist/ui/filters/QuickFilters.js +2 -2
  316. package/dist/ui/filters/QuickFilters.js.map +1 -1
  317. package/dist/ui/labels/tagOf.js +7 -0
  318. package/dist/ui/labels/tagOf.js.map +1 -0
  319. package/dist/ui/layout/CollapsibleSection.js +6 -2
  320. package/dist/ui/layout/CollapsibleSection.js.map +1 -1
  321. package/dist/ui/layout/ContentDialog.js +2 -2
  322. package/dist/ui/layout/ContentDialog.js.map +1 -1
  323. package/dist/ui/layout/ErrorPage.js +21 -7
  324. package/dist/ui/layout/ErrorPage.js.map +1 -1
  325. package/dist/ui/layout/FullPageLoader.js +2 -2
  326. package/dist/ui/layout/FullPageLoader.js.map +1 -1
  327. package/dist/ui/layout/PageHeading.js +2 -2
  328. package/dist/ui/layout/PageHeading.js.map +1 -1
  329. package/dist/ui/metrics/BarList.js +18 -13
  330. package/dist/ui/metrics/BarList.js.map +1 -1
  331. package/dist/ui/metrics/CalendarHeatmap.js +11 -6
  332. package/dist/ui/metrics/CalendarHeatmap.js.map +1 -1
  333. package/dist/ui/metrics/ChartTooltip.js +1 -1
  334. package/dist/ui/metrics/ChartTooltip.js.map +1 -1
  335. package/dist/ui/metrics/ColumnChart.js +59 -63
  336. package/dist/ui/metrics/ColumnChart.js.map +1 -1
  337. package/dist/ui/metrics/LineChart.js +117 -0
  338. package/dist/ui/metrics/LineChart.js.map +1 -0
  339. package/dist/ui/metrics/MetricCard.js +41 -8
  340. package/dist/ui/metrics/MetricCard.js.map +1 -1
  341. package/dist/ui/metrics/TableToggle.js +4 -2
  342. package/dist/ui/metrics/TableToggle.js.map +1 -1
  343. package/dist/ui/metrics/chartParts.js +93 -0
  344. package/dist/ui/metrics/chartParts.js.map +1 -0
  345. package/dist/ui/navigation/BackButton.js +2 -2
  346. package/dist/ui/navigation/BackButton.js.map +1 -1
  347. package/dist/ui/navigation/ColorSchemeToggle.js +6 -3
  348. package/dist/ui/navigation/ColorSchemeToggle.js.map +1 -1
  349. package/dist/ui/navigation/FontSizeToggle.js +3 -2
  350. package/dist/ui/navigation/FontSizeToggle.js.map +1 -1
  351. package/dist/ui/navigation/LanguageToggle.js +40 -19
  352. package/dist/ui/navigation/LanguageToggle.js.map +1 -1
  353. package/dist/ui/navigation/PageBreadcrumbs.js +2 -2
  354. package/dist/ui/navigation/PageBreadcrumbs.js.map +1 -1
  355. package/dist/ui/timeline/GroupedTimeline.js +10 -8
  356. package/dist/ui/timeline/GroupedTimeline.js.map +1 -1
  357. package/dist/upload/FileDropzone.js +5 -3
  358. package/dist/upload/FileDropzone.js.map +1 -1
  359. package/package.json +14 -6
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { jsxs, Fragment, jsx } from "react/jsx-runtime";
2
+ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
3
3
  import { useState } from "react";
4
4
  import Alert from "@mui/material/Alert";
5
5
  import Button from "@mui/material/Button";
@@ -13,27 +13,33 @@ import { useToolkitTranslation } from "../toolkit/useToolkitTranslation.js";
13
13
  import { INSTALL_VARIANT } from "./installInstructions.js";
14
14
  import InstallInstructionsDialog from "./InstallInstructionsDialog.js";
15
15
  import { useInstallPrompt } from "./useInstallPrompt.js";
16
+ import ComponentTexts from "../toolkit/ComponentTexts.js";
16
17
  const TAB_BAR_GAP_PIXELS = 8;
17
- function InstallPromptBanner({ message }) {
18
- const { t } = useToolkitTranslation("common");
18
+ function InstallPromptBanner({ message, onInstallChoice, onShowSteps, onDismiss, texts }) {
19
+ const { t } = useToolkitTranslation("common", texts);
19
20
  const { variant, dismissed, promptInstall, dismiss } = useInstallPrompt();
20
21
  const [stepsOpen, setStepsOpen] = useState(false);
21
22
  if (!variant || dismissed) {
22
23
  return null;
23
24
  }
24
25
  const isNative = variant === INSTALL_VARIANT.native;
25
- const handleAction = () => {
26
+ const handleAction = async () => {
26
27
  if (isNative) {
27
- promptInstall();
28
+ onInstallChoice?.(await promptInstall());
28
29
  } else {
29
30
  setStepsOpen(true);
31
+ onShowSteps?.();
30
32
  }
31
33
  };
34
+ const handleDismiss = () => {
35
+ dismiss();
36
+ onDismiss?.();
37
+ };
32
38
  const handleStepsClose = () => {
33
39
  setStepsOpen(false);
34
- dismiss();
40
+ handleDismiss();
35
41
  };
36
- return /* @__PURE__ */ jsxs(Fragment, { children: [
42
+ return /* @__PURE__ */ jsx(ComponentTexts, { namespace: "common", texts, children: /* @__PURE__ */ jsxs(Fragment, { children: [
37
43
  /* @__PURE__ */ jsx(
38
44
  Snackbar,
39
45
  {
@@ -48,7 +54,7 @@ function InstallPromptBanner({ message }) {
48
54
  sx: { width: "100%", alignItems: "center" },
49
55
  action: /* @__PURE__ */ jsxs(Fragment, { children: [
50
56
  /* @__PURE__ */ jsx(Button, { color: "inherit", size: "small", onClick: handleAction, children: isNative ? t("install.action") : t("install.howTo") }),
51
- /* @__PURE__ */ jsx(IconButton, { size: "small", color: "inherit", "aria-label": t("install.dismiss"), onClick: dismiss, children: /* @__PURE__ */ jsx(CloseRoundedIcon, { fontSize: "small" }) })
57
+ /* @__PURE__ */ jsx(IconButton, { size: "small", color: "inherit", "aria-label": t("install.dismiss"), onClick: handleDismiss, children: /* @__PURE__ */ jsx(CloseRoundedIcon, { fontSize: "small" }) })
52
58
  ] }),
53
59
  children: message ?? t("install.banner")
54
60
  }
@@ -56,7 +62,7 @@ function InstallPromptBanner({ message }) {
56
62
  }
57
63
  ),
58
64
  /* @__PURE__ */ jsx(InstallInstructionsDialog, { variant, open: stepsOpen, onClose: handleStepsClose })
59
- ] });
65
+ ] }) });
60
66
  }
61
67
  export {
62
68
  InstallPromptBanner as default
@@ -1 +1 @@
1
- {"version":3,"file":"InstallPromptBanner.js","sources":["../../src/pwa/InstallPromptBanner.jsx"],"sourcesContent":["// La invitación a instalar la app, abajo al centro. Según el equipo (useInstallPrompt): con el diálogo del navegador, el\n// botón «Instalar»; en iPhone, iPad y Firefox de Android, «Cómo instalar» abre los pasos (InstallInstructionsDialog).\n// Ya instalada, sin manera de instalar o después de «Ahora no», no pinta nada; quien se arrepienta la encuentra en el menú\n// de la cuenta (InstallAppSetting).\n//\n// Color de información, no de advertencia: instalar es una sugerencia, no un problema. En el teléfono con vidrio va encima\n// de la barra de pestañas; sin ella, respeta la zona del gesto de inicio del iPhone.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useState} from 'react';\nimport Alert from '@mui/material/Alert';\nimport Button from '@mui/material/Button';\nimport IconButton from '@mui/material/IconButton';\nimport Snackbar from '@mui/material/Snackbar';\nimport CloseRoundedIcon from '@mui/icons-material/CloseRounded';\nimport InstallMobileRoundedIcon from '@mui/icons-material/InstallMobileRounded';\n\nimport {aboveMobileTabBarOf} from '@/appShell/shellSizes.js';\nimport {usesMobileTabBar} from '@/appShell/tabBarRule.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {INSTALL_VARIANT} from '@/pwa/installInstructions.js';\nimport InstallInstructionsDialog from '@/pwa/InstallInstructionsDialog.jsx';\nimport {useInstallPrompt} from '@/pwa/useInstallPrompt.js';\n\n// lo que se separa de la barra de pestañas (px)\nconst TAB_BAR_GAP_PIXELS = 8;\n\n/**\n * @param {object} [props]\n * @param {string} [props.message] ya traducido; por omisión el del paquete con el nombre de la app (appName de ToolkitProvider)\n */\nexport default function InstallPromptBanner({message}) {\n const {t} = useToolkitTranslation('common');\n const {variant, dismissed, promptInstall, dismiss} = useInstallPrompt();\n const [stepsOpen, setStepsOpen] = useState(false);\n\n // nada que ofrecer o ya dijo «Ahora no»\n if (!variant || dismissed) {\n return null;\n }\n\n const isNative = variant === INSTALL_VARIANT.native;\n\n const handleAction = () => {\n if (isNative) {\n promptInstall();\n } else {\n setStepsOpen(true);\n }\n };\n\n // si ya vio los pasos, no se le vuelven a ofrecer en cada visita\n const handleStepsClose = () => {\n setStepsOpen(false);\n dismiss();\n };\n\n return (\n <>\n <Snackbar open anchorOrigin={{vertical: 'bottom', horizontal: 'center'}}\n sx={(theme) => (usesMobileTabBar(theme)\n ? aboveMobileTabBarOf(TAB_BAR_GAP_PIXELS)\n : {mb: 'env(safe-area-inset-bottom)'})}>\n <Alert severity=\"info\" icon={<InstallMobileRoundedIcon />} sx={{width: '100%', alignItems: 'center'}}\n action={(\n <>\n <Button color=\"inherit\" size=\"small\" onClick={handleAction}>\n {isNative ? t('install.action') : t('install.howTo')}\n </Button>\n <IconButton size=\"small\" color=\"inherit\" aria-label={t('install.dismiss')} onClick={dismiss}>\n <CloseRoundedIcon fontSize=\"small\" />\n </IconButton>\n </>\n )}>\n {message ?? t('install.banner')}\n </Alert>\n </Snackbar>\n <InstallInstructionsDialog variant={variant} open={stepsOpen} onClose={handleStepsClose} />\n </>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;AAyBA,MAAM,qBAAqB;AAM3B,SAAwB,oBAAoB,EAAC,WAAU;AACnD,QAAM,EAAC,EAAA,IAAK,sBAAsB,QAAQ;AAC1C,QAAM,EAAC,SAAS,WAAW,eAAe,QAAA,IAAW,iBAAA;AACrD,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,KAAK;AAGhD,MAAI,CAAC,WAAW,WAAW;AACvB,WAAO;AAAA,EACX;AAEA,QAAM,WAAW,YAAY,gBAAgB;AAE7C,QAAM,eAAe,MAAM;AACvB,QAAI,UAAU;AACV,oBAAA;AAAA,IACJ,OAAO;AACH,mBAAa,IAAI;AAAA,IACrB;AAAA,EACJ;AAGA,QAAM,mBAAmB,MAAM;AAC3B,iBAAa,KAAK;AAClB,YAAA;AAAA,EACJ;AAEA,SACI,qBAAA,UAAA,EACI,UAAA;AAAA,IAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QAAS,MAAI;AAAA,QAAC,cAAc,EAAC,UAAU,UAAU,YAAY,SAAA;AAAA,QACpD,IAAI,CAAC,UAAW,iBAAiB,KAAK,IAChC,oBAAoB,kBAAkB,IACtC,EAAC,IAAI,8BAAA;AAAA,QACjB,UAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YAAM,UAAS;AAAA,YAAO,0BAAO,0BAAA,EAAyB;AAAA,YAAI,IAAI,EAAC,OAAO,QAAQ,YAAY,SAAA;AAAA,YACpF,QACI,qBAAA,UAAA,EACI,UAAA;AAAA,cAAA,oBAAC,QAAA,EAAO,OAAM,WAAU,MAAK,SAAQ,SAAS,cACzC,UAAA,WAAW,EAAE,gBAAgB,IAAI,EAAE,eAAe,GACvD;AAAA,kCACC,YAAA,EAAW,MAAK,SAAQ,OAAM,WAAU,cAAY,EAAE,iBAAiB,GAAG,SAAS,SAChF,UAAA,oBAAC,kBAAA,EAAiB,UAAS,SAAQ,EAAA,CACvC;AAAA,YAAA,GACJ;AAAA,YAEN,UAAA,WAAW,EAAE,gBAAgB;AAAA,UAAA;AAAA,QAAA;AAAA,MAClC;AAAA,IAAA;AAAA,wBAEH,2BAAA,EAA0B,SAAkB,MAAM,WAAW,SAAS,iBAAA,CAAkB;AAAA,EAAA,GAC7F;AAER;"}
1
+ {"version":3,"file":"InstallPromptBanner.js","sources":["../../src/pwa/InstallPromptBanner.jsx"],"sourcesContent":["// La invitación a instalar la app, abajo al centro. Según el equipo (useInstallPrompt): con el diálogo del navegador, el\n// botón «Instalar»; en iPhone, iPad y Firefox de Android, «Cómo instalar» abre los pasos (InstallInstructionsDialog).\n// Ya instalada, sin manera de instalar o después de «Ahora no», no pinta nada; quien se arrepienta la encuentra en el menú\n// de la cuenta (InstallAppSetting).\n//\n// Color de información, no de advertencia: instalar es una sugerencia, no un problema. En el teléfono con vidrio va encima\n// de la barra de pestañas; sin ella, respeta la zona del gesto de inicio del iPhone.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useState} from 'react';\nimport Alert from '@mui/material/Alert';\nimport Button from '@mui/material/Button';\nimport IconButton from '@mui/material/IconButton';\nimport Snackbar from '@mui/material/Snackbar';\nimport CloseRoundedIcon from '@mui/icons-material/CloseRounded';\nimport InstallMobileRoundedIcon from '@mui/icons-material/InstallMobileRounded';\n\nimport {aboveMobileTabBarOf} from '@/appShell/shellSizes.js';\nimport {usesMobileTabBar} from '@/appShell/tabBarRule.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {INSTALL_VARIANT} from '@/pwa/installInstructions.js';\nimport InstallInstructionsDialog from '@/pwa/InstallInstructionsDialog.jsx';\nimport {useInstallPrompt} from '@/pwa/useInstallPrompt.js';\nimport ComponentTexts from '@/toolkit/ComponentTexts.jsx';\n\n// lo que se separa de la barra de pestañas (px)\nconst TAB_BAR_GAP_PIXELS = 8;\n\n/**\n * @param {object} [props]\n * @param {string} [props.message] ya traducido; por omisión el del paquete con el nombre de la app (appName de ToolkitProvider)\n * @param {(outcome: 'accepted'|'dismissed'|null) => void} [props.onInstallChoice] lo que contestó la persona al diálogo de\n * instalar del navegador (Chrome, Edge, Android)\n * @param {() => void} [props.onShowSteps] abrió los pasos para instalar (iPhone, iPad, Firefox de Android)\n * @param {() => void} [props.onDismiss] dijo «Ahora no» (o cerró los pasos): no se le vuelve a ofrecer en este navegador\n * @param {object} [props.texts] los textos que cambian, con la forma de `common` en los textos del paquete\n */\nexport default function InstallPromptBanner({message, onInstallChoice, onShowSteps, onDismiss, texts}) {\n const {t} = useToolkitTranslation('common', texts);\n const {variant, dismissed, promptInstall, dismiss} = useInstallPrompt();\n const [stepsOpen, setStepsOpen] = useState(false);\n\n // nada que ofrecer o ya dijo «Ahora no»\n if (!variant || dismissed) {\n return null;\n }\n\n const isNative = variant === INSTALL_VARIANT.native;\n\n const handleAction = async () => {\n if (isNative) {\n onInstallChoice?.(await promptInstall());\n } else {\n setStepsOpen(true);\n onShowSteps?.();\n }\n };\n const handleDismiss = () => {\n dismiss();\n onDismiss?.();\n };\n\n // si ya vio los pasos, no se le vuelven a ofrecer en cada visita\n const handleStepsClose = () => {\n setStepsOpen(false);\n handleDismiss();\n };\n\n return (\n <ComponentTexts namespace=\"common\" texts={texts}>\n <>\n <Snackbar open anchorOrigin={{vertical: 'bottom', horizontal: 'center'}}\n sx={(theme) => (usesMobileTabBar(theme)\n ? aboveMobileTabBarOf(TAB_BAR_GAP_PIXELS)\n : {mb: 'env(safe-area-inset-bottom)'})}>\n <Alert severity=\"info\" icon={<InstallMobileRoundedIcon />} sx={{width: '100%', alignItems: 'center'}}\n action={(\n <>\n <Button color=\"inherit\" size=\"small\" onClick={handleAction}>\n {isNative ? t('install.action') : t('install.howTo')}\n </Button>\n <IconButton size=\"small\" color=\"inherit\" aria-label={t('install.dismiss')} onClick={handleDismiss}>\n <CloseRoundedIcon fontSize=\"small\" />\n </IconButton>\n </>\n )}>\n {message ?? t('install.banner')}\n </Alert>\n </Snackbar>\n <InstallInstructionsDialog variant={variant} open={stepsOpen} onClose={handleStepsClose} />\n </>\n </ComponentTexts>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;AA0BA,MAAM,qBAAqB;AAW3B,SAAwB,oBAAoB,EAAC,SAAS,iBAAiB,aAAa,WAAW,SAAQ;AACnG,QAAM,EAAC,EAAA,IAAK,sBAAsB,UAAU,KAAK;AACjD,QAAM,EAAC,SAAS,WAAW,eAAe,QAAA,IAAW,iBAAA;AACrD,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,KAAK;AAGhD,MAAI,CAAC,WAAW,WAAW;AACvB,WAAO;AAAA,EACX;AAEA,QAAM,WAAW,YAAY,gBAAgB;AAE7C,QAAM,eAAe,YAAY;AAC7B,QAAI,UAAU;AACV,wBAAkB,MAAM,eAAe;AAAA,IAC3C,OAAO;AACH,mBAAa,IAAI;AACjB,oBAAA;AAAA,IACJ;AAAA,EACJ;AACA,QAAM,gBAAgB,MAAM;AACxB,YAAA;AACA,gBAAA;AAAA,EACJ;AAGA,QAAM,mBAAmB,MAAM;AAC3B,iBAAa,KAAK;AAClB,kBAAA;AAAA,EACJ;AAEA,SACI,oBAAC,gBAAA,EAAe,WAAU,UAAS,OAC/B,UAAA,qBAAA,UAAA,EACI,UAAA;AAAA,IAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QAAS,MAAI;AAAA,QAAC,cAAc,EAAC,UAAU,UAAU,YAAY,SAAA;AAAA,QACpD,IAAI,CAAC,UAAW,iBAAiB,KAAK,IAChC,oBAAoB,kBAAkB,IACtC,EAAC,IAAI,8BAAA;AAAA,QACjB,UAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YAAM,UAAS;AAAA,YAAO,0BAAO,0BAAA,EAAyB;AAAA,YAAI,IAAI,EAAC,OAAO,QAAQ,YAAY,SAAA;AAAA,YACpF,QACI,qBAAA,UAAA,EACI,UAAA;AAAA,cAAA,oBAAC,QAAA,EAAO,OAAM,WAAU,MAAK,SAAQ,SAAS,cACzC,UAAA,WAAW,EAAE,gBAAgB,IAAI,EAAE,eAAe,GACvD;AAAA,kCACC,YAAA,EAAW,MAAK,SAAQ,OAAM,WAAU,cAAY,EAAE,iBAAiB,GAAG,SAAS,eAChF,UAAA,oBAAC,kBAAA,EAAiB,UAAS,SAAQ,EAAA,CACvC;AAAA,YAAA,GACJ;AAAA,YAEN,UAAA,WAAW,EAAE,gBAAgB;AAAA,UAAA;AAAA,QAAA;AAAA,MAClC;AAAA,IAAA;AAAA,wBAEH,2BAAA,EAA0B,SAAkB,MAAM,WAAW,SAAS,iBAAA,CAAkB;AAAA,EAAA,EAAA,CAC7F,EAAA,CACJ;AAER;"}
@@ -42,12 +42,13 @@ function useInstallPrompt() {
42
42
  }, []);
43
43
  const promptInstall = useCallback(async () => {
44
44
  if (!deferredPrompt) {
45
- return;
45
+ return null;
46
46
  }
47
47
  deferredPrompt.prompt();
48
48
  const { outcome } = await deferredPrompt.userChoice;
49
49
  log.info("respuesta al diálogo de instalar", { outcome });
50
50
  setDeferredPrompt(null);
51
+ return outcome;
51
52
  }, [deferredPrompt]);
52
53
  const dismiss = useCallback(() => {
53
54
  setDismissed(true);
@@ -1 +1 @@
1
- {"version":3,"file":"useInstallPrompt.js","sources":["../../src/pwa/useInstallPrompt.js"],"sourcesContent":["// La invitación a instalar la app (agregar a la pantalla de inicio): escucha al navegador, decide qué variante enseñar\n// (installInstructions.js) y recuerda el «Ahora no». Es el de walk-ot, ya probado en iPhone y Android.\n//\n// El navegador NO tiene una sola manera de instalar:\n// - Android con Chromium (Chrome, Samsung Internet, Edge) avisa con beforeinstallprompt. Se guarda el aviso (preventDefault\n// quita su barrita) y se lanza desde NUESTRO botón.\n// - Safari de iOS nunca avisa: se instala a mano y el camino cambió en iOS 26.\n// - Firefox de Android tampoco avisa: a mano.\n//\n// El «Ahora no» se guarda en este navegador. Mientras no se lee (en el servidor y en el primer pintado) se supone que ya\n// lo dijo: así la invitación nunca parpadea al cargar ni cambia entre el servidor y el navegador.\n//\n// Es un gancho: function, como los demás del paquete.\nimport {useCallback, useEffect, useMemo, useState} from 'react';\n\nimport {createLogger} from '@/observability/logger.js';\nimport {detectPlatform, resolveInstallVariant} from '@/pwa/installInstructions.js';\n\nconst log = createLogger('pwa');\n\nconst DISMISS_STORAGE_KEY = 'calcita:installPromptDismissed';\nconst DISMISSED = '1';\n\n/** ¿Ya se abrió como app instalada? En el iPhone lo dice navigator.standalone; en los demás, el display-mode. */\nfunction isRunningInstalled() {\n return window.matchMedia?.('(display-mode: standalone)').matches === true || window.navigator.standalone === true;\n}\n\n/** El «Ahora no» guardado; con el almacenamiento bloqueado se toma como no dicho. */\nfunction readDismissed() {\n try {\n return window.localStorage.getItem(DISMISS_STORAGE_KEY) === DISMISSED;\n } catch {\n return false;\n }\n}\n\n/**\n * @returns {{variant: string|null, isInstalled: boolean, dismissed: boolean, dismiss: () => void, promptInstall: () => Promise<void>}}\n * variant: una de INSTALL_VARIANT o null (nada que ofrecer). NO toma en cuenta el «Ahora no»: la invitación sí lo respeta,\n * pero la entrada del menú de la cuenta no, porque ahí la persona la busca a propósito.\n */\nexport function useInstallPrompt() {\n const [deferredPrompt, setDeferredPrompt] = useState(null);\n const [isInstalled, setIsInstalled] = useState(false);\n const [platform, setPlatform] = useState(null);\n // se supone dicho hasta leer el navegador (ver arriba)\n const [dismissed, setDismissed] = useState(true);\n\n useEffect(() => {\n setPlatform(detectPlatform(window.navigator));\n setIsInstalled(isRunningInstalled());\n setDismissed(readDismissed());\n\n const handleBeforeInstall = (event) => {\n // el momento lo elige la persona con nuestro botón, no la barrita del navegador\n event.preventDefault();\n setDeferredPrompt(event);\n };\n const handleInstalled = () => {\n setIsInstalled(true);\n setDeferredPrompt(null);\n log.info('app instalada');\n };\n window.addEventListener('beforeinstallprompt', handleBeforeInstall);\n window.addEventListener('appinstalled', handleInstalled);\n return () => {\n window.removeEventListener('beforeinstallprompt', handleBeforeInstall);\n window.removeEventListener('appinstalled', handleInstalled);\n };\n }, []);\n\n // El diálogo del navegador sirve UNA vez: con cualquier respuesta se suelta. Cancelarlo no es «Ahora no» (eso solo lo\n // dice el botón de la invitación).\n const promptInstall = useCallback(async () => {\n if (!deferredPrompt) {\n return;\n }\n deferredPrompt.prompt();\n const {outcome} = await deferredPrompt.userChoice;\n log.info('respuesta al diálogo de instalar', {outcome});\n setDeferredPrompt(null);\n }, [deferredPrompt]);\n\n const dismiss = useCallback(() => {\n setDismissed(true);\n try {\n window.localStorage.setItem(DISMISS_STORAGE_KEY, DISMISSED);\n } catch {\n // almacenamiento bloqueado: se cierra igual, solo no se recuerda\n }\n }, []);\n\n const variant = useMemo(() => {\n if (!platform) {\n return null;\n }\n return resolveInstallVariant({platform, canPromptNative: deferredPrompt !== null, isInstalled});\n }, [platform, deferredPrompt, isInstalled]);\n\n return {variant, isInstalled, dismissed, dismiss, promptInstall};\n}\n"],"names":[],"mappings":";;;;AAkBA,MAAM,MAAM,aAAa,KAAK;AAE9B,MAAM,sBAAsB;AAC5B,MAAM,YAAY;AAGlB,SAAS,qBAAqB;AAC1B,SAAO,OAAO,aAAa,4BAA4B,EAAE,YAAY,QAAQ,OAAO,UAAU,eAAe;AACjH;AAGA,SAAS,gBAAgB;AACrB,MAAI;AACA,WAAO,OAAO,aAAa,QAAQ,mBAAmB,MAAM;AAAA,EAChE,QAAQ;AACJ,WAAO;AAAA,EACX;AACJ;AAOO,SAAS,mBAAmB;AAC/B,QAAM,CAAC,gBAAgB,iBAAiB,IAAI,SAAS,IAAI;AACzD,QAAM,CAAC,aAAa,cAAc,IAAI,SAAS,KAAK;AACpD,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,IAAI;AAE7C,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,IAAI;AAE/C,YAAU,MAAM;AACZ,gBAAY,eAAe,OAAO,SAAS,CAAC;AAC5C,mBAAe,mBAAkB,CAAE;AACnC,iBAAa,cAAa,CAAE;AAE5B,UAAM,sBAAsB,CAAC,UAAU;AAEnC,YAAM,eAAc;AACpB,wBAAkB,KAAK;AAAA,IAC3B;AACA,UAAM,kBAAkB,MAAM;AAC1B,qBAAe,IAAI;AACnB,wBAAkB,IAAI;AACtB,UAAI,KAAK,eAAe;AAAA,IAC5B;AACA,WAAO,iBAAiB,uBAAuB,mBAAmB;AAClE,WAAO,iBAAiB,gBAAgB,eAAe;AACvD,WAAO,MAAM;AACT,aAAO,oBAAoB,uBAAuB,mBAAmB;AACrE,aAAO,oBAAoB,gBAAgB,eAAe;AAAA,IAC9D;AAAA,EACJ,GAAG,CAAA,CAAE;AAIL,QAAM,gBAAgB,YAAY,YAAY;AAC1C,QAAI,CAAC,gBAAgB;AACjB;AAAA,IACJ;AACA,mBAAe,OAAM;AACrB,UAAM,EAAC,QAAO,IAAI,MAAM,eAAe;AACvC,QAAI,KAAK,oCAAoC,EAAC,QAAO,CAAC;AACtD,sBAAkB,IAAI;AAAA,EAC1B,GAAG,CAAC,cAAc,CAAC;AAEnB,QAAM,UAAU,YAAY,MAAM;AAC9B,iBAAa,IAAI;AACjB,QAAI;AACA,aAAO,aAAa,QAAQ,qBAAqB,SAAS;AAAA,IAC9D,QAAQ;AAAA,IAER;AAAA,EACJ,GAAG,CAAA,CAAE;AAEL,QAAM,UAAU,QAAQ,MAAM;AAC1B,QAAI,CAAC,UAAU;AACX,aAAO;AAAA,IACX;AACA,WAAO,sBAAsB,EAAC,UAAU,iBAAiB,mBAAmB,MAAM,YAAW,CAAC;AAAA,EAClG,GAAG,CAAC,UAAU,gBAAgB,WAAW,CAAC;AAE1C,SAAO,EAAC,SAAS,aAAa,WAAW,SAAS,cAAa;AACnE;"}
1
+ {"version":3,"file":"useInstallPrompt.js","sources":["../../src/pwa/useInstallPrompt.js"],"sourcesContent":["// La invitación a instalar la app (agregar a la pantalla de inicio): escucha al navegador, decide qué variante enseñar\n// (installInstructions.js) y recuerda el «Ahora no». Es el de walk-ot, ya probado en iPhone y Android.\n//\n// El navegador NO tiene una sola manera de instalar:\n// - Android con Chromium (Chrome, Samsung Internet, Edge) avisa con beforeinstallprompt. Se guarda el aviso (preventDefault\n// quita su barrita) y se lanza desde NUESTRO botón.\n// - Safari de iOS nunca avisa: se instala a mano y el camino cambió en iOS 26.\n// - Firefox de Android tampoco avisa: a mano.\n//\n// El «Ahora no» se guarda en este navegador. Mientras no se lee (en el servidor y en el primer pintado) se supone que ya\n// lo dijo: así la invitación nunca parpadea al cargar ni cambia entre el servidor y el navegador.\n//\n// Es un gancho: function, como los demás del paquete.\nimport {useCallback, useEffect, useMemo, useState} from 'react';\n\nimport {createLogger} from '@/observability/logger.js';\nimport {detectPlatform, resolveInstallVariant} from '@/pwa/installInstructions.js';\n\nconst log = createLogger('pwa');\n\nconst DISMISS_STORAGE_KEY = 'calcita:installPromptDismissed';\nconst DISMISSED = '1';\n\n/** ¿Ya se abrió como app instalada? En el iPhone lo dice navigator.standalone; en los demás, el display-mode. */\nfunction isRunningInstalled() {\n return window.matchMedia?.('(display-mode: standalone)').matches === true || window.navigator.standalone === true;\n}\n\n/** El «Ahora no» guardado; con el almacenamiento bloqueado se toma como no dicho. */\nfunction readDismissed() {\n try {\n return window.localStorage.getItem(DISMISS_STORAGE_KEY) === DISMISSED;\n } catch {\n return false;\n }\n}\n\n/**\n * @returns {{variant: string|null, isInstalled: boolean, dismissed: boolean, dismiss: () => void, promptInstall: () => Promise<'accepted'|'dismissed'|null>}}\n * promptInstall regresa lo que contestó la persona al diálogo del navegador (null: ya no había diálogo)\n * variant: una de INSTALL_VARIANT o null (nada que ofrecer). NO toma en cuenta el «Ahora no»: la invitación sí lo respeta,\n * pero la entrada del menú de la cuenta no, porque ahí la persona la busca a propósito.\n */\nexport function useInstallPrompt() {\n const [deferredPrompt, setDeferredPrompt] = useState(null);\n const [isInstalled, setIsInstalled] = useState(false);\n const [platform, setPlatform] = useState(null);\n // se supone dicho hasta leer el navegador (ver arriba)\n const [dismissed, setDismissed] = useState(true);\n\n useEffect(() => {\n setPlatform(detectPlatform(window.navigator));\n setIsInstalled(isRunningInstalled());\n setDismissed(readDismissed());\n\n const handleBeforeInstall = (event) => {\n // el momento lo elige la persona con nuestro botón, no la barrita del navegador\n event.preventDefault();\n setDeferredPrompt(event);\n };\n const handleInstalled = () => {\n setIsInstalled(true);\n setDeferredPrompt(null);\n log.info('app instalada');\n };\n window.addEventListener('beforeinstallprompt', handleBeforeInstall);\n window.addEventListener('appinstalled', handleInstalled);\n return () => {\n window.removeEventListener('beforeinstallprompt', handleBeforeInstall);\n window.removeEventListener('appinstalled', handleInstalled);\n };\n }, []);\n\n // El diálogo del navegador sirve UNA vez: con cualquier respuesta se suelta. Cancelarlo no es «Ahora no» (eso solo lo\n // dice el botón de la invitación).\n const promptInstall = useCallback(async () => {\n if (!deferredPrompt) {\n return null;\n }\n deferredPrompt.prompt();\n const {outcome} = await deferredPrompt.userChoice;\n log.info('respuesta al diálogo de instalar', {outcome});\n setDeferredPrompt(null);\n return outcome;\n }, [deferredPrompt]);\n\n const dismiss = useCallback(() => {\n setDismissed(true);\n try {\n window.localStorage.setItem(DISMISS_STORAGE_KEY, DISMISSED);\n } catch {\n // almacenamiento bloqueado: se cierra igual, solo no se recuerda\n }\n }, []);\n\n const variant = useMemo(() => {\n if (!platform) {\n return null;\n }\n return resolveInstallVariant({platform, canPromptNative: deferredPrompt !== null, isInstalled});\n }, [platform, deferredPrompt, isInstalled]);\n\n return {variant, isInstalled, dismissed, dismiss, promptInstall};\n}\n"],"names":[],"mappings":";;;;AAkBA,MAAM,MAAM,aAAa,KAAK;AAE9B,MAAM,sBAAsB;AAC5B,MAAM,YAAY;AAGlB,SAAS,qBAAqB;AAC1B,SAAO,OAAO,aAAa,4BAA4B,EAAE,YAAY,QAAQ,OAAO,UAAU,eAAe;AACjH;AAGA,SAAS,gBAAgB;AACrB,MAAI;AACA,WAAO,OAAO,aAAa,QAAQ,mBAAmB,MAAM;AAAA,EAChE,QAAQ;AACJ,WAAO;AAAA,EACX;AACJ;AAQO,SAAS,mBAAmB;AAC/B,QAAM,CAAC,gBAAgB,iBAAiB,IAAI,SAAS,IAAI;AACzD,QAAM,CAAC,aAAa,cAAc,IAAI,SAAS,KAAK;AACpD,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,IAAI;AAE7C,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,IAAI;AAE/C,YAAU,MAAM;AACZ,gBAAY,eAAe,OAAO,SAAS,CAAC;AAC5C,mBAAe,mBAAkB,CAAE;AACnC,iBAAa,cAAa,CAAE;AAE5B,UAAM,sBAAsB,CAAC,UAAU;AAEnC,YAAM,eAAc;AACpB,wBAAkB,KAAK;AAAA,IAC3B;AACA,UAAM,kBAAkB,MAAM;AAC1B,qBAAe,IAAI;AACnB,wBAAkB,IAAI;AACtB,UAAI,KAAK,eAAe;AAAA,IAC5B;AACA,WAAO,iBAAiB,uBAAuB,mBAAmB;AAClE,WAAO,iBAAiB,gBAAgB,eAAe;AACvD,WAAO,MAAM;AACT,aAAO,oBAAoB,uBAAuB,mBAAmB;AACrE,aAAO,oBAAoB,gBAAgB,eAAe;AAAA,IAC9D;AAAA,EACJ,GAAG,CAAA,CAAE;AAIL,QAAM,gBAAgB,YAAY,YAAY;AAC1C,QAAI,CAAC,gBAAgB;AACjB,aAAO;AAAA,IACX;AACA,mBAAe,OAAM;AACrB,UAAM,EAAC,QAAO,IAAI,MAAM,eAAe;AACvC,QAAI,KAAK,oCAAoC,EAAC,QAAO,CAAC;AACtD,sBAAkB,IAAI;AACtB,WAAO;AAAA,EACX,GAAG,CAAC,cAAc,CAAC;AAEnB,QAAM,UAAU,YAAY,MAAM;AAC9B,iBAAa,IAAI;AACjB,QAAI;AACA,aAAO,aAAa,QAAQ,qBAAqB,SAAS;AAAA,IAC9D,QAAQ;AAAA,IAER;AAAA,EACJ,GAAG,CAAA,CAAE;AAEL,QAAM,UAAU,QAAQ,MAAM;AAC1B,QAAI,CAAC,UAAU;AACX,aAAO;AAAA,IACX;AACA,WAAO,sBAAsB,EAAC,UAAU,iBAAiB,mBAAmB,MAAM,YAAW,CAAC;AAAA,EAClG,GAAG,CAAC,UAAU,gBAAgB,WAAW,CAAC;AAE1C,SAAO,EAAC,SAAS,aAAa,WAAW,SAAS,cAAa;AACnE;"}
@@ -6,8 +6,9 @@ import { default as default3 } from "./hookForm/FormColorField.js";
6
6
  import { default as default4 } from "./hookForm/FormNumberField.js";
7
7
  import { default as default5 } from "./hookForm/FormProblemAlert.js";
8
8
  import { default as default6 } from "./hookForm/FormSelectField.js";
9
- import { default as default7 } from "./hookForm/FormTextField.js";
10
- import { default as default8 } from "./hookForm/SubmitButton.js";
9
+ import { default as default7 } from "./hookForm/FormWizard.js";
10
+ import { default as default8 } from "./hookForm/FormTextField.js";
11
+ import { default as default9 } from "./hookForm/SubmitButton.js";
11
12
  import { SERVER_PROBLEM_ROOT, applyServerProblem } from "./hookForm/applyServerProblem.js";
12
13
  import { useHookFormField } from "./hookForm/useHookFormField.js";
13
14
  export {
@@ -18,9 +19,10 @@ export {
18
19
  default5 as FormProblemAlert,
19
20
  FormProvider,
20
21
  default6 as FormSelectField,
21
- default7 as FormTextField,
22
+ default8 as FormTextField,
23
+ default7 as FormWizard,
22
24
  SERVER_PROBLEM_ROOT,
23
- default8 as SubmitButton,
25
+ default9 as SubmitButton,
24
26
  applyServerProblem,
25
27
  useForm,
26
28
  useFormContext,
@@ -1 +1 @@
1
- {"version":3,"file":"react-hook-form.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;"}
1
+ {"version":3,"file":"react-hook-form.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;"}
@@ -18,7 +18,7 @@ import PasswordOutlinedIcon from "@mui/icons-material/PasswordOutlined";
18
18
  import PhonelinkLockOutlinedIcon from "@mui/icons-material/PhonelinkLockOutlined";
19
19
  import PhonelinkEraseOutlinedIcon from "@mui/icons-material/PhonelinkEraseOutlined";
20
20
  import SyncLockOutlinedIcon from "@mui/icons-material/SyncLockOutlined";
21
- import { formatDateTime } from "../formatting.js";
21
+ import { useFormatters } from "../toolkit/useFormatters.js";
22
22
  import { useToolkitTranslation } from "../toolkit/useToolkitTranslation.js";
23
23
  import SectionCard from "../ui/layout/SectionCard.js";
24
24
  const ACCOUNT_EVENT_KIND = Object.freeze({
@@ -49,8 +49,9 @@ const EVENT_LOOK = Object.freeze({
49
49
  });
50
50
  const DEFAULT_LOOK = Object.freeze({ Icon: EventNoteOutlinedIcon });
51
51
  const LOADING_HEIGHT_PIXELS = 160;
52
- function ActivityLog({ events, error = null }) {
53
- const { t } = useToolkitTranslation("security");
52
+ function ActivityLog({ events, error = null, texts }) {
53
+ const { t } = useToolkitTranslation("security", texts);
54
+ const { formatDateTime } = useFormatters();
54
55
  return /* @__PURE__ */ jsx(SectionCard, { title: t("activity.title"), children: /* @__PURE__ */ jsxs(Stack, { spacing: 2, children: [
55
56
  /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "text.secondary", children: t("activity.description") }),
56
57
  error && /* @__PURE__ */ jsx(Alert, { severity: "error", children: error }),
@@ -1 +1 @@
1
- {"version":3,"file":"ActivityLog.js","sources":["../../src/security/ActivityLog.jsx"],"sourcesContent":["// La bitácora de la cuenta: lo último que pasó (entró, contraseña equivocada, cambió su contraseña, activó la verificación,\n// agregó una llave…), con la hora, el equipo y la red a medias. Para que la persona reconozca lo que no fue ella.\n//\n// Cada evento llega con su tipo (kind) y el texto sale de los textos del paquete (security:activity.kinds.<kind>); un tipo\n// propio del proyecto trae su texto en description.\n//\n// <ActivityLog events={events} />\n//\n// Genérico del paquete: function (regla de la casa).\nimport Alert from '@mui/material/Alert';\nimport List from '@mui/material/List';\nimport ListItem from '@mui/material/ListItem';\nimport ListItemIcon from '@mui/material/ListItemIcon';\nimport ListItemText from '@mui/material/ListItemText';\nimport Skeleton from '@mui/material/Skeleton';\nimport Stack from '@mui/material/Stack';\nimport Typography from '@mui/material/Typography';\nimport CancelOutlinedIcon from '@mui/icons-material/CancelOutlined';\nimport EventNoteOutlinedIcon from '@mui/icons-material/EventNoteOutlined';\nimport FingerprintOutlinedIcon from '@mui/icons-material/FingerprintOutlined';\nimport KeyOutlinedIcon from '@mui/icons-material/KeyOutlined';\nimport LoginOutlinedIcon from '@mui/icons-material/LoginOutlined';\nimport LogoutOutlinedIcon from '@mui/icons-material/LogoutOutlined';\nimport PasswordOutlinedIcon from '@mui/icons-material/PasswordOutlined';\nimport PhonelinkLockOutlinedIcon from '@mui/icons-material/PhonelinkLockOutlined';\nimport PhonelinkEraseOutlinedIcon from '@mui/icons-material/PhonelinkEraseOutlined';\nimport SyncLockOutlinedIcon from '@mui/icons-material/SyncLockOutlined';\n\nimport {formatDateTime} from '@/formatting.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport SectionCard from '@/ui/layout/SectionCard.jsx';\n\n/** Los tipos de evento que el paquete conoce, con su texto en security:activity.kinds. */\nexport const ACCOUNT_EVENT_KIND = Object.freeze({\n signedIn: 'signedIn',\n signInFailed: 'signInFailed',\n signedOut: 'signedOut',\n passwordChanged: 'passwordChanged',\n twoStepEnabled: 'twoStepEnabled',\n twoStepDisabled: 'twoStepDisabled',\n passkeyAdded: 'passkeyAdded',\n passkeyRemoved: 'passkeyRemoved',\n recoveryCodesGenerated: 'recoveryCodesGenerated',\n recoveryCodeUsed: 'recoveryCodeUsed',\n sessionsClosed: 'sessionsClosed',\n});\n\n// el ícono de cada tipo, y en rojo lo que pide atención (un intento con contraseña equivocada, quitar protección)\nconst EVENT_LOOK = Object.freeze({\n [ACCOUNT_EVENT_KIND.signedIn]: {Icon: LoginOutlinedIcon},\n [ACCOUNT_EVENT_KIND.signInFailed]: {Icon: CancelOutlinedIcon, color: 'error'},\n [ACCOUNT_EVENT_KIND.signedOut]: {Icon: LogoutOutlinedIcon},\n [ACCOUNT_EVENT_KIND.passwordChanged]: {Icon: PasswordOutlinedIcon},\n [ACCOUNT_EVENT_KIND.twoStepEnabled]: {Icon: PhonelinkLockOutlinedIcon, color: 'success'},\n [ACCOUNT_EVENT_KIND.twoStepDisabled]: {Icon: PhonelinkEraseOutlinedIcon, color: 'warning'},\n [ACCOUNT_EVENT_KIND.passkeyAdded]: {Icon: FingerprintOutlinedIcon, color: 'success'},\n [ACCOUNT_EVENT_KIND.passkeyRemoved]: {Icon: FingerprintOutlinedIcon, color: 'warning'},\n [ACCOUNT_EVENT_KIND.recoveryCodesGenerated]: {Icon: KeyOutlinedIcon},\n [ACCOUNT_EVENT_KIND.recoveryCodeUsed]: {Icon: KeyOutlinedIcon, color: 'warning'},\n [ACCOUNT_EVENT_KIND.sessionsClosed]: {Icon: SyncLockOutlinedIcon},\n});\nconst DEFAULT_LOOK = Object.freeze({Icon: EventNoteOutlinedIcon});\nconst LOADING_HEIGHT_PIXELS = 160;\n\n/**\n * @param {object} props\n * @param {Array<{id: string, kind: string, occurredAt: string, device?: string|null, network?: string|null, description?: string}>|null} props.events\n * del más nuevo al más viejo; null mientras llegan\n * @param {string|null} [props.error] ya traducido si no se pudieron traer\n */\nexport default function ActivityLog({events, error = null}) {\n const {t} = useToolkitTranslation('security');\n\n return (\n <SectionCard title={t('activity.title')}>\n <Stack spacing={2}>\n <Typography variant=\"body2\" color=\"text.secondary\">{t('activity.description')}</Typography>\n {error && <Alert severity=\"error\">{error}</Alert>}\n {!events && !error && <Skeleton variant=\"rounded\" height={LOADING_HEIGHT_PIXELS} />}\n {events?.length === 0 && <Typography variant=\"body2\" color=\"text.secondary\">{t('activity.empty')}</Typography>}\n {events?.length > 0 && (\n <List dense disablePadding>\n {events.map((event) => {\n const look = EVENT_LOOK[event.kind] ?? DEFAULT_LOOK;\n const where = event.device && event.network ? ` · ${t('activity.from', {device: event.device, network: event.network})}` : '';\n return (\n <ListItem key={event.id} disableGutters>\n <ListItemIcon sx={{minWidth: 36}}><look.Icon fontSize=\"small\" color={look.color ?? 'action'} /></ListItemIcon>\n <ListItemText primary={event.description ?? t(`activity.kinds.${event.kind}`)}\n secondary={`${formatDateTime(event.occurredAt)}${where}`} />\n </ListItem>\n );\n })}\n </List>\n )}\n </Stack>\n </SectionCard>\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ActivityLog.js","sources":["../../src/security/ActivityLog.jsx"],"sourcesContent":["// La bitácora de la cuenta: lo último que pasó (entró, contraseña equivocada, cambió su contraseña, activó la verificación,\n// agregó una llave…), con la hora, el equipo y la red a medias. Para que la persona reconozca lo que no fue ella.\n//\n// Cada evento llega con su tipo (kind) y el texto sale de los textos del paquete (security:activity.kinds.<kind>); un tipo\n// propio del proyecto trae su texto en description.\n//\n// <ActivityLog events={events} />\n//\n// Genérico del paquete: function (regla de la casa).\nimport Alert from '@mui/material/Alert';\nimport List from '@mui/material/List';\nimport ListItem from '@mui/material/ListItem';\nimport ListItemIcon from '@mui/material/ListItemIcon';\nimport ListItemText from '@mui/material/ListItemText';\nimport Skeleton from '@mui/material/Skeleton';\nimport Stack from '@mui/material/Stack';\nimport Typography from '@mui/material/Typography';\nimport CancelOutlinedIcon from '@mui/icons-material/CancelOutlined';\nimport EventNoteOutlinedIcon from '@mui/icons-material/EventNoteOutlined';\nimport FingerprintOutlinedIcon from '@mui/icons-material/FingerprintOutlined';\nimport KeyOutlinedIcon from '@mui/icons-material/KeyOutlined';\nimport LoginOutlinedIcon from '@mui/icons-material/LoginOutlined';\nimport LogoutOutlinedIcon from '@mui/icons-material/LogoutOutlined';\nimport PasswordOutlinedIcon from '@mui/icons-material/PasswordOutlined';\nimport PhonelinkLockOutlinedIcon from '@mui/icons-material/PhonelinkLockOutlined';\nimport PhonelinkEraseOutlinedIcon from '@mui/icons-material/PhonelinkEraseOutlined';\nimport SyncLockOutlinedIcon from '@mui/icons-material/SyncLockOutlined';\n\nimport {useFormatters} from '@/toolkit/useFormatters.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport SectionCard from '@/ui/layout/SectionCard.jsx';\n\n/** Los tipos de evento que el paquete conoce, con su texto en security:activity.kinds. */\nexport const ACCOUNT_EVENT_KIND = Object.freeze({\n signedIn: 'signedIn',\n signInFailed: 'signInFailed',\n signedOut: 'signedOut',\n passwordChanged: 'passwordChanged',\n twoStepEnabled: 'twoStepEnabled',\n twoStepDisabled: 'twoStepDisabled',\n passkeyAdded: 'passkeyAdded',\n passkeyRemoved: 'passkeyRemoved',\n recoveryCodesGenerated: 'recoveryCodesGenerated',\n recoveryCodeUsed: 'recoveryCodeUsed',\n sessionsClosed: 'sessionsClosed',\n});\n\n// el ícono de cada tipo, y en rojo lo que pide atención (un intento con contraseña equivocada, quitar protección)\nconst EVENT_LOOK = Object.freeze({\n [ACCOUNT_EVENT_KIND.signedIn]: {Icon: LoginOutlinedIcon},\n [ACCOUNT_EVENT_KIND.signInFailed]: {Icon: CancelOutlinedIcon, color: 'error'},\n [ACCOUNT_EVENT_KIND.signedOut]: {Icon: LogoutOutlinedIcon},\n [ACCOUNT_EVENT_KIND.passwordChanged]: {Icon: PasswordOutlinedIcon},\n [ACCOUNT_EVENT_KIND.twoStepEnabled]: {Icon: PhonelinkLockOutlinedIcon, color: 'success'},\n [ACCOUNT_EVENT_KIND.twoStepDisabled]: {Icon: PhonelinkEraseOutlinedIcon, color: 'warning'},\n [ACCOUNT_EVENT_KIND.passkeyAdded]: {Icon: FingerprintOutlinedIcon, color: 'success'},\n [ACCOUNT_EVENT_KIND.passkeyRemoved]: {Icon: FingerprintOutlinedIcon, color: 'warning'},\n [ACCOUNT_EVENT_KIND.recoveryCodesGenerated]: {Icon: KeyOutlinedIcon},\n [ACCOUNT_EVENT_KIND.recoveryCodeUsed]: {Icon: KeyOutlinedIcon, color: 'warning'},\n [ACCOUNT_EVENT_KIND.sessionsClosed]: {Icon: SyncLockOutlinedIcon},\n});\nconst DEFAULT_LOOK = Object.freeze({Icon: EventNoteOutlinedIcon});\nconst LOADING_HEIGHT_PIXELS = 160;\n\n/**\n * @param {object} props\n * @param {Array<{id: string, kind: string, occurredAt: string, device?: string|null, network?: string|null, description?: string}>|null} props.events\n * del más nuevo al más viejo; null mientras llegan\n * @param {string|null} [props.error] ya traducido si no se pudieron traer\n * @param {object} [props.texts] los textos que cambian, con la forma de `security` en los textos del paquete\n */\nexport default function ActivityLog({events, error = null, texts}) {\n const {t} = useToolkitTranslation('security', texts);\n const {formatDateTime} = useFormatters();\n\n return (\n <SectionCard title={t('activity.title')}>\n <Stack spacing={2}>\n <Typography variant=\"body2\" color=\"text.secondary\">{t('activity.description')}</Typography>\n {error && <Alert severity=\"error\">{error}</Alert>}\n {!events && !error && <Skeleton variant=\"rounded\" height={LOADING_HEIGHT_PIXELS} />}\n {events?.length === 0 && <Typography variant=\"body2\" color=\"text.secondary\">{t('activity.empty')}</Typography>}\n {events?.length > 0 && (\n <List dense disablePadding>\n {events.map((event) => {\n const look = EVENT_LOOK[event.kind] ?? DEFAULT_LOOK;\n const where = event.device && event.network ? ` · ${t('activity.from', {device: event.device, network: event.network})}` : '';\n return (\n <ListItem key={event.id} disableGutters>\n <ListItemIcon sx={{minWidth: 36}}><look.Icon fontSize=\"small\" color={look.color ?? 'action'} /></ListItemIcon>\n <ListItemText primary={event.description ?? t(`activity.kinds.${event.kind}`)}\n secondary={`${formatDateTime(event.occurredAt)}${where}`} />\n </ListItem>\n );\n })}\n </List>\n )}\n </Stack>\n </SectionCard>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -20,8 +20,8 @@ const CODE_FONT = "ui-monospace, SFMono-Regular, Menlo, monospace";
20
20
  function todayText() {
21
21
  return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
22
22
  }
23
- function RecoveryCodesDialog({ codes, account = "", onClose }) {
24
- const { t } = useToolkitTranslation("security");
23
+ function RecoveryCodesDialog({ codes, account = "", onClose, onCodesSaved, texts }) {
24
+ const { t } = useToolkitTranslation("security", texts);
25
25
  const { appName } = useToolkitSettings();
26
26
  const [isSaved, setIsSaved] = useState(false);
27
27
  const isOpen = Boolean(codes?.length);
@@ -39,6 +39,7 @@ function RecoveryCodesDialog({ codes, account = "", onClose }) {
39
39
  ""
40
40
  ];
41
41
  saveBlob(new Blob([lines.join("\n")], { type: "text/plain;charset=utf-8" }), t("recoveryCodes.fileName", { date }));
42
+ onCodesSaved?.("download");
42
43
  };
43
44
  const close = () => {
44
45
  setIsSaved(false);
@@ -63,7 +64,7 @@ function RecoveryCodesDialog({ codes, account = "", onClose }) {
63
64
  columnGap: 4
64
65
  }, children: (codes ?? []).map((code) => /* @__PURE__ */ jsx("li", { children: code }, code)) }),
65
66
  /* @__PURE__ */ jsxs(Stack, { direction: "row", spacing: 1, sx: { alignItems: "center" }, children: [
66
- /* @__PURE__ */ jsx(CopyButton, { value: (codes ?? []).join("\n"), label: t("recoveryCodes.copyAll") }),
67
+ /* @__PURE__ */ jsx(CopyButton, { value: (codes ?? []).join("\n"), label: t("recoveryCodes.copyAll"), onCopied: () => onCodesSaved?.("copy") }),
67
68
  /* @__PURE__ */ jsx(Button, { size: "small", startIcon: /* @__PURE__ */ jsx(DownloadIcon, {}), onClick: download, children: t("recoveryCodes.download") })
68
69
  ] }),
69
70
  /* @__PURE__ */ jsx(
@@ -1 +1 @@
1
- {"version":3,"file":"RecoveryCodesDialog.js","sources":["../../src/security/RecoveryCodesDialog.jsx"],"sourcesContent":["// Los códigos de recuperación, que se enseñan UNA sola vez (el servidor solo guarda su huella, no los códigos).\n//\n// No se puede cerrar sin marcar «Ya los guardé»: es la única salida que tiene la persona el día que pierda su teléfono,\n// y un aviso que se cierra con Esc o con un clic afuera se cierra sin leer. Se pueden copiar todos o bajar en un .txt con\n// el nombre de la app, la cuenta y la fecha.\n//\n// <RecoveryCodesDialog codes={codes} account={session.email} onClose={() => setCodes(null)} />\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useState} from 'react';\nimport Alert from '@mui/material/Alert';\nimport Box from '@mui/material/Box';\nimport Button from '@mui/material/Button';\nimport Checkbox from '@mui/material/Checkbox';\nimport Dialog from '@mui/material/Dialog';\nimport DialogActions from '@mui/material/DialogActions';\nimport DialogContent from '@mui/material/DialogContent';\nimport DialogTitle from '@mui/material/DialogTitle';\nimport FormControlLabel from '@mui/material/FormControlLabel';\nimport Stack from '@mui/material/Stack';\nimport DownloadIcon from '@mui/icons-material/Download';\n\nimport {saveBlob} from '@/files.js';\nimport {useToolkitSettings} from '@/toolkit/toolkitContext.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport CopyButton from '@/ui/actions/CopyButton.jsx';\n\nconst CODE_FONT = 'ui-monospace, SFMono-Regular, Menlo, monospace';\n\n/**\n * La fecha de hoy como AAAA-MM-DD, para el nombre y el encabezado del archivo.\n *\n * @returns {string}\n */\nfunction todayText() {\n return new Date().toISOString().slice(0, 10);\n}\n\n/**\n * @param {object} props\n * @param {string[]|null} props.codes los códigos recién generados; sin códigos el diálogo está cerrado\n * @param {string} [props.account] la cuenta (normalmente el correo), para el archivo\n * @param {() => void} props.onClose solo se llama después de «Ya los guardé»\n */\nexport default function RecoveryCodesDialog({codes, account = '', onClose}) {\n const {t} = useToolkitTranslation('security');\n const {appName} = useToolkitSettings();\n const [isSaved, setIsSaved] = useState(false);\n const isOpen = Boolean(codes?.length);\n\n const download = () => {\n const date = todayText();\n const lines = [\n t('recoveryCodes.fileTitle', {appName}),\n t('recoveryCodes.fileAccount', {account}),\n t('recoveryCodes.fileGenerated', {date}),\n '',\n t('recoveryCodes.fileHowTo'),\n t('recoveryCodes.fileKeepSafe'),\n '',\n ...codes,\n '',\n ];\n saveBlob(new Blob([lines.join('\\n')], {type: 'text/plain;charset=utf-8'}), t('recoveryCodes.fileName', {date}));\n };\n\n const close = () => {\n setIsSaved(false);\n onClose();\n };\n\n return (\n // ni Esc ni un clic afuera lo cierran: solo «Listo», después de marcar que los guardó\n <Dialog open={isOpen} maxWidth=\"xs\" fullWidth disableEscapeKeyDown onClose={() => {}}>\n <DialogTitle>{t('recoveryCodes.title')}</DialogTitle>\n <DialogContent>\n <Stack spacing={2}>\n <Alert severity=\"warning\">{t('recoveryCodes.warning')}</Alert>\n <Box component=\"ol\" sx={{\n m: 0, p: 2, pl: 5, borderRadius: 2, bgcolor: 'action.hover', fontFamily: CODE_FONT, fontSize: 15,\n columns: 2, columnGap: 4,\n }}>\n {(codes ?? []).map((code) => <li key={code}>{code}</li>)}\n </Box>\n <Stack direction=\"row\" spacing={1} sx={{alignItems: 'center'}}>\n <CopyButton value={(codes ?? []).join('\\n')} label={t('recoveryCodes.copyAll')} />\n <Button size=\"small\" startIcon={<DownloadIcon />} onClick={download}>{t('recoveryCodes.download')}</Button>\n </Stack>\n <FormControlLabel control={<Checkbox checked={isSaved} onChange={(event) => setIsSaved(event.target.checked)} />}\n label={t('recoveryCodes.saved')} />\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button variant=\"contained\" disabled={!isSaved} onClick={close}>{t('recoveryCodes.done')}</Button>\n </DialogActions>\n </Dialog>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;AA2BA,MAAM,YAAY;AAOlB,SAAS,YAAY;AACjB,8BAAW,QAAO,cAAc,MAAM,GAAG,EAAE;AAC/C;AAQA,SAAwB,oBAAoB,EAAC,OAAO,UAAU,IAAI,WAAU;AACxE,QAAM,EAAC,EAAA,IAAK,sBAAsB,UAAU;AAC5C,QAAM,EAAC,QAAA,IAAW,mBAAA;AAClB,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,KAAK;AAC5C,QAAM,SAAS,QAAQ,OAAO,MAAM;AAEpC,QAAM,WAAW,MAAM;AACnB,UAAM,OAAO,UAAA;AACb,UAAM,QAAQ;AAAA,MACV,EAAE,2BAA2B,EAAC,SAAQ;AAAA,MACtC,EAAE,6BAA6B,EAAC,SAAQ;AAAA,MACxC,EAAE,+BAA+B,EAAC,MAAK;AAAA,MACvC;AAAA,MACA,EAAE,yBAAyB;AAAA,MAC3B,EAAE,4BAA4B;AAAA,MAC9B;AAAA,MACA,GAAG;AAAA,MACH;AAAA,IAAA;AAEJ,aAAS,IAAI,KAAK,CAAC,MAAM,KAAK,IAAI,CAAC,GAAG,EAAC,MAAM,2BAAA,CAA2B,GAAG,EAAE,0BAA0B,EAAC,KAAA,CAAK,CAAC;AAAA,EAClH;AAEA,QAAM,QAAQ,MAAM;AAChB,eAAW,KAAK;AAChB,YAAA;AAAA,EACJ;AAEA;AAAA;AAAA,IAEI,qBAAC,QAAA,EAAO,MAAM,QAAQ,UAAS,MAAK,WAAS,MAAC,sBAAoB,MAAC,SAAS,MAAM;AAAA,IAAC,GAC/E,UAAA;AAAA,MAAA,oBAAC,aAAA,EAAa,UAAA,EAAE,qBAAqB,EAAA,CAAE;AAAA,MACvC,oBAAC,eAAA,EACG,UAAA,qBAAC,OAAA,EAAM,SAAS,GACZ,UAAA;AAAA,QAAA,oBAAC,OAAA,EAAM,UAAS,WAAW,UAAA,EAAE,uBAAuB,GAAE;AAAA,QACtD,oBAAC,KAAA,EAAI,WAAU,MAAK,IAAI;AAAA,UACpB,GAAG;AAAA,UAAG,GAAG;AAAA,UAAG,IAAI;AAAA,UAAG,cAAc;AAAA,UAAG,SAAS;AAAA,UAAgB,YAAY;AAAA,UAAW,UAAU;AAAA,UAC9F,SAAS;AAAA,UAAG,WAAW;AAAA,QAAA,GAErB,WAAA,SAAS,CAAA,GAAI,IAAI,CAAC,SAAS,oBAAC,MAAA,EAAe,UAAA,KAAA,GAAP,IAAY,CAAK,EAAA,CAC3D;AAAA,QACA,qBAAC,OAAA,EAAM,WAAU,OAAM,SAAS,GAAG,IAAI,EAAC,YAAY,SAAA,GAChD,UAAA;AAAA,UAAA,oBAAC,YAAA,EAAW,QAAQ,SAAS,CAAA,GAAI,KAAK,IAAI,GAAG,OAAO,EAAE,uBAAuB,EAAA,CAAG;AAAA,UAChF,oBAAC,QAAA,EAAO,MAAK,SAAQ,WAAW,oBAAC,cAAA,CAAA,CAAa,GAAI,SAAS,UAAW,UAAA,EAAE,wBAAwB,EAAA,CAAE;AAAA,QAAA,GACtG;AAAA,QACA;AAAA,UAAC;AAAA,UAAA;AAAA,YAAiB,SAAS,oBAAC,UAAA,EAAS,SAAS,SAAS,UAAU,CAAC,UAAU,WAAW,MAAM,OAAO,OAAO,EAAA,CAAG;AAAA,YAC5F,OAAO,EAAE,qBAAqB;AAAA,UAAA;AAAA,QAAA;AAAA,MAAG,EAAA,CACvD,EAAA,CACJ;AAAA,MACA,oBAAC,eAAA,EACG,UAAA,oBAAC,QAAA,EAAO,SAAQ,aAAY,UAAU,CAAC,SAAS,SAAS,OAAQ,UAAA,EAAE,oBAAoB,GAAE,EAAA,CAC7F;AAAA,IAAA,EAAA,CACJ;AAAA;AAER;"}
1
+ {"version":3,"file":"RecoveryCodesDialog.js","sources":["../../src/security/RecoveryCodesDialog.jsx"],"sourcesContent":["// Los códigos de recuperación, que se enseñan UNA sola vez (el servidor solo guarda su huella, no los códigos).\n//\n// No se puede cerrar sin marcar «Ya los guardé»: es la única salida que tiene la persona el día que pierda su teléfono,\n// y un aviso que se cierra con Esc o con un clic afuera se cierra sin leer. Se pueden copiar todos o bajar en un .txt con\n// el nombre de la app, la cuenta y la fecha.\n//\n// <RecoveryCodesDialog codes={codes} account={session.email} onClose={() => setCodes(null)} />\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useState} from 'react';\nimport Alert from '@mui/material/Alert';\nimport Box from '@mui/material/Box';\nimport Button from '@mui/material/Button';\nimport Checkbox from '@mui/material/Checkbox';\nimport Dialog from '@mui/material/Dialog';\nimport DialogActions from '@mui/material/DialogActions';\nimport DialogContent from '@mui/material/DialogContent';\nimport DialogTitle from '@mui/material/DialogTitle';\nimport FormControlLabel from '@mui/material/FormControlLabel';\nimport Stack from '@mui/material/Stack';\nimport DownloadIcon from '@mui/icons-material/Download';\n\nimport {saveBlob} from '@/files.js';\nimport {useToolkitSettings} from '@/toolkit/toolkitContext.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport CopyButton from '@/ui/actions/CopyButton.jsx';\n\nconst CODE_FONT = 'ui-monospace, SFMono-Regular, Menlo, monospace';\n\n/**\n * La fecha de hoy como AAAA-MM-DD, para el nombre y el encabezado del archivo.\n *\n * @returns {string}\n */\nfunction todayText() {\n return new Date().toISOString().slice(0, 10);\n}\n\n/**\n * @param {object} props\n * @param {string[]|null} props.codes los códigos recién generados; sin códigos el diálogo está cerrado\n * @param {string} [props.account] la cuenta (normalmente el correo), para el archivo\n * @param {() => void} props.onClose solo se llama después de «Ya los guardé»\n * @param {(method: 'copy'|'download') => void} [props.onCodesSaved] la persona los copió o los descargó (para la bitácora de\n * seguridad)\n * @param {object} [props.texts] los textos que cambian, con la forma de `security` en los textos del paquete\n */\nexport default function RecoveryCodesDialog({codes, account = '', onClose, onCodesSaved, texts}) {\n const {t} = useToolkitTranslation('security', texts);\n const {appName} = useToolkitSettings();\n const [isSaved, setIsSaved] = useState(false);\n const isOpen = Boolean(codes?.length);\n\n const download = () => {\n const date = todayText();\n const lines = [\n t('recoveryCodes.fileTitle', {appName}),\n t('recoveryCodes.fileAccount', {account}),\n t('recoveryCodes.fileGenerated', {date}),\n '',\n t('recoveryCodes.fileHowTo'),\n t('recoveryCodes.fileKeepSafe'),\n '',\n ...codes,\n '',\n ];\n saveBlob(new Blob([lines.join('\\n')], {type: 'text/plain;charset=utf-8'}), t('recoveryCodes.fileName', {date}));\n onCodesSaved?.('download');\n };\n\n const close = () => {\n setIsSaved(false);\n onClose();\n };\n\n return (\n // ni Esc ni un clic afuera lo cierran: solo «Listo», después de marcar que los guardó\n <Dialog open={isOpen} maxWidth=\"xs\" fullWidth disableEscapeKeyDown onClose={() => {}}>\n <DialogTitle>{t('recoveryCodes.title')}</DialogTitle>\n <DialogContent>\n <Stack spacing={2}>\n <Alert severity=\"warning\">{t('recoveryCodes.warning')}</Alert>\n <Box component=\"ol\" sx={{\n m: 0, p: 2, pl: 5, borderRadius: 2, bgcolor: 'action.hover', fontFamily: CODE_FONT, fontSize: 15,\n columns: 2, columnGap: 4,\n }}>\n {(codes ?? []).map((code) => <li key={code}>{code}</li>)}\n </Box>\n <Stack direction=\"row\" spacing={1} sx={{alignItems: 'center'}}>\n <CopyButton value={(codes ?? []).join('\\n')} label={t('recoveryCodes.copyAll')} onCopied={() => onCodesSaved?.('copy')} />\n <Button size=\"small\" startIcon={<DownloadIcon />} onClick={download}>{t('recoveryCodes.download')}</Button>\n </Stack>\n <FormControlLabel control={<Checkbox checked={isSaved} onChange={(event) => setIsSaved(event.target.checked)} />}\n label={t('recoveryCodes.saved')} />\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button variant=\"contained\" disabled={!isSaved} onClick={close}>{t('recoveryCodes.done')}</Button>\n </DialogActions>\n </Dialog>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;AA2BA,MAAM,YAAY;AAOlB,SAAS,YAAY;AACjB,8BAAW,QAAO,cAAc,MAAM,GAAG,EAAE;AAC/C;AAWA,SAAwB,oBAAoB,EAAC,OAAO,UAAU,IAAI,SAAS,cAAc,SAAQ;AAC7F,QAAM,EAAC,EAAA,IAAK,sBAAsB,YAAY,KAAK;AACnD,QAAM,EAAC,QAAA,IAAW,mBAAA;AAClB,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,KAAK;AAC5C,QAAM,SAAS,QAAQ,OAAO,MAAM;AAEpC,QAAM,WAAW,MAAM;AACnB,UAAM,OAAO,UAAA;AACb,UAAM,QAAQ;AAAA,MACV,EAAE,2BAA2B,EAAC,SAAQ;AAAA,MACtC,EAAE,6BAA6B,EAAC,SAAQ;AAAA,MACxC,EAAE,+BAA+B,EAAC,MAAK;AAAA,MACvC;AAAA,MACA,EAAE,yBAAyB;AAAA,MAC3B,EAAE,4BAA4B;AAAA,MAC9B;AAAA,MACA,GAAG;AAAA,MACH;AAAA,IAAA;AAEJ,aAAS,IAAI,KAAK,CAAC,MAAM,KAAK,IAAI,CAAC,GAAG,EAAC,MAAM,2BAAA,CAA2B,GAAG,EAAE,0BAA0B,EAAC,KAAA,CAAK,CAAC;AAC9G,mBAAe,UAAU;AAAA,EAC7B;AAEA,QAAM,QAAQ,MAAM;AAChB,eAAW,KAAK;AAChB,YAAA;AAAA,EACJ;AAEA;AAAA;AAAA,IAEI,qBAAC,QAAA,EAAO,MAAM,QAAQ,UAAS,MAAK,WAAS,MAAC,sBAAoB,MAAC,SAAS,MAAM;AAAA,IAAC,GAC/E,UAAA;AAAA,MAAA,oBAAC,aAAA,EAAa,UAAA,EAAE,qBAAqB,EAAA,CAAE;AAAA,MACvC,oBAAC,eAAA,EACG,UAAA,qBAAC,OAAA,EAAM,SAAS,GACZ,UAAA;AAAA,QAAA,oBAAC,OAAA,EAAM,UAAS,WAAW,UAAA,EAAE,uBAAuB,GAAE;AAAA,QACtD,oBAAC,KAAA,EAAI,WAAU,MAAK,IAAI;AAAA,UACpB,GAAG;AAAA,UAAG,GAAG;AAAA,UAAG,IAAI;AAAA,UAAG,cAAc;AAAA,UAAG,SAAS;AAAA,UAAgB,YAAY;AAAA,UAAW,UAAU;AAAA,UAC9F,SAAS;AAAA,UAAG,WAAW;AAAA,QAAA,GAErB,WAAA,SAAS,CAAA,GAAI,IAAI,CAAC,SAAS,oBAAC,MAAA,EAAe,UAAA,KAAA,GAAP,IAAY,CAAK,EAAA,CAC3D;AAAA,QACA,qBAAC,OAAA,EAAM,WAAU,OAAM,SAAS,GAAG,IAAI,EAAC,YAAY,SAAA,GAChD,UAAA;AAAA,UAAA,oBAAC,cAAW,QAAQ,SAAS,CAAA,GAAI,KAAK,IAAI,GAAG,OAAO,EAAE,uBAAuB,GAAG,UAAU,MAAM,eAAe,MAAM,GAAG;AAAA,UACxH,oBAAC,QAAA,EAAO,MAAK,SAAQ,WAAW,oBAAC,cAAA,CAAA,CAAa,GAAI,SAAS,UAAW,UAAA,EAAE,wBAAwB,EAAA,CAAE;AAAA,QAAA,GACtG;AAAA,QACA;AAAA,UAAC;AAAA,UAAA;AAAA,YAAiB,SAAS,oBAAC,UAAA,EAAS,SAAS,SAAS,UAAU,CAAC,UAAU,WAAW,MAAM,OAAO,OAAO,EAAA,CAAG;AAAA,YAC5F,OAAO,EAAE,qBAAqB;AAAA,UAAA;AAAA,QAAA;AAAA,MAAG,EAAA,CACvD,EAAA,CACJ;AAAA,MACA,oBAAC,eAAA,EACG,UAAA,oBAAC,QAAA,EAAO,SAAQ,aAAY,UAAU,CAAC,SAAS,SAAS,OAAQ,UAAA,EAAE,oBAAoB,GAAE,EAAA,CAC7F;AAAA,IAAA,EAAA,CACJ;AAAA;AAER;"}
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { jsxs, jsx } from "react/jsx-runtime";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import Alert from "@mui/material/Alert";
4
4
  import Button from "@mui/material/Button";
5
5
  import Divider from "@mui/material/Divider";
@@ -15,13 +15,15 @@ import PhoneIphoneOutlinedIcon from "@mui/icons-material/PhoneIphoneOutlined";
15
15
  import ConfirmDialog from "../feedback/ConfirmDialog.js";
16
16
  import { toastSuccess } from "../feedback/toast.js";
17
17
  import { useConfirmAction } from "../feedback/useConfirmAction.js";
18
- import { formatDateTime } from "../formatting.js";
18
+ import { useFormatters } from "../toolkit/useFormatters.js";
19
19
  import { useToolkitTranslation } from "../toolkit/useToolkitTranslation.js";
20
20
  import Tag from "../ui/labels/Tag.js";
21
21
  import SectionCard from "../ui/layout/SectionCard.js";
22
+ import ComponentTexts from "../toolkit/ComponentTexts.js";
22
23
  const LOADING_HEIGHT_PIXELS = 96;
23
24
  function SessionRow({ session }) {
24
25
  const { t } = useToolkitTranslation("security");
26
+ const { formatDateTime } = useFormatters();
25
27
  const DeviceIcon = session.isMobile ? PhoneIphoneOutlinedIcon : ComputerOutlinedIcon;
26
28
  const device = t("sessions.device", {
27
29
  browser: session.browser ?? t("sessions.otherBrowser"),
@@ -39,8 +41,8 @@ function SessionRow({ session }) {
39
41
  ] }), secondary: details.join(" · "), slotProps: { primary: { sx: { fontWeight: 600 } } } })
40
42
  ] });
41
43
  }
42
- function SessionsCard({ sessions, error = null, onCloseOthers, onClosed }) {
43
- const { t } = useToolkitTranslation("security");
44
+ function SessionsCard({ sessions, error = null, onCloseOthers, onClosed, texts }) {
45
+ const { t } = useToolkitTranslation("security", texts);
44
46
  const othersCount = (sessions ?? []).filter((session) => !session.isCurrent).length;
45
47
  const confirmation = useConfirmAction({
46
48
  run: () => onCloseOthers(),
@@ -49,7 +51,7 @@ function SessionsCard({ sessions, error = null, onCloseOthers, onClosed }) {
49
51
  onClosed?.();
50
52
  }
51
53
  });
52
- return /* @__PURE__ */ jsxs(SectionCard, { title: t("sessions.title"), children: [
54
+ return /* @__PURE__ */ jsx(ComponentTexts, { namespace: "security", texts, children: /* @__PURE__ */ jsxs(SectionCard, { title: t("sessions.title"), children: [
53
55
  /* @__PURE__ */ jsxs(Stack, { spacing: 2, children: [
54
56
  /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "text.secondary", children: t("sessions.description") }),
55
57
  error && /* @__PURE__ */ jsx(Alert, { severity: "error", children: error }),
@@ -73,7 +75,7 @@ function SessionsCard({ sessions, error = null, onCloseOthers, onClosed }) {
73
75
  confirmLabel: t("sessions.confirm")
74
76
  }
75
77
  )
76
- ] });
78
+ ] }) });
77
79
  }
78
80
  export {
79
81
  SessionsCard as default
@@ -1 +1 @@
1
- {"version":3,"file":"SessionsCard.js","sources":["../../src/security/SessionsCard.jsx"],"sourcesContent":["// «Mis sesiones»: los equipos donde la cuenta sigue abierta (este, marcado) y «Cerrar la sesión en los demás equipos».\n//\n// No pide la contraseña: cerrar sesiones no da acceso a nada, solo lo quita. Nunca se pinta la IP completa ni el user\n// agent: el servidor manda el navegador, el sistema y la red a medias (p. ej. «187.190.x.x»), que bastan para reconocer\n// un equipo sin exponer datos.\n//\n// <SessionsCard sessions={sessions} onCloseOthers={async () => (await api.post('/sessions/close-others')).closed} />\n//\n// Genérico del paquete: function (regla de la casa).\nimport Alert from '@mui/material/Alert';\nimport Button from '@mui/material/Button';\nimport Divider from '@mui/material/Divider';\nimport List from '@mui/material/List';\nimport ListItem from '@mui/material/ListItem';\nimport ListItemIcon from '@mui/material/ListItemIcon';\nimport ListItemText from '@mui/material/ListItemText';\nimport Skeleton from '@mui/material/Skeleton';\nimport Stack from '@mui/material/Stack';\nimport Typography from '@mui/material/Typography';\nimport ComputerOutlinedIcon from '@mui/icons-material/ComputerOutlined';\nimport PhoneIphoneOutlinedIcon from '@mui/icons-material/PhoneIphoneOutlined';\n\nimport ConfirmDialog from '@/feedback/ConfirmDialog.jsx';\nimport {toastSuccess} from '@/feedback/toast.jsx';\nimport {useConfirmAction} from '@/feedback/useConfirmAction.js';\nimport {formatDateTime} from '@/formatting.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport Tag from '@/ui/labels/Tag.jsx';\nimport SectionCard from '@/ui/layout/SectionCard.jsx';\n\nconst LOADING_HEIGHT_PIXELS = 96;\n\n/**\n * Un renglón: el equipo (navegador en sistema), desde cuándo y su red a medias.\n *\n * @param {{session: object}} props\n */\nfunction SessionRow({session}) {\n const {t} = useToolkitTranslation('security');\n const DeviceIcon = session.isMobile ? PhoneIphoneOutlinedIcon : ComputerOutlinedIcon;\n const device = t('sessions.device', {\n browser: session.browser ?? t('sessions.otherBrowser'), platform: session.platform ?? t('sessions.otherPlatform'),\n });\n const details = [t('sessions.since', {date: formatDateTime(session.startedAt)})];\n if (session.network) {\n details.push(t('sessions.network', {network: session.network}));\n }\n\n return (\n <ListItem disableGutters alignItems=\"flex-start\" sx={{gap: 1}}>\n <ListItemIcon sx={{minWidth: 36, mt: 0.5}}><DeviceIcon /></ListItemIcon>\n <ListItemText primary={(\n <Stack direction=\"row\" spacing={1} useFlexGap sx={{alignItems: 'center', flexWrap: 'wrap'}}>\n <span>{device}</span>\n {session.isCurrent && <Tag label={t('sessions.current')} color=\"success\" />}\n </Stack>\n )} secondary={details.join(' · ')} slotProps={{primary: {sx: {fontWeight: 600}}}} />\n </ListItem>\n );\n}\n\n/**\n * @param {object} props\n * @param {Array<{id: string, browser?: string|null, platform?: string|null, isMobile?: boolean, startedAt: string, network?: string|null, isCurrent?: boolean}>|null} props.sessions\n * null mientras llegan\n * @param {string|null} [props.error] ya traducido (ApiError.message) si no se pudieron traer\n * @param {() => Promise<number>} props.onCloseOthers cierra las demás; regresa cuántas cerró\n * @param {() => void} [props.onClosed] p. ej. volver a pedir la lista\n */\nexport default function SessionsCard({sessions, error = null, onCloseOthers, onClosed}) {\n const {t} = useToolkitTranslation('security');\n const othersCount = (sessions ?? []).filter((session) => !session.isCurrent).length;\n const confirmation = useConfirmAction({\n run: () => onCloseOthers(),\n onDone: (closedCount) => {\n toastSuccess(closedCount ? t('sessions.closed', {count: closedCount}) : t('sessions.noneClosed'));\n onClosed?.();\n },\n });\n\n return (\n <SectionCard title={t('sessions.title')}>\n <Stack spacing={2}>\n <Typography variant=\"body2\" color=\"text.secondary\">{t('sessions.description')}</Typography>\n {error && <Alert severity=\"error\">{error}</Alert>}\n {!sessions && !error && <Skeleton variant=\"rounded\" height={LOADING_HEIGHT_PIXELS} />}\n {sessions && (\n <List disablePadding>\n {sessions.map((session, index) => (\n <li key={session.id}>\n {index > 0 && <Divider component=\"div\" />}\n <SessionRow session={session} />\n </li>\n ))}\n </List>\n )}\n <Stack spacing={1} sx={{alignItems: 'flex-start'}}>\n {sessions && othersCount === 0 && <Typography variant=\"caption\" color=\"text.secondary\">{t('sessions.onlyThis')}</Typography>}\n <Button variant=\"outlined\" color=\"warning\" disabled={!sessions || othersCount === 0} onClick={() => confirmation.open({})}>\n {t('sessions.closeOthers')}\n </Button>\n </Stack>\n </Stack>\n <ConfirmDialog {...confirmation.dialogProps} tone=\"warning\" title={t('sessions.confirmTitle')}\n description={t('sessions.confirmText', {count: othersCount})} confirmLabel={t('sessions.confirm')} />\n </SectionCard>\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"SessionsCard.js","sources":["../../src/security/SessionsCard.jsx"],"sourcesContent":["// «Mis sesiones»: los equipos donde la cuenta sigue abierta (este, marcado) y «Cerrar la sesión en los demás equipos».\n//\n// No pide la contraseña: cerrar sesiones no da acceso a nada, solo lo quita. Nunca se pinta la IP completa ni el user\n// agent: el servidor manda el navegador, el sistema y la red a medias (p. ej. «187.190.x.x»), que bastan para reconocer\n// un equipo sin exponer datos.\n//\n// <SessionsCard sessions={sessions} onCloseOthers={async () => (await api.post('/sessions/close-others')).closed} />\n//\n// Genérico del paquete: function (regla de la casa).\nimport Alert from '@mui/material/Alert';\nimport Button from '@mui/material/Button';\nimport Divider from '@mui/material/Divider';\nimport List from '@mui/material/List';\nimport ListItem from '@mui/material/ListItem';\nimport ListItemIcon from '@mui/material/ListItemIcon';\nimport ListItemText from '@mui/material/ListItemText';\nimport Skeleton from '@mui/material/Skeleton';\nimport Stack from '@mui/material/Stack';\nimport Typography from '@mui/material/Typography';\nimport ComputerOutlinedIcon from '@mui/icons-material/ComputerOutlined';\nimport PhoneIphoneOutlinedIcon from '@mui/icons-material/PhoneIphoneOutlined';\n\nimport ConfirmDialog from '@/feedback/ConfirmDialog.jsx';\nimport {toastSuccess} from '@/feedback/toast.jsx';\nimport {useConfirmAction} from '@/feedback/useConfirmAction.js';\nimport {useFormatters} from '@/toolkit/useFormatters.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport Tag from '@/ui/labels/Tag.jsx';\nimport SectionCard from '@/ui/layout/SectionCard.jsx';\nimport ComponentTexts from '@/toolkit/ComponentTexts.jsx';\n\nconst LOADING_HEIGHT_PIXELS = 96;\n\n/**\n * Un renglón: el equipo (navegador en sistema), desde cuándo y su red a medias.\n *\n * @param {{session: object}} props\n */\nfunction SessionRow({session}) {\n const {t} = useToolkitTranslation('security');\n const {formatDateTime} = useFormatters();\n const DeviceIcon = session.isMobile ? PhoneIphoneOutlinedIcon : ComputerOutlinedIcon;\n const device = t('sessions.device', {\n browser: session.browser ?? t('sessions.otherBrowser'), platform: session.platform ?? t('sessions.otherPlatform'),\n });\n const details = [t('sessions.since', {date: formatDateTime(session.startedAt)})];\n if (session.network) {\n details.push(t('sessions.network', {network: session.network}));\n }\n\n return (\n <ListItem disableGutters alignItems=\"flex-start\" sx={{gap: 1}}>\n <ListItemIcon sx={{minWidth: 36, mt: 0.5}}><DeviceIcon /></ListItemIcon>\n <ListItemText primary={(\n <Stack direction=\"row\" spacing={1} useFlexGap sx={{alignItems: 'center', flexWrap: 'wrap'}}>\n <span>{device}</span>\n {session.isCurrent && <Tag label={t('sessions.current')} color=\"success\" />}\n </Stack>\n )} secondary={details.join(' · ')} slotProps={{primary: {sx: {fontWeight: 600}}}} />\n </ListItem>\n );\n}\n\n/**\n * @param {object} props\n * @param {Array<{id: string, browser?: string|null, platform?: string|null, isMobile?: boolean, startedAt: string, network?: string|null, isCurrent?: boolean}>|null} props.sessions\n * null mientras llegan\n * @param {string|null} [props.error] ya traducido (ApiError.message) si no se pudieron traer\n * @param {() => Promise<number>} props.onCloseOthers cierra las demás; regresa cuántas cerró\n * @param {() => void} [props.onClosed] p. ej. volver a pedir la lista\n * @param {object} [props.texts] los textos que cambian, con la forma de `security` en los textos del paquete\n */\nexport default function SessionsCard({sessions, error = null, onCloseOthers, onClosed, texts}) {\n const {t} = useToolkitTranslation('security', texts);\n const othersCount = (sessions ?? []).filter((session) => !session.isCurrent).length;\n const confirmation = useConfirmAction({\n run: () => onCloseOthers(),\n onDone: (closedCount) => {\n toastSuccess(closedCount ? t('sessions.closed', {count: closedCount}) : t('sessions.noneClosed'));\n onClosed?.();\n },\n });\n\n return (\n <ComponentTexts namespace=\"security\" texts={texts}>\n <SectionCard title={t('sessions.title')}>\n <Stack spacing={2}>\n <Typography variant=\"body2\" color=\"text.secondary\">{t('sessions.description')}</Typography>\n {error && <Alert severity=\"error\">{error}</Alert>}\n {!sessions && !error && <Skeleton variant=\"rounded\" height={LOADING_HEIGHT_PIXELS} />}\n {sessions && (\n <List disablePadding>\n {sessions.map((session, index) => (\n <li key={session.id}>\n {index > 0 && <Divider component=\"div\" />}\n <SessionRow session={session} />\n </li>\n ))}\n </List>\n )}\n <Stack spacing={1} sx={{alignItems: 'flex-start'}}>\n {sessions && othersCount === 0 && <Typography variant=\"caption\" color=\"text.secondary\">{t('sessions.onlyThis')}</Typography>}\n <Button variant=\"outlined\" color=\"warning\" disabled={!sessions || othersCount === 0} onClick={() => confirmation.open({})}>\n {t('sessions.closeOthers')}\n </Button>\n </Stack>\n </Stack>\n <ConfirmDialog {...confirmation.dialogProps} tone=\"warning\" title={t('sessions.confirmTitle')}\n description={t('sessions.confirmText', {count: othersCount})} confirmLabel={t('sessions.confirm')} />\n </SectionCard>\n </ComponentTexts>\n );\n}\n"],"names":[],"mappings":"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"}
package/dist/table.js CHANGED
@@ -11,6 +11,7 @@ import { toServerTableParams } from "./DataTable/serverTableParams.js";
11
11
  import { buildTableActors, tableMachine } from "./DataTable/tableMachine.js";
12
12
  import { useDebouncedFilter } from "./DataTable/useDebouncedFilter.js";
13
13
  import { FILTER_KINDS, isFilterActive, useTableState } from "./DataTable/useTableState.js";
14
+ import { SELECTION_PHONE_LAYOUT } from "./ui/actions/SelectionActions.js";
14
15
  export {
15
16
  default2 as AuditTrailDialog,
16
17
  default3 as AuditTrailTable,
@@ -18,6 +19,7 @@ export {
18
19
  FILTER_KINDS,
19
20
  default5 as FilterChips,
20
21
  default6 as GoToPage,
22
+ SELECTION_PHONE_LAYOUT,
21
23
  default7 as ServerTable,
22
24
  TableFilterContext,
23
25
  buildChipConfig,
package/dist/table.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"table.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;"}
1
+ {"version":3,"file":"table.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;"}
@@ -0,0 +1,13 @@
1
+ function MuiDialog() {
2
+ return {
3
+ MuiDialog: {
4
+ styleOverrides: {
5
+ paper: { outline: 0 }
6
+ }
7
+ }
8
+ };
9
+ }
10
+ export {
11
+ MuiDialog
12
+ };
13
+ //# sourceMappingURL=MuiDialog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MuiDialog.js","sources":["../../../src/theme/components/MuiDialog.js"],"sourcesContent":["// Las ventanas (Dialog): sin el contorno de foco del navegador alrededor de la ventana entera.\n//\n// Desde MUI 7.3 el foco, al abrir, cae en la ventana misma (el Paper con role=\"dialog\"), para que el lector de pantalla diga\n// su título. MUI le quita el contorno a su contenedor («para ratón, toque y teclado», dice su código), pero no a la ventana,\n// y Chrome la marcaba con un borde azul aunque se abriera con clic. La ventana no es un control: lo que se pica adentro\n// conserva su aro al llegar con Tab.\n//\n// Funciones puras, sin React: function (regla de la casa).\nexport function MuiDialog() {\n return {\n MuiDialog: {\n styleOverrides: {\n paper: {outline: 0},\n },\n },\n };\n}\n"],"names":[],"mappings":"AAQO,SAAS,YAAY;AACxB,SAAO;AAAA,IACH,WAAW;AAAA,MACP,gBAAgB;AAAA,QACZ,OAAO,EAAC,SAAS,EAAC;AAAA,MAClC;AAAA,IACA;AAAA,EACA;AACA;"}
@@ -1 +1 @@
1
- {"version":3,"file":"createToolkitTheme.js","sources":["../../src/theme/createToolkitTheme.js"],"sourcesContent":["// El tema del framework: el estilo de Stackcraft (del tema de walk-ot) con claro y oscuro. Es opcional: ToolkitProvider\n// funciona con cualquier tema de MUI.\n//\n// const theme = createToolkitTheme(); // los colores de Stackcraft\n// const theme = createToolkitTheme({glass: true}); // con vidrio líquido en lo que flota\n// const theme = createToolkitTheme({glass: {opacity: 0.7}}); // con vidrio más transparente (por omisión 0.8)\n// const theme = createToolkitTheme({glass: {surfaces: true}}); // también las tarjetas del contenido (con advertencia)\n// const theme = createToolkitTheme({glass: true, bar: TOP_BAR.band}); // la barra de arriba en franja (por omisión, cápsulas)\n// const theme = createToolkitTheme({colorSchemes: {light: {palette: {primary: {main: '#0B5FFF'}}}, dark: {...}}});\n//\n// Lo que trae, con su porqué:\n// - variables CSS y los dos modos: ColorSchemeToggle cambia de modo sin volver a pintar, y las gráficas leen sus colores\n// de las variables (var(--mui-palette-text-primary)); sin ellas su texto se quedaba negro en oscuro.\n// - colorSchemeSelector 'class': el modo lo elige la persona con el botón (con 'media' solo mandaría el sistema\n// operativo y el botón no haría nada). Con Next.js va con <InitColorSchemeScript attribute=\"class\" />.\n// - letra base de 16 px: con menos, el iPhone hace zoom al entrar a un campo. body1 16 px y body2 15 px en el teléfono;\n// responsiveFontSizes los ajusta por ancho de pantalla.\n// - transiciones de 220 ms, iguales al abrir y al cerrar: más ágiles que las de Material (300 ms) y sin el desfase de\n// 30 ms entre lo que solo conoce una transición (la barra) y lo que conoce dos (el menú lateral).\n// - esquinas de 8 px y los ajustes de componentes de toolkitComponents().\n// - glass: el vidrio líquido en lo que flota y en los controles (glassSurface.js explica dónde y por qué). Queda anotado\n// en theme.toolkit.glass para que los componentes del framework que flotan (el marco, los avisos) lo sepan sin otra prop.\n// - components: los ajustes propios del proyecto se SUMAN a los del framework (mergeComponents), no los borran.\n//\n// Funciones puras, sin React: function (regla de la casa).\nimport {createTheme, responsiveFontSizes} from '@mui/material/styles';\n\nimport {glassComponents} from '@/theme/components/glassComponents.js';\nimport {GLASS_OPACITY} from '@/theme/glassSurface.js';\nimport {mergeComponents} from '@/theme/mergeComponents.js';\nimport {stackcraftDarkPalette, stackcraftLightPalette} from '@/theme/stackcraftPalette.js';\nimport {trackingOf} from '@/theme/appleTracking.js';\nimport {toolkitComponents} from '@/theme/toolkitComponents.js';\nimport {MINIMAL_BACKGROUND, minimalComponents} from '@/theme/components/minimalComponents.js';\nimport {createLogger} from '@/observability/logger.js';\n\nconst log = createLogger('theme');\n// el aviso de las tarjetas de vidrio sale una vez por pestaña: el tema se vuelve a armar cada vez que cambia algo (un\n// configurador de tema lo arma en cada clic) y la consola se llenaba del mismo aviso\nlet hasWarnedAboutGlassSurfaces = false;\n\nconst SYSTEM_FONT_FAMILY = 'system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif';\nconst CORNER_RADIUS_PIXELS = 8;\n// cuántos niveles de sombra trae el tema de MUI (del 0 al 24)\nconst SHADOW_LEVELS = 25;\n\n/** El estilo del tema: el de Stackcraft (con vidrio o sin él) o el minimalista, plano y tonal. */\nexport const THEME_LOOK = Object.freeze({standard: 'standard', minimal: 'minimal'});\n\n/**\n * La barra de arriba, con vidrio y con el minimalista: por omisión como las ventanas de macOS Tahoe, sin franja (el nombre\n * y los botones en cápsulas por grupo; lo eligió el autor al verlas), o la franja de lado a lado. Con vidrio las cápsulas\n * son de vidrio y el contenido pasa por debajo desenfocado; con el minimalista son bloques del tono de las tarjetas sobre\n * el color del fondo (AppLayout). El estilo de Stackcraft sin vidrio se queda con su barra de siempre.\n */\nexport const TOP_BAR = Object.freeze({band: 'band', capsules: 'capsules'});\n\n/**\n * Lo que pidió el proyecto del vidrio, ya revisado: false (sin vidrio) o {opacity, surfaces}.\n *\n * @param {boolean|{opacity?: number, surfaces?: boolean}} glass\n * @returns {false|{opacity: number, surfaces: boolean}}\n */\nfunction glassSettingsOf(glass) {\n if (!glass) {\n return false;\n }\n const requested = glass === true ? {} : glass;\n const opacity = requested.opacity ?? GLASS_OPACITY;\n // un 80 en lugar de 0.8 daría un color inválido y la barra saldría sin fondo, sin ningún aviso\n if (typeof opacity !== 'number' || opacity < 0 || opacity > 1) {\n throw new RangeError(`createToolkitTheme: glass.opacity va de 0 a 1 (llegó ${opacity}).`);\n }\n const surfaces = Boolean(requested.surfaces);\n // se permite, pero con advertencia: la guía de Apple pide el contenido sin vidrio y cada tarjeta desenfoca lo de atrás\n if (surfaces && !hasWarnedAboutGlassSurfaces) {\n hasWarnedAboutGlassSurfaces = true;\n log.warn('glass.surfaces pone vidrio también en el contenido (tarjetas, avisos, tabla): pesa más en equipos modestos '\n + 'y la guía de Apple no lo recomienda («no use Liquid Glass en la capa del contenido»). Úselo en una portada o una '\n + 'demostración, no en pantallas de trabajo.');\n }\n return {opacity, surfaces};\n}\n\n/**\n * @param {object} [options]\n * @param {{light?: object|boolean, dark?: object|boolean}} [options.colorSchemes] los colores de cada modo; por omisión los de\n * Stackcraft; true: los de MUI tal cual\n * @param {'class'|'data'|'media'|string} [options.colorSchemeSelector] cómo se elige el modo; por omisión 'class'\n * @param {string} [options.fontFamily] por omisión la letra del sistema\n * @param {boolean|{opacity?: number, surfaces?: boolean}} [options.glass] vidrio líquido en lo que flota y en los\n * controles; por omisión no. Con {opacity} se elige qué tan opaco (0 a 1; por omisión GLASS_OPACITY, 0.6) y con\n * {surfaces: true} también las tarjetas del contenido (por omisión sólidas, como pide la guía; avisa en consola)\n * @param {'standard'|'minimal'} [options.look] el estilo (THEME_LOOK): el de Stackcraft (por omisión) o el minimalista,\n * plano y tonal (sin bordes ni sombras: las superficies se separan por su tono; sin vidrio aunque se pida)\n * @param {'capsules'|'band'} [options.bar] la barra de arriba con vidrio o con el minimalista (TOP_BAR): por omisión en\n * cápsulas; 'band' la franja de lado a lado\n * @param {object} [options.components] ajustes propios de componentes (se suman; en el mismo estilo ganan los del proyecto)\n * @param {object} [options.overrides] cualquier otra cosa de createTheme (breakpoints, spacing…)\n * @returns {object} el tema de MUI\n */\nexport function createToolkitTheme({\n colorSchemes = {light: {palette: stackcraftLightPalette}, dark: {palette: stackcraftDarkPalette}},\n colorSchemeSelector = 'class',\n fontFamily = SYSTEM_FONT_FAMILY,\n glass = false,\n look = THEME_LOOK.standard,\n bar = TOP_BAR.capsules,\n components = {},\n overrides = {},\n} = {}) {\n // un nombre mal escrito dejaría la franja sin avisar: mejor que truene al armar el tema\n if (!Object.values(TOP_BAR).includes(bar)) {\n throw new RangeError(`createToolkitTheme: bar es '${TOP_BAR.capsules}' o '${TOP_BAR.band}' (llegó ${bar}).`);\n }\n const isMinimal = look === THEME_LOOK.minimal;\n // el minimalista es sólido: sin vidrio aunque se pida\n const glassSettings = isMinimal ? null : glassSettingsOf(glass);\n const frameworkComponents = isMinimal\n ? mergeComponents(toolkitComponents(), minimalComponents())\n : (glassSettings ? mergeComponents(toolkitComponents(), glassComponents({surfaces: glassSettings.surfaces})) : toolkitComponents());\n // el minimalista separa las superficies por su tono: el fondo gris agrupado y la tarjeta un escalón arriba\n const schemes = isMinimal\n ? Object.fromEntries(Object.entries(colorSchemes).map(([mode, scheme]) => [mode, {\n ...scheme, palette: {...scheme.palette, background: MINIMAL_BACKGROUND[mode] ?? scheme.palette?.background},\n }]))\n : colorSchemes;\n const baseTheme = createTheme({\n cssVariables: {colorSchemeSelector},\n colorSchemes: schemes,\n // el espaciado entre letras (tracking) de la tabla de SF Pro de Apple para cada tamaño (appleTracking.js). MUI\n // trae uno POSITIVO pensado para Roboto (+0.15 px en el texto, +0.4 px en botones y textos chicos) y con la letra del\n // sistema se veía la letra separada (lo reportaron: «kerning»); Apple, de 13 a 22 pt, lo cierra\n typography: {\n fontFamily,\n h1: {letterSpacing: trackingOf(96)},\n h2: {letterSpacing: trackingOf(60)},\n h3: {letterSpacing: trackingOf(48)},\n h4: {letterSpacing: trackingOf(34)},\n h5: {fontWeight: 600, letterSpacing: trackingOf(24)},\n h6: {fontWeight: 600, letterSpacing: trackingOf(20)},\n subtitle1: {letterSpacing: trackingOf(16)},\n subtitle2: {letterSpacing: trackingOf(14)},\n body1: {fontSize: '1rem', letterSpacing: trackingOf(16)},\n body2: {fontSize: '0.9375rem', letterSpacing: trackingOf(15)},\n button: {fontSize: '0.9375rem', textTransform: 'none', fontWeight: 500, letterSpacing: trackingOf(15)},\n caption: {letterSpacing: trackingOf(12)},\n },\n transitions: {\n duration: {\n shortest: 150,\n shorter: 180,\n short: 200,\n standard: 220,\n complex: 280,\n enteringScreen: 220,\n leavingScreen: 220,\n },\n },\n shape: {borderRadius: CORNER_RADIUS_PIXELS},\n components: mergeComponents(frameworkComponents, components),\n // sin sombras en el minimalista: lo plano es la idea\n ...(isMinimal && {shadows: Array(SHADOW_LEVELS).fill('none')}),\n toolkit: {glass: glassSettings, look, bar},\n ...overrides,\n });\n return responsiveFontSizes(baseTheme);\n}\n"],"names":[],"mappings":";;;;;;;;;;AAoCA,MAAM,MAAM,aAAa,OAAO;AAGhC,IAAI,8BAA8B;AAElC,MAAM,qBAAqB;AAC3B,MAAM,uBAAuB;AAE7B,MAAM,gBAAgB;AAGV,MAAC,aAAa,OAAO,OAAO,EAAC,UAAU,YAAY,SAAS,UAAS,CAAC;AAQtE,MAAC,UAAU,OAAO,OAAO,EAAC,MAAM,QAAQ,UAAU,WAAU,CAAC;AAQzE,SAAS,gBAAgB,OAAO;AAC5B,MAAI,CAAC,OAAO;AACR,WAAO;AAAA,EACX;AACA,QAAM,YAAY,UAAU,OAAO,CAAA,IAAK;AACxC,QAAM,UAAU,UAAU,WAAW;AAErC,MAAI,OAAO,YAAY,YAAY,UAAU,KAAK,UAAU,GAAG;AAC3D,UAAM,IAAI,WAAW,wDAAwD,OAAO,IAAI;AAAA,EAC5F;AACA,QAAM,WAAW,QAAQ,UAAU,QAAQ;AAE3C,MAAI,YAAY,CAAC,6BAA6B;AAC1C,kCAA8B;AAC9B,QAAI,KAAK,uQAEwC;AAAA,EACrD;AACA,SAAO,EAAC,SAAS,SAAQ;AAC7B;AAmBO,SAAS,mBAAmB;AAAA,EAC/B,eAAe,EAAC,OAAO,EAAC,SAAS,uBAAsB,GAAG,MAAM,EAAC,SAAS,sBAAqB,EAAC;AAAA,EAChG,sBAAsB;AAAA,EACtB,aAAa;AAAA,EACb,QAAQ;AAAA,EACR,OAAO,WAAW;AAAA,EAClB,MAAM,QAAQ;AAAA,EACd,aAAa,CAAA;AAAA,EACb,YAAY,CAAA;AAChB,IAAI,IAAI;AAEJ,MAAI,CAAC,OAAO,OAAO,OAAO,EAAE,SAAS,GAAG,GAAG;AACvC,UAAM,IAAI,WAAW,+BAA+B,QAAQ,QAAQ,QAAQ,QAAQ,IAAI,YAAY,GAAG,IAAI;AAAA,EAC/G;AACA,QAAM,YAAY,SAAS,WAAW;AAEtC,QAAM,gBAAgB,YAAY,OAAO,gBAAgB,KAAK;AAC9D,QAAM,sBAAsB,YACtB,gBAAgB,kBAAiB,GAAI,kBAAiB,CAAE,IACvD,gBAAgB,gBAAgB,kBAAiB,GAAI,gBAAgB,EAAC,UAAU,cAAc,SAAQ,CAAC,CAAC,IAAI,kBAAiB;AAEpI,QAAM,UAAU,YACV,OAAO,YAAY,OAAO,QAAQ,YAAY,EAAE,IAAI,CAAC,CAAC,MAAM,MAAM,MAAM,CAAC,MAAM;AAAA,IAC7E,GAAG;AAAA,IAAQ,SAAS,EAAC,GAAG,OAAO,SAAS,YAAY,mBAAmB,IAAI,KAAK,OAAO,SAAS,WAAU;AAAA,EACtH,CAAS,CAAC,CAAC,IACD;AACN,QAAM,YAAY,YAAY;AAAA,IAC1B,cAAc,EAAC,oBAAmB;AAAA,IAClC,cAAc;AAAA;AAAA;AAAA;AAAA,IAId,YAAY;AAAA,MACR;AAAA,MACA,IAAI,EAAC,eAAe,WAAW,EAAE,EAAC;AAAA,MAClC,IAAI,EAAC,eAAe,WAAW,EAAE,EAAC;AAAA,MAClC,IAAI,EAAC,eAAe,WAAW,EAAE,EAAC;AAAA,MAClC,IAAI,EAAC,eAAe,WAAW,EAAE,EAAC;AAAA,MAClC,IAAI,EAAC,YAAY,KAAK,eAAe,WAAW,EAAE,EAAC;AAAA,MACnD,IAAI,EAAC,YAAY,KAAK,eAAe,WAAW,EAAE,EAAC;AAAA,MACnD,WAAW,EAAC,eAAe,WAAW,EAAE,EAAC;AAAA,MACzC,WAAW,EAAC,eAAe,WAAW,EAAE,EAAC;AAAA,MACzC,OAAO,EAAC,UAAU,QAAQ,eAAe,WAAW,EAAE,EAAC;AAAA,MACvD,OAAO,EAAC,UAAU,aAAa,eAAe,WAAW,EAAE,EAAC;AAAA,MAC5D,QAAQ,EAAC,UAAU,aAAa,eAAe,QAAQ,YAAY,KAAK,eAAe,WAAW,EAAE,EAAC;AAAA,MACrG,SAAS,EAAC,eAAe,WAAW,EAAE,EAAC;AAAA,IACnD;AAAA,IACQ,aAAa;AAAA,MACT,UAAU;AAAA,QACN,UAAU;AAAA,QACV,SAAS;AAAA,QACT,OAAO;AAAA,QACP,UAAU;AAAA,QACV,SAAS;AAAA,QACT,gBAAgB;AAAA,QAChB,eAAe;AAAA,MAC/B;AAAA,IACA;AAAA,IACQ,OAAO,EAAC,cAAc,qBAAoB;AAAA,IAC1C,YAAY,gBAAgB,qBAAqB,UAAU;AAAA;AAAA,IAE3D,GAAI,aAAa,EAAC,SAAS,MAAM,aAAa,EAAE,KAAK,MAAM,EAAC;AAAA,IAC5D,SAAS,EAAC,OAAO,eAAe,MAAM,IAAG;AAAA,IACzC,GAAG;AAAA,EACX,CAAK;AACD,SAAO,oBAAoB,SAAS;AACxC;"}
1
+ {"version":3,"file":"createToolkitTheme.js","sources":["../../src/theme/createToolkitTheme.js"],"sourcesContent":["// El tema del framework: el estilo de Stackcraft (del tema de walk-ot) con claro y oscuro. Es opcional: ToolkitProvider\n// funciona con cualquier tema de MUI.\n//\n// const theme = createToolkitTheme(); // los colores de Stackcraft\n// const theme = createToolkitTheme({glass: true}); // con vidrio líquido en lo que flota\n// const theme = createToolkitTheme({glass: {opacity: 0.7}}); // con vidrio más transparente (por omisión 0.8)\n// const theme = createToolkitTheme({glass: {surfaces: true}}); // también las tarjetas del contenido (con advertencia)\n// const theme = createToolkitTheme({glass: true, bar: TOP_BAR.band}); // la barra de arriba en franja (por omisión, cápsulas)\n// const theme = createToolkitTheme({colorSchemes: {light: {palette: {primary: {main: '#0B5FFF'}}}, dark: {...}}});\n//\n// Lo que trae, con su porqué:\n// - variables CSS y los dos modos: ColorSchemeToggle cambia de modo sin volver a pintar, y las gráficas leen sus colores\n// del tema (theme.vars): con variables cambian solas al cambiar de modo, sin ellas se quedarían con las del arranque.\n// - colorSchemeSelector 'class': el modo lo elige la persona con el botón (con 'media' solo mandaría el sistema\n// operativo y el botón no haría nada). Con Next.js va con <InitColorSchemeScript attribute=\"class\" />.\n// - letra base de 16 px: con menos, el iPhone hace zoom al entrar a un campo. body1 16 px y body2 15 px en el teléfono;\n// responsiveFontSizes los ajusta por ancho de pantalla.\n// - transiciones de 220 ms, iguales al abrir y al cerrar: más ágiles que las de Material (300 ms) y sin el desfase de\n// 30 ms entre lo que solo conoce una transición (la barra) y lo que conoce dos (el menú lateral).\n// - esquinas de 8 px y los ajustes de componentes de toolkitComponents().\n// - glass: el vidrio líquido en lo que flota y en los controles (glassSurface.js explica dónde y por qué). Queda anotado\n// en theme.toolkit.glass para que los componentes del framework que flotan (el marco, los avisos) lo sepan sin otra prop.\n// - components: los ajustes propios del proyecto se SUMAN a los del framework (mergeComponents), no los borran.\n//\n// Funciones puras, sin React: function (regla de la casa).\nimport {createTheme, responsiveFontSizes} from '@mui/material/styles';\n\nimport {glassComponents} from '@/theme/components/glassComponents.js';\nimport {GLASS_OPACITY} from '@/theme/glassSurface.js';\nimport {mergeComponents} from '@/theme/mergeComponents.js';\nimport {stackcraftDarkPalette, stackcraftLightPalette} from '@/theme/stackcraftPalette.js';\nimport {trackingOf} from '@/theme/appleTracking.js';\nimport {toolkitComponents} from '@/theme/toolkitComponents.js';\nimport {MINIMAL_BACKGROUND, minimalComponents} from '@/theme/components/minimalComponents.js';\nimport {createLogger} from '@/observability/logger.js';\n\nconst log = createLogger('theme');\n// el aviso de las tarjetas de vidrio sale una vez por pestaña: el tema se vuelve a armar cada vez que cambia algo (un\n// configurador de tema lo arma en cada clic) y la consola se llenaba del mismo aviso\nlet hasWarnedAboutGlassSurfaces = false;\n\nconst SYSTEM_FONT_FAMILY = 'system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif';\nconst CORNER_RADIUS_PIXELS = 8;\n// cuántos niveles de sombra trae el tema de MUI (del 0 al 24)\nconst SHADOW_LEVELS = 25;\n\n/** El estilo del tema: el de Stackcraft (con vidrio o sin él) o el minimalista, plano y tonal. */\nexport const THEME_LOOK = Object.freeze({standard: 'standard', minimal: 'minimal'});\n\n/**\n * La barra de arriba, con vidrio y con el minimalista: por omisión como las ventanas de macOS Tahoe, sin franja (el nombre\n * y los botones en cápsulas por grupo; lo eligió el autor al verlas), o la franja de lado a lado. Con vidrio las cápsulas\n * son de vidrio y el contenido pasa por debajo desenfocado; con el minimalista son bloques del tono de las tarjetas sobre\n * el color del fondo (AppLayout). El estilo de Stackcraft sin vidrio se queda con su barra de siempre.\n */\nexport const TOP_BAR = Object.freeze({band: 'band', capsules: 'capsules'});\n\n/**\n * Lo que pidió el proyecto del vidrio, ya revisado: false (sin vidrio) o {opacity, surfaces}.\n *\n * @param {boolean|{opacity?: number, surfaces?: boolean}} glass\n * @returns {false|{opacity: number, surfaces: boolean}}\n */\nfunction glassSettingsOf(glass) {\n if (!glass) {\n return false;\n }\n const requested = glass === true ? {} : glass;\n const opacity = requested.opacity ?? GLASS_OPACITY;\n // un 80 en lugar de 0.8 daría un color inválido y la barra saldría sin fondo, sin ningún aviso\n if (typeof opacity !== 'number' || opacity < 0 || opacity > 1) {\n throw new RangeError(`createToolkitTheme: glass.opacity va de 0 a 1 (llegó ${opacity}).`);\n }\n const surfaces = Boolean(requested.surfaces);\n // se permite, pero con advertencia: la guía de Apple pide el contenido sin vidrio y cada tarjeta desenfoca lo de atrás\n if (surfaces && !hasWarnedAboutGlassSurfaces) {\n hasWarnedAboutGlassSurfaces = true;\n log.warn('glass.surfaces pone vidrio también en el contenido (tarjetas, avisos, tabla): pesa más en equipos modestos '\n + 'y la guía de Apple no lo recomienda («no use Liquid Glass en la capa del contenido»). Úselo en una portada o una '\n + 'demostración, no en pantallas de trabajo.');\n }\n return {opacity, surfaces};\n}\n\n/**\n * @param {object} [options]\n * @param {{light?: object|boolean, dark?: object|boolean}} [options.colorSchemes] los colores de cada modo; por omisión los de\n * Stackcraft; true: los de MUI tal cual\n * @param {'class'|'data'|'media'|string} [options.colorSchemeSelector] cómo se elige el modo; por omisión 'class'\n * @param {string} [options.fontFamily] por omisión la letra del sistema\n * @param {boolean|{opacity?: number, surfaces?: boolean}} [options.glass] vidrio líquido en lo que flota y en los\n * controles; por omisión no. Con {opacity} se elige qué tan opaco (0 a 1; por omisión GLASS_OPACITY, 0.6) y con\n * {surfaces: true} también las tarjetas del contenido (por omisión sólidas, como pide la guía; avisa en consola)\n * @param {'standard'|'minimal'} [options.look] el estilo (THEME_LOOK): el de Stackcraft (por omisión) o el minimalista,\n * plano y tonal (sin bordes ni sombras: las superficies se separan por su tono; sin vidrio aunque se pida)\n * @param {'capsules'|'band'} [options.bar] la barra de arriba con vidrio o con el minimalista (TOP_BAR): por omisión en\n * cápsulas; 'band' la franja de lado a lado\n * @param {object} [options.components] ajustes propios de componentes (se suman; en el mismo estilo ganan los del proyecto)\n * @param {object} [options.overrides] cualquier otra cosa de createTheme (breakpoints, spacing…)\n * @returns {object} el tema de MUI\n */\nexport function createToolkitTheme({\n colorSchemes = {light: {palette: stackcraftLightPalette}, dark: {palette: stackcraftDarkPalette}},\n colorSchemeSelector = 'class',\n fontFamily = SYSTEM_FONT_FAMILY,\n glass = false,\n look = THEME_LOOK.standard,\n bar = TOP_BAR.capsules,\n components = {},\n overrides = {},\n} = {}) {\n // un nombre mal escrito dejaría la franja sin avisar: mejor que truene al armar el tema\n if (!Object.values(TOP_BAR).includes(bar)) {\n throw new RangeError(`createToolkitTheme: bar es '${TOP_BAR.capsules}' o '${TOP_BAR.band}' (llegó ${bar}).`);\n }\n const isMinimal = look === THEME_LOOK.minimal;\n // el minimalista es sólido: sin vidrio aunque se pida\n const glassSettings = isMinimal ? null : glassSettingsOf(glass);\n const frameworkComponents = isMinimal\n ? mergeComponents(toolkitComponents(), minimalComponents())\n : (glassSettings ? mergeComponents(toolkitComponents(), glassComponents({surfaces: glassSettings.surfaces})) : toolkitComponents());\n // el minimalista separa las superficies por su tono: el fondo gris agrupado y la tarjeta un escalón arriba\n const schemes = isMinimal\n ? Object.fromEntries(Object.entries(colorSchemes).map(([mode, scheme]) => [mode, {\n ...scheme, palette: {...scheme.palette, background: MINIMAL_BACKGROUND[mode] ?? scheme.palette?.background},\n }]))\n : colorSchemes;\n const baseTheme = createTheme({\n cssVariables: {colorSchemeSelector},\n colorSchemes: schemes,\n // el espaciado entre letras (tracking) de la tabla de SF Pro de Apple para cada tamaño (appleTracking.js). MUI\n // trae uno POSITIVO pensado para Roboto (+0.15 px en el texto, +0.4 px en botones y textos chicos) y con la letra del\n // sistema se veía la letra separada (lo reportaron: «kerning»); Apple, de 13 a 22 pt, lo cierra\n typography: {\n fontFamily,\n h1: {letterSpacing: trackingOf(96)},\n h2: {letterSpacing: trackingOf(60)},\n h3: {letterSpacing: trackingOf(48)},\n h4: {letterSpacing: trackingOf(34)},\n h5: {fontWeight: 600, letterSpacing: trackingOf(24)},\n h6: {fontWeight: 600, letterSpacing: trackingOf(20)},\n subtitle1: {letterSpacing: trackingOf(16)},\n subtitle2: {letterSpacing: trackingOf(14)},\n body1: {fontSize: '1rem', letterSpacing: trackingOf(16)},\n body2: {fontSize: '0.9375rem', letterSpacing: trackingOf(15)},\n button: {fontSize: '0.9375rem', textTransform: 'none', fontWeight: 500, letterSpacing: trackingOf(15)},\n caption: {letterSpacing: trackingOf(12)},\n },\n transitions: {\n duration: {\n shortest: 150,\n shorter: 180,\n short: 200,\n standard: 220,\n complex: 280,\n enteringScreen: 220,\n leavingScreen: 220,\n },\n },\n shape: {borderRadius: CORNER_RADIUS_PIXELS},\n components: mergeComponents(frameworkComponents, components),\n // sin sombras en el minimalista: lo plano es la idea\n ...(isMinimal && {shadows: Array(SHADOW_LEVELS).fill('none')}),\n toolkit: {glass: glassSettings, look, bar},\n ...overrides,\n });\n return responsiveFontSizes(baseTheme);\n}\n"],"names":[],"mappings":"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"}
@@ -4,6 +4,7 @@ import { MuiButton } from "./components/MuiButton.js";
4
4
  import { MuiButtonBase } from "./components/MuiButtonBase.js";
5
5
  import { MuiChip } from "./components/MuiChip.js";
6
6
  import { MuiDataGrid } from "./components/MuiDataGrid.js";
7
+ import { MuiDialog } from "./components/MuiDialog.js";
7
8
  import { MuiDrawer } from "./components/MuiDrawer.js";
8
9
  import { MuiMenuItem } from "./components/MuiMenuItem.js";
9
10
  import { MuiTypography } from "./components/MuiTypography.js";
@@ -15,6 +16,7 @@ function toolkitComponents() {
15
16
  ...MuiButtonBase(),
16
17
  ...MuiChip(),
17
18
  ...MuiDataGrid(),
19
+ ...MuiDialog(),
18
20
  ...MuiDrawer(),
19
21
  ...MuiMenuItem(),
20
22
  ...MuiTypography()
@@ -1 +1 @@
1
- {"version":3,"file":"toolkitComponents.js","sources":["../../src/theme/toolkitComponents.js"],"sourcesContent":["// Los ajustes de componentes de MUI del framework (del tema de walk-ot), juntos. Un archivo por componente en\n// theme/components/: para cambiar cómo se ve un componente se abre el suyo y nada más.\n//\n// createToolkitTheme() ya los trae. Un proyecto con su propio createTheme los puede usar sueltos:\n// createTheme({components: {...toolkitComponents(), ...losMios}})\n//\n// Funciones puras, sin React: function (regla de la casa).\nimport {MuiAppBar} from '@/theme/components/MuiAppBar.js';\nimport {MuiBackdrop} from '@/theme/components/MuiBackdrop.js';\nimport {MuiButton} from '@/theme/components/MuiButton.js';\nimport {MuiButtonBase} from '@/theme/components/MuiButtonBase.js';\nimport {MuiChip} from '@/theme/components/MuiChip.js';\nimport {MuiDataGrid} from '@/theme/components/MuiDataGrid.js';\nimport {MuiDrawer} from '@/theme/components/MuiDrawer.js';\nimport {MuiMenuItem} from '@/theme/components/MuiMenuItem.js';\nimport {MuiTypography} from '@/theme/components/MuiTypography.js';\n\n/**\n * @returns {object} el `components` del tema de MUI\n */\nexport function toolkitComponents() {\n return {\n ...MuiAppBar(),\n ...MuiBackdrop(),\n ...MuiButton(),\n ...MuiButtonBase(),\n ...MuiChip(),\n ...MuiDataGrid(),\n ...MuiDrawer(),\n ...MuiMenuItem(),\n ...MuiTypography(),\n };\n}\n"],"names":[],"mappings":";;;;;;;;;AAoBO,SAAS,oBAAoB;AAChC,SAAO;AAAA,IACH,GAAG,UAAS;AAAA,IACZ,GAAG,YAAW;AAAA,IACd,GAAG,UAAS;AAAA,IACZ,GAAG,cAAa;AAAA,IAChB,GAAG,QAAO;AAAA,IACV,GAAG,YAAW;AAAA,IACd,GAAG,UAAS;AAAA,IACZ,GAAG,YAAW;AAAA,IACd,GAAG,cAAa;AAAA,EACxB;AACA;"}
1
+ {"version":3,"file":"toolkitComponents.js","sources":["../../src/theme/toolkitComponents.js"],"sourcesContent":["// Los ajustes de componentes de MUI del framework (del tema de walk-ot), juntos. Un archivo por componente en\n// theme/components/: para cambiar cómo se ve un componente se abre el suyo y nada más.\n//\n// createToolkitTheme() ya los trae. Un proyecto con su propio createTheme los puede usar sueltos:\n// createTheme({components: {...toolkitComponents(), ...losMios}})\n//\n// Funciones puras, sin React: function (regla de la casa).\nimport {MuiAppBar} from '@/theme/components/MuiAppBar.js';\nimport {MuiBackdrop} from '@/theme/components/MuiBackdrop.js';\nimport {MuiButton} from '@/theme/components/MuiButton.js';\nimport {MuiButtonBase} from '@/theme/components/MuiButtonBase.js';\nimport {MuiChip} from '@/theme/components/MuiChip.js';\nimport {MuiDataGrid} from '@/theme/components/MuiDataGrid.js';\nimport {MuiDialog} from '@/theme/components/MuiDialog.js';\nimport {MuiDrawer} from '@/theme/components/MuiDrawer.js';\nimport {MuiMenuItem} from '@/theme/components/MuiMenuItem.js';\nimport {MuiTypography} from '@/theme/components/MuiTypography.js';\n\n/**\n * @returns {object} el `components` del tema de MUI\n */\nexport function toolkitComponents() {\n return {\n ...MuiAppBar(),\n ...MuiBackdrop(),\n ...MuiButton(),\n ...MuiButtonBase(),\n ...MuiChip(),\n ...MuiDataGrid(),\n ...MuiDialog(),\n ...MuiDrawer(),\n ...MuiMenuItem(),\n ...MuiTypography(),\n };\n}\n"],"names":[],"mappings":";;;;;;;;;;AAqBO,SAAS,oBAAoB;AAChC,SAAO;AAAA,IACH,GAAG,UAAS;AAAA,IACZ,GAAG,YAAW;AAAA,IACd,GAAG,UAAS;AAAA,IACZ,GAAG,cAAa;AAAA,IAChB,GAAG,QAAO;AAAA,IACV,GAAG,YAAW;AAAA,IACd,GAAG,UAAS;AAAA,IACZ,GAAG,UAAS;AAAA,IACZ,GAAG,YAAW;AAAA,IACd,GAAG,cAAa;AAAA,EACxB;AACA;"}
@@ -0,0 +1,13 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { useContext, useMemo } from "react";
4
+ import { ComponentTextsContext } from "./componentTextsContext.js";
5
+ function ComponentTexts({ namespace, texts, children }) {
6
+ const parent = useContext(ComponentTextsContext);
7
+ const layer = useMemo(() => ({ namespace, texts, parent }), [namespace, texts, parent]);
8
+ return /* @__PURE__ */ jsx(ComponentTextsContext, { value: layer, children });
9
+ }
10
+ export {
11
+ ComponentTexts as default
12
+ };
13
+ //# sourceMappingURL=ComponentTexts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ComponentTexts.js","sources":["../../src/toolkit/ComponentTexts.jsx"],"sourcesContent":["// Cambia los textos de un namespace del paquete para todo lo que va adentro: un módulo con su propio vocabulario\n// («pedido» en lugar de «registro» en todas sus tablas) sin tocar el idioma completo del proveedor.\n//\n// <ComponentTexts namespace=\"table\" texts={{count: {records_one: '{{total}} pedido', records_other: '{{total}} pedidos'}}}>\n// <ServerTable … />\n// </ComponentTexts>\n//\n// `texts` lleva la misma forma y las mismas llaves que ese namespace en los textos del paquete (src/locales/es.js), con sus\n// plurales (_one, _other) y {{variables}}, ya en el idioma de la pantalla. Lo que no trae sale del proveedor y del paquete.\n// Los componentes con varios textos reciben lo mismo en su prop `texts`, que gana sobre esto.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useContext, useMemo} from 'react';\n\nimport {ComponentTextsContext} from '@/toolkit/componentTextsContext.js';\n\n/**\n * @param {object} props\n * @param {string} props.namespace 'table', 'common', 'account'… (los de src/locales/es.js)\n * @param {object} [props.texts] los textos que cambian, con la forma de ese namespace\n * @param {import('react').ReactNode} props.children\n */\nexport default function ComponentTexts({namespace, texts, children}) {\n const parent = useContext(ComponentTextsContext);\n const layer = useMemo(() => ({namespace, texts, parent}), [namespace, texts, parent]);\n\n return <ComponentTextsContext value={layer}>{children}</ComponentTextsContext>;\n}\n"],"names":[],"mappings":";;;;AAsBA,SAAwB,eAAe,EAAC,WAAW,OAAO,YAAW;AACjE,QAAM,SAAS,WAAW,qBAAqB;AAC/C,QAAM,QAAQ,QAAQ,OAAO,EAAC,WAAW,OAAO,OAAA,IAAU,CAAC,WAAW,OAAO,MAAM,CAAC;AAEpF,SAAO,oBAAC,uBAAA,EAAsB,OAAO,OAAQ,SAAA,CAAS;AAC1D;"}