@stackcraftmx/calcita 0.31.0 → 0.32.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 (278) hide show
  1. package/README.md +7 -0
  2. package/dist/DataTable/AuditTrailDialog.js +4 -1
  3. package/dist/DataTable/AuditTrailDialog.js.map +1 -1
  4. package/dist/DataTable/AuditTrailTable.js +4 -1
  5. package/dist/DataTable/AuditTrailTable.js.map +1 -1
  6. package/dist/DataTable/ColumnFilters/HeaderFilterPopover.js +3 -1
  7. package/dist/DataTable/ColumnFilters/HeaderFilterPopover.js.map +1 -1
  8. package/dist/DataTable/ColumnFilters/MultiSelectColumnFilter.js +3 -1
  9. package/dist/DataTable/ColumnFilters/MultiSelectColumnFilter.js.map +1 -1
  10. package/dist/DataTable/ColumnFilters/SortableFilterHeader.js +4 -1
  11. package/dist/DataTable/ColumnFilters/SortableFilterHeader.js.map +1 -1
  12. package/dist/DataTable/ColumnFilters/TextColumnFilter.js +3 -1
  13. package/dist/DataTable/ColumnFilters/TextColumnFilter.js.map +1 -1
  14. package/dist/DataTable/EmptyState.js +3 -1
  15. package/dist/DataTable/EmptyState.js.map +1 -1
  16. package/dist/DataTable/FilterChips.js +3 -1
  17. package/dist/DataTable/FilterChips.js.map +1 -1
  18. package/dist/DataTable/GoToPage.js +3 -1
  19. package/dist/DataTable/GoToPage.js.map +1 -1
  20. package/dist/DataTable/ServerTable.js +4 -1
  21. package/dist/DataTable/ServerTable.js.map +1 -1
  22. package/dist/DataTable/columns.js +3 -1
  23. package/dist/DataTable/columns.js.map +1 -1
  24. package/dist/access/Can.js +3 -1
  25. package/dist/access/Can.js.map +1 -1
  26. package/dist/access/PermissionMatrix.js +3 -1
  27. package/dist/access/PermissionMatrix.js.map +1 -1
  28. package/dist/access/RequireAccess.js +3 -1
  29. package/dist/access/RequireAccess.js.map +1 -1
  30. package/dist/account/AccountProvider.js +3 -1
  31. package/dist/account/AccountProvider.js.map +1 -1
  32. package/dist/account/AccountSecuritySection.js +3 -1
  33. package/dist/account/AccountSecuritySection.js.map +1 -1
  34. package/dist/account/DemoTotpCodeButton.js +3 -1
  35. package/dist/account/DemoTotpCodeButton.js.map +1 -1
  36. package/dist/account/ForgotPasswordScreen.js +3 -1
  37. package/dist/account/ForgotPasswordScreen.js.map +1 -1
  38. package/dist/account/ProfileScreen.js +4 -1
  39. package/dist/account/ProfileScreen.js.map +1 -1
  40. package/dist/account/ResetPasswordScreen.js +4 -1
  41. package/dist/account/ResetPasswordScreen.js.map +1 -1
  42. package/dist/account/SignInScreen.js +3 -1
  43. package/dist/account/SignInScreen.js.map +1 -1
  44. package/dist/appShell/AccountMenu.js +3 -1
  45. package/dist/appShell/AccountMenu.js.map +1 -1
  46. package/dist/appShell/AppLayout.js +4 -1
  47. package/dist/appShell/AppLayout.js.map +1 -1
  48. package/dist/appShell/AppearanceSettings.js +4 -1
  49. package/dist/appShell/AppearanceSettings.js.map +1 -1
  50. package/dist/appShell/AuthLayout.js +3 -1
  51. package/dist/appShell/AuthLayout.js.map +1 -1
  52. package/dist/appShell/BarActions.js +3 -1
  53. package/dist/appShell/BarActions.js.map +1 -1
  54. package/dist/appShell/BarSearch.js +3 -1
  55. package/dist/appShell/BarSearch.js.map +1 -1
  56. package/dist/appShell/HeaderTools.js +4 -1
  57. package/dist/appShell/HeaderTools.js.map +1 -1
  58. package/dist/appShell/MenuLinks.js +3 -1
  59. package/dist/appShell/MenuLinks.js.map +1 -1
  60. package/dist/appShell/MobileTabBar.js +3 -1
  61. package/dist/appShell/MobileTabBar.js.map +1 -1
  62. package/dist/appShell/SettingsMenu.js +3 -1
  63. package/dist/appShell/SettingsMenu.js.map +1 -1
  64. package/dist/appShell/SideMenu.js +4 -1
  65. package/dist/appShell/SideMenu.js.map +1 -1
  66. package/dist/appShell/SideMenuEntry.js +3 -1
  67. package/dist/appShell/SideMenuEntry.js.map +1 -1
  68. package/dist/appShell/SubmenuActionsMenu.js +3 -1
  69. package/dist/appShell/SubmenuActionsMenu.js.map +1 -1
  70. package/dist/appShell/TabBarEditor.js +3 -1
  71. package/dist/appShell/TabBarEditor.js.map +1 -1
  72. package/dist/appShell/TopMenu.js +3 -1
  73. package/dist/appShell/TopMenu.js.map +1 -1
  74. package/dist/appShell/Wordmark.js +3 -1
  75. package/dist/appShell/Wordmark.js.map +1 -1
  76. package/dist/appShell/pageTitle.js +3 -1
  77. package/dist/appShell/pageTitle.js.map +1 -1
  78. package/dist/auth/PasswordChecklist.js +3 -1
  79. package/dist/auth/PasswordChecklist.js.map +1 -1
  80. package/dist/calendar/EventCalendar.js +4 -1
  81. package/dist/calendar/EventCalendar.js.map +1 -1
  82. package/dist/calendar/EventDetails.js +3 -1
  83. package/dist/calendar/EventDetails.js.map +1 -1
  84. package/dist/datePickers/DateInput.js +4 -1
  85. package/dist/datePickers/DateInput.js.map +1 -1
  86. package/dist/downloads/DownloadManager.js +3 -1
  87. package/dist/downloads/DownloadManager.js.map +1 -1
  88. package/dist/feedback/ConfirmDialog.js +4 -1
  89. package/dist/feedback/ConfirmDialog.js.map +1 -1
  90. package/dist/feedback/ModalWaiting.js +3 -1
  91. package/dist/feedback/ModalWaiting.js.map +1 -1
  92. package/dist/feedback/NewBuildNotice.js +3 -1
  93. package/dist/feedback/NewBuildNotice.js.map +1 -1
  94. package/dist/filepond/FileDropzone.js +4 -1
  95. package/dist/filepond/FileDropzone.js.map +1 -1
  96. package/dist/formatting.js +8 -5
  97. package/dist/formatting.js.map +1 -1
  98. package/dist/forms/FormProblemAlert.js +3 -1
  99. package/dist/forms/FormProblemAlert.js.map +1 -1
  100. package/dist/forms/FormServerProblem.js +3 -1
  101. package/dist/forms/FormServerProblem.js.map +1 -1
  102. package/dist/forms/FormWizard.js +3 -1
  103. package/dist/forms/FormWizard.js.map +1 -1
  104. package/dist/forms/auth/ChangePasswordForm.js +3 -1
  105. package/dist/forms/auth/ChangePasswordForm.js.map +1 -1
  106. package/dist/forms/auth/ForgotPasswordForm.js +3 -1
  107. package/dist/forms/auth/ForgotPasswordForm.js.map +1 -1
  108. package/dist/forms/auth/SignInForm.js +3 -1
  109. package/dist/forms/auth/SignInForm.js.map +1 -1
  110. package/dist/forms/security/SecondStepForm.js +3 -1
  111. package/dist/forms/security/SecondStepForm.js.map +1 -1
  112. package/dist/forms/security/TwoStepVerificationCard.js +3 -1
  113. package/dist/forms/security/TwoStepVerificationCard.js.map +1 -1
  114. package/dist/hookForm/FormProblemAlert.js +3 -1
  115. package/dist/hookForm/FormProblemAlert.js.map +1 -1
  116. package/dist/hookForm/FormWizard.js +3 -1
  117. package/dist/hookForm/FormWizard.js.map +1 -1
  118. package/dist/inbox/InboxLayout.js +4 -1
  119. package/dist/inbox/InboxLayout.js.map +1 -1
  120. package/dist/inbox/MessageList.js +4 -1
  121. package/dist/inbox/MessageList.js.map +1 -1
  122. package/dist/inbox/MessageView.js +3 -1
  123. package/dist/inbox/MessageView.js.map +1 -1
  124. package/dist/index.js +3 -1
  125. package/dist/inputs/CatalogInput.js +4 -1
  126. package/dist/inputs/CatalogInput.js.map +1 -1
  127. package/dist/inputs/ColorInput.js +4 -1
  128. package/dist/inputs/ColorInput.js.map +1 -1
  129. package/dist/inputs/FieldFrame.js +3 -1
  130. package/dist/inputs/FieldFrame.js.map +1 -1
  131. package/dist/inputs/FormSection.js +5 -2
  132. package/dist/inputs/FormSection.js.map +1 -1
  133. package/dist/inputs/NumberInput.js +4 -1
  134. package/dist/inputs/NumberInput.js.map +1 -1
  135. package/dist/inputs/SelectInput.js +4 -1
  136. package/dist/inputs/SelectInput.js.map +1 -1
  137. package/dist/inputs/WizardLayout.js +4 -1
  138. package/dist/inputs/WizardLayout.js.map +1 -1
  139. package/dist/kanban/KanbanBoard.js +4 -1
  140. package/dist/kanban/KanbanBoard.js.map +1 -1
  141. package/dist/maps/MapView.js +4 -1
  142. package/dist/maps/MapView.js.map +1 -1
  143. package/dist/pwa/InstallAppSetting.js +3 -1
  144. package/dist/pwa/InstallAppSetting.js.map +1 -1
  145. package/dist/pwa/InstallInstructionsDialog.js +3 -1
  146. package/dist/pwa/InstallInstructionsDialog.js.map +1 -1
  147. package/dist/pwa/InstallPromptBanner.js +3 -1
  148. package/dist/pwa/InstallPromptBanner.js.map +1 -1
  149. package/dist/security/ActivityLog.js +3 -1
  150. package/dist/security/ActivityLog.js.map +1 -1
  151. package/dist/security/RecoveryCodesDialog.js +3 -1
  152. package/dist/security/RecoveryCodesDialog.js.map +1 -1
  153. package/dist/security/SessionsCard.js +3 -1
  154. package/dist/security/SessionsCard.js.map +1 -1
  155. package/dist/theme/createToolkitTheme.js +4 -1
  156. package/dist/theme/createToolkitTheme.js.map +1 -1
  157. package/dist/toolkit/ComponentTexts.js +3 -1
  158. package/dist/toolkit/ComponentTexts.js.map +1 -1
  159. package/dist/toolkit/ToolkitProvider.js +24 -3
  160. package/dist/toolkit/ToolkitProvider.js.map +1 -1
  161. package/dist/toolkit/toolkitContext.js.map +1 -1
  162. package/dist/toolkit/toolkitSettings.js +9 -0
  163. package/dist/toolkit/toolkitSettings.js.map +1 -1
  164. package/dist/toolkit/unknownProps.js +33 -0
  165. package/dist/toolkit/unknownProps.js.map +1 -0
  166. package/dist/types/DataTable/AuditTrailTable.d.ts +1 -1
  167. package/dist/types/DataTable/ColumnFilters/TextColumnFilter.d.ts +1 -1
  168. package/dist/types/DataTable/columns.d.ts +3 -1
  169. package/dist/types/access/Can.d.ts +1 -1
  170. package/dist/types/access/RequireAccess.d.ts +1 -1
  171. package/dist/types/account/AccountProvider.d.ts +1 -1
  172. package/dist/types/account/ForgotPasswordScreen.d.ts +1 -1
  173. package/dist/types/account/ResetPasswordScreen.d.ts +1 -1
  174. package/dist/types/appShell/MenuLinks.d.ts +1 -1
  175. package/dist/types/appShell/pageTitle.d.ts +1 -1
  176. package/dist/types/datePickers/DateInput.d.ts +1 -1
  177. package/dist/types/downloads/DownloadManager.d.ts +1 -1
  178. package/dist/types/formatting.d.ts +2 -1
  179. package/dist/types/forms/FormProblemAlert.d.ts +1 -1
  180. package/dist/types/forms/FormServerProblem.d.ts +1 -1
  181. package/dist/types/forms/FormWizard.d.ts +1 -1
  182. package/dist/types/forms/auth/ChangePasswordForm.d.ts +1 -1
  183. package/dist/types/forms/auth/ForgotPasswordForm.d.ts +1 -1
  184. package/dist/types/hookForm/FormProblemAlert.d.ts +1 -1
  185. package/dist/types/hookForm/FormWizard.d.ts +1 -1
  186. package/dist/types/index.d.ts +1 -1
  187. package/dist/types/inputs/CatalogInput.d.ts +1 -1
  188. package/dist/types/inputs/FieldFrame.d.ts +1 -1
  189. package/dist/types/inputs/NumberInput.d.ts +1 -1
  190. package/dist/types/pwa/InstallAppSetting.d.ts +1 -1
  191. package/dist/types/theme/createToolkitTheme.d.ts +1 -1
  192. package/dist/types/toolkit/ComponentTexts.d.ts +1 -1
  193. package/dist/types/toolkit/ToolkitProvider.d.ts +4 -1
  194. package/dist/types/toolkit/toolkitContext.d.ts +2 -1
  195. package/dist/types/toolkit/toolkitSettings.d.ts +13 -2
  196. package/dist/types/toolkit/unknownProps.d.ts +23 -0
  197. package/dist/types/ui/actions/CopyButton.d.ts +1 -1
  198. package/dist/types/ui/actions/SecretField.d.ts +1 -1
  199. package/dist/types/ui/labels/DictionaryText.d.ts +1 -1
  200. package/dist/types/ui/layout/FullPageLoader.d.ts +1 -1
  201. package/dist/types/ui/metrics/CalendarHeatmap.d.ts +9 -8
  202. package/dist/types/ui/metrics/chartParts.d.ts +3 -3
  203. package/dist/types/ui/metrics/heatmapScale.d.ts +23 -0
  204. package/dist/types/ui/navigation/DocumentTitle.d.ts +1 -1
  205. package/dist/ui/actions/ActionsMenu.js +3 -1
  206. package/dist/ui/actions/ActionsMenu.js.map +1 -1
  207. package/dist/ui/actions/CopyButton.js +3 -1
  208. package/dist/ui/actions/CopyButton.js.map +1 -1
  209. package/dist/ui/actions/DownloadMenu.js +4 -1
  210. package/dist/ui/actions/DownloadMenu.js.map +1 -1
  211. package/dist/ui/actions/SecretField.js +3 -1
  212. package/dist/ui/actions/SecretField.js.map +1 -1
  213. package/dist/ui/actions/SelectionActions.js +3 -1
  214. package/dist/ui/actions/SelectionActions.js.map +1 -1
  215. package/dist/ui/filters/DateRangeFilter.js +3 -1
  216. package/dist/ui/filters/DateRangeFilter.js.map +1 -1
  217. package/dist/ui/filters/PeriodRangeSelector.js +3 -1
  218. package/dist/ui/filters/PeriodRangeSelector.js.map +1 -1
  219. package/dist/ui/filters/QuickFilters.js +3 -1
  220. package/dist/ui/filters/QuickFilters.js.map +1 -1
  221. package/dist/ui/filters/SegmentedControl.js +3 -1
  222. package/dist/ui/filters/SegmentedControl.js.map +1 -1
  223. package/dist/ui/labels/DictionaryText.js +3 -1
  224. package/dist/ui/labels/DictionaryText.js.map +1 -1
  225. package/dist/ui/labels/StatusChip.js +3 -1
  226. package/dist/ui/labels/StatusChip.js.map +1 -1
  227. package/dist/ui/layout/CollapsibleSection.js +3 -1
  228. package/dist/ui/layout/CollapsibleSection.js.map +1 -1
  229. package/dist/ui/layout/ContentDialog.js +3 -1
  230. package/dist/ui/layout/ContentDialog.js.map +1 -1
  231. package/dist/ui/layout/DefinitionList.js +3 -1
  232. package/dist/ui/layout/DefinitionList.js.map +1 -1
  233. package/dist/ui/layout/DetailHeader.js +3 -1
  234. package/dist/ui/layout/DetailHeader.js.map +1 -1
  235. package/dist/ui/layout/ErrorPage.js +4 -1
  236. package/dist/ui/layout/ErrorPage.js.map +1 -1
  237. package/dist/ui/layout/FullPageLoader.js +3 -1
  238. package/dist/ui/layout/FullPageLoader.js.map +1 -1
  239. package/dist/ui/layout/PageHeader.js +3 -1
  240. package/dist/ui/layout/PageHeader.js.map +1 -1
  241. package/dist/ui/layout/PageHeading.js +3 -1
  242. package/dist/ui/layout/PageHeading.js.map +1 -1
  243. package/dist/ui/layout/ProfileHeader.js +3 -1
  244. package/dist/ui/layout/ProfileHeader.js.map +1 -1
  245. package/dist/ui/layout/SectionCard.js +3 -1
  246. package/dist/ui/layout/SectionCard.js.map +1 -1
  247. package/dist/ui/metrics/BarList.js +3 -1
  248. package/dist/ui/metrics/BarList.js.map +1 -1
  249. package/dist/ui/metrics/CalendarHeatmap.js +13 -4
  250. package/dist/ui/metrics/CalendarHeatmap.js.map +1 -1
  251. package/dist/ui/metrics/ChartTooltip.js.map +1 -1
  252. package/dist/ui/metrics/ColumnChart.js +4 -1
  253. package/dist/ui/metrics/ColumnChart.js.map +1 -1
  254. package/dist/ui/metrics/LineChart.js +4 -1
  255. package/dist/ui/metrics/LineChart.js.map +1 -1
  256. package/dist/ui/metrics/MetricCard.js +4 -1
  257. package/dist/ui/metrics/MetricCard.js.map +1 -1
  258. package/dist/ui/metrics/TableToggle.js +3 -1
  259. package/dist/ui/metrics/TableToggle.js.map +1 -1
  260. package/dist/ui/metrics/chartParts.js +7 -3
  261. package/dist/ui/metrics/chartParts.js.map +1 -1
  262. package/dist/ui/metrics/heatmapScale.js +15 -1
  263. package/dist/ui/metrics/heatmapScale.js.map +1 -1
  264. package/dist/ui/navigation/ColorSchemeToggle.js +3 -1
  265. package/dist/ui/navigation/ColorSchemeToggle.js.map +1 -1
  266. package/dist/ui/navigation/DocumentTitle.js +3 -1
  267. package/dist/ui/navigation/DocumentTitle.js.map +1 -1
  268. package/dist/ui/navigation/FontSizeToggle.js +3 -1
  269. package/dist/ui/navigation/FontSizeToggle.js.map +1 -1
  270. package/dist/ui/navigation/LanguageToggle.js +3 -1
  271. package/dist/ui/navigation/LanguageToggle.js.map +1 -1
  272. package/dist/ui/navigation/PageBreadcrumbs.js +3 -1
  273. package/dist/ui/navigation/PageBreadcrumbs.js.map +1 -1
  274. package/dist/ui/timeline/GroupedTimeline.js +3 -1
  275. package/dist/ui/timeline/GroupedTimeline.js.map +1 -1
  276. package/dist/upload/FileDropzone.js +4 -1
  277. package/dist/upload/FileDropzone.js.map +1 -1
  278. package/package.json +1 -1
@@ -10,9 +10,11 @@ import InstallInstructionsDialog from "./InstallInstructionsDialog.js";
10
10
  import { useInstallPrompt } from "./useInstallPrompt.js";
11
11
  import ComponentTexts from "../toolkit/ComponentTexts.js";
12
12
  import { piecePart } from "../theme/themePieces.js";
13
+ import { warnUnknownProps } from "../toolkit/unknownProps.js";
13
14
  const THEME_NAME = "CalcitaInstallAppSetting";
14
15
  const InstallRoot = piecePart(THEME_NAME, "Root", Box);
15
- function InstallAppSetting({ texts } = {}) {
16
+ function InstallAppSetting({ texts, ...unknownProps } = {}) {
17
+ warnUnknownProps("InstallAppSetting", unknownProps);
16
18
  const { t } = useToolkitTranslation("common", texts);
17
19
  const { variant, promptInstall } = useInstallPrompt();
18
20
  const [stepsOpen, setStepsOpen] = useState(false);
@@ -1 +1 @@
1
- {"version":3,"file":"InstallAppSetting.js","sources":["../../src/pwa/InstallAppSetting.jsx"],"sourcesContent":["// «Instalar la app» dentro del menú de la cuenta (accountSettings de AppLayout): para quien dijo «Ahora no» a la invitación\n// y después sí la quiere. No respeta el «Ahora no» (aquí la persona la busca a propósito). Ya instalada o sin manera de\n// instalar en ese equipo, no pinta nada.\n//\n// El diálogo de los pasos va aquí mismo: el menú de la cuenta se queda abierto mientras se ve, y al cerrarlo se regresa al\n// menú.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaInstallAppSetting con defaultProps y\n// styleOverrides de sus partes: root.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useState} from 'react';\nimport Box from '@mui/material/Box';\nimport Button from '@mui/material/Button';\nimport InstallMobileRoundedIcon from '@mui/icons-material/InstallMobileRounded';\n\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';\nimport {piecePart} from '@/theme/themePieces.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaInstallAppSetting)\nconst THEME_NAME = 'CalcitaInstallAppSetting';\nconst InstallRoot = piecePart(THEME_NAME, 'Root', Box);\n\n/**\n * @param {object} [props]\n * @param {object} [props.texts] los textos que cambian, con la forma de `common` en los textos del paquete\n */\nexport default function InstallAppSetting({texts} = {}) {\n const {t} = useToolkitTranslation('common', texts);\n const {variant, promptInstall} = useInstallPrompt();\n const [stepsOpen, setStepsOpen] = useState(false);\n\n if (!variant) {\n return null;\n }\n\n const handleClick = () => {\n if (variant === INSTALL_VARIANT.native) {\n promptInstall();\n } else {\n setStepsOpen(true);\n }\n };\n\n return (\n <ComponentTexts namespace=\"common\" texts={texts}>\n <InstallRoot ownSx={{px: 1, py: 0.5}}>\n <Button fullWidth variant=\"outlined\" startIcon={<InstallMobileRoundedIcon />} onClick={handleClick}>\n {t('install.menuLabel')}\n </Button>\n <InstallInstructionsDialog variant={variant} open={stepsOpen} onClose={() => setStepsOpen(false)} />\n </InstallRoot>\n </ComponentTexts>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;;AAwBA,MAAM,aAAa;AACnB,MAAM,cAAc,UAAU,YAAY,QAAQ,GAAG;AAMrD,SAAwB,kBAAkB,EAAC,MAAA,IAAS,IAAI;AACpD,QAAM,EAAC,EAAA,IAAK,sBAAsB,UAAU,KAAK;AACjD,QAAM,EAAC,SAAS,cAAA,IAAiB,iBAAA;AACjC,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,KAAK;AAEhD,MAAI,CAAC,SAAS;AACV,WAAO;AAAA,EACX;AAEA,QAAM,cAAc,MAAM;AACtB,QAAI,YAAY,gBAAgB,QAAQ;AACpC,oBAAA;AAAA,IACJ,OAAO;AACH,mBAAa,IAAI;AAAA,IACrB;AAAA,EACJ;AAEA,SACI,oBAAC,gBAAA,EAAe,WAAU,UAAS,OAC/B,UAAA,qBAAC,aAAA,EAAY,OAAO,EAAC,IAAI,GAAG,IAAI,OAC5B,UAAA;AAAA,IAAA,oBAAC,QAAA,EAAO,WAAS,MAAC,SAAQ,YAAW,WAAW,oBAAC,0BAAA,CAAA,CAAyB,GAAI,SAAS,aAClF,UAAA,EAAE,mBAAmB,GAC1B;AAAA,IACA,oBAAC,6BAA0B,SAAkB,MAAM,WAAW,SAAS,MAAM,aAAa,KAAK,EAAA,CAAG;AAAA,EAAA,EAAA,CACtG,EAAA,CACJ;AAER;"}
1
+ {"version":3,"file":"InstallAppSetting.js","sources":["../../src/pwa/InstallAppSetting.jsx"],"sourcesContent":["// «Instalar la app» dentro del menú de la cuenta (accountSettings de AppLayout): para quien dijo «Ahora no» a la invitación\n// y después sí la quiere. No respeta el «Ahora no» (aquí la persona la busca a propósito). Ya instalada o sin manera de\n// instalar en ese equipo, no pinta nada.\n//\n// El diálogo de los pasos va aquí mismo: el menú de la cuenta se queda abierto mientras se ve, y al cerrarlo se regresa al\n// menú.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaInstallAppSetting con defaultProps y\n// styleOverrides de sus partes: root.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useState} from 'react';\nimport Box from '@mui/material/Box';\nimport Button from '@mui/material/Button';\nimport InstallMobileRoundedIcon from '@mui/icons-material/InstallMobileRounded';\n\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';\nimport {piecePart} from '@/theme/themePieces.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaInstallAppSetting)\nconst THEME_NAME = 'CalcitaInstallAppSetting';\nconst InstallRoot = piecePart(THEME_NAME, 'Root', Box);\n\n/**\n * @param {object} [props]\n * @param {object} [props.texts] los textos que cambian, con la forma de `common` en los textos del paquete\n */\nexport default function InstallAppSetting({texts, ...unknownProps} = {}) {\n warnUnknownProps('InstallAppSetting', unknownProps);\n const {t} = useToolkitTranslation('common', texts);\n const {variant, promptInstall} = useInstallPrompt();\n const [stepsOpen, setStepsOpen] = useState(false);\n\n if (!variant) {\n return null;\n }\n\n const handleClick = () => {\n if (variant === INSTALL_VARIANT.native) {\n promptInstall();\n } else {\n setStepsOpen(true);\n }\n };\n\n return (\n <ComponentTexts namespace=\"common\" texts={texts}>\n <InstallRoot ownSx={{px: 1, py: 0.5}}>\n <Button fullWidth variant=\"outlined\" startIcon={<InstallMobileRoundedIcon />} onClick={handleClick}>\n {t('install.menuLabel')}\n </Button>\n <InstallInstructionsDialog variant={variant} open={stepsOpen} onClose={() => setStepsOpen(false)} />\n </InstallRoot>\n </ComponentTexts>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;AAyBA,MAAM,aAAa;AACnB,MAAM,cAAc,UAAU,YAAY,QAAQ,GAAG;AAMrD,SAAwB,kBAAkB,EAAC,OAAO,GAAG,aAAA,IAAgB,CAAA,GAAI;AACrE,mBAAiB,qBAAqB,YAAY;AAClD,QAAM,EAAC,EAAA,IAAK,sBAAsB,UAAU,KAAK;AACjD,QAAM,EAAC,SAAS,cAAA,IAAiB,iBAAA;AACjC,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,KAAK;AAEhD,MAAI,CAAC,SAAS;AACV,WAAO;AAAA,EACX;AAEA,QAAM,cAAc,MAAM;AACtB,QAAI,YAAY,gBAAgB,QAAQ;AACpC,oBAAA;AAAA,IACJ,OAAO;AACH,mBAAa,IAAI;AAAA,IACrB;AAAA,EACJ;AAEA,SACI,oBAAC,gBAAA,EAAe,WAAU,UAAS,OAC/B,UAAA,qBAAC,aAAA,EAAY,OAAO,EAAC,IAAI,GAAG,IAAI,OAC5B,UAAA;AAAA,IAAA,oBAAC,QAAA,EAAO,WAAS,MAAC,SAAQ,YAAW,WAAW,oBAAC,0BAAA,CAAA,CAAyB,GAAI,SAAS,aAClF,UAAA,EAAE,mBAAmB,GAC1B;AAAA,IACA,oBAAC,6BAA0B,SAAkB,MAAM,WAAW,SAAS,MAAM,aAAa,KAAK,EAAA,CAAG;AAAA,EAAA,EAAA,CACtG,EAAA,CACJ;AAER;"}
@@ -9,6 +9,7 @@ import Typography from "@mui/material/Typography";
9
9
  import { useToolkitTranslation } from "../toolkit/useToolkitTranslation.js";
10
10
  import { INSTALL_VARIANT } from "./installInstructions.js";
11
11
  import { piecePart, usePieceProps } from "../theme/themePieces.js";
12
+ import { warnUnknownProps } from "../toolkit/unknownProps.js";
12
13
  const THEME_NAME = "CalcitaInstallInstructions";
13
14
  const InstructionsSteps = piecePart(THEME_NAME, "Steps", Typography);
14
15
  const STEPS_BY_VARIANT = Object.freeze({
@@ -20,7 +21,8 @@ function variantHasInstructions(variant) {
20
21
  return Boolean(STEPS_BY_VARIANT[variant]);
21
22
  }
22
23
  function InstallInstructionsDialog(props) {
23
- const { variant, open, onClose, texts } = usePieceProps(props, THEME_NAME);
24
+ const { variant, open, onClose, texts, ...unknownProps } = usePieceProps(props, THEME_NAME);
25
+ warnUnknownProps(THEME_NAME, unknownProps);
24
26
  const { t } = useToolkitTranslation("common", texts);
25
27
  const steps = STEPS_BY_VARIANT[variant];
26
28
  if (!steps) {
@@ -1 +1 @@
1
- {"version":3,"file":"InstallInstructionsDialog.js","sources":["../../src/pwa/InstallInstructionsDialog.jsx"],"sourcesContent":["// Los pasos para instalar la app a mano, según el equipo (iOS 26 cambió el camino; Firefox usa su propio menú). Lo usan la\n// invitación (InstallPromptBanner) y la entrada del menú de la cuenta (InstallAppSetting): los dos abren el mismo diálogo.\n// Si la variante no lleva pasos (el navegador tiene su propio diálogo, o no hay nada que ofrecer) no pinta nada.\n//\n// OJO, para quien lo monte: NO dentro de un Menu de MUI que se cierra; el Menu quita a sus hijos al cerrarse y el diálogo\n// nunca aparecería. Va junto al Menu o en un lugar que se quede pintado.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaInstallInstructions con defaultProps y\n// styleOverrides de sus partes: steps.\n//\n// Genérico del paquete: function (regla de la casa).\nimport Button from '@mui/material/Button';\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 Typography from '@mui/material/Typography';\n\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {INSTALL_VARIANT} from '@/pwa/installInstructions.js';\nimport {piecePart, usePieceProps} from '@/theme/themePieces.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaInstallInstructions)\nconst THEME_NAME = 'CalcitaInstallInstructions';\nconst InstructionsSteps = piecePart(THEME_NAME, 'Steps', Typography);\n\n// cada variante con pasos a mano → su título y sus pasos en los textos del paquete (common:install.<variante>)\nconst STEPS_BY_VARIANT = Object.freeze({\n [INSTALL_VARIANT.iosModern]: ['step1', 'step2', 'step3', 'step4'],\n [INSTALL_VARIANT.iosClassic]: ['step1', 'step2', 'step3'],\n [INSTALL_VARIANT.androidFirefox]: ['step1', 'step2', 'step3'],\n});\n\n/**\n * ¿Esta variante se instala con pasos a mano (y no con el diálogo del navegador)?\n *\n * @param {string|null} variant\n * @returns {boolean}\n */\nexport function variantHasInstructions(variant) {\n return Boolean(STEPS_BY_VARIANT[variant]);\n}\n\n/**\n * @param {object} props\n * @param {string|null} props.variant una de INSTALL_VARIANT (useInstallPrompt)\n * @param {boolean} props.open\n * @param {() => void} props.onClose\n * @param {object} [props.texts] los textos que cambian, con la forma de `common` en los textos del paquete\n */\nexport default function InstallInstructionsDialog(props) {\n // lo que el proyecto puso en su tema (components.CalcitaInstallInstructions.defaultProps) y encima lo que llega aquí\n const {variant, open, onClose, texts} = usePieceProps(props, THEME_NAME);\n const {t} = useToolkitTranslation('common', texts);\n const steps = STEPS_BY_VARIANT[variant];\n if (!steps) {\n return null;\n }\n\n return (\n <Dialog open={open} onClose={onClose} maxWidth=\"xs\" fullWidth>\n <DialogTitle>{t(`install.${variant}.title`)}</DialogTitle>\n <DialogContent>\n {/* pasos numerados con palabras, sin dibujar los íconos del sistema: cambian entre versiones */}\n <InstructionsSteps component=\"ol\" ownSx={{m: 0, pl: 3, display: 'grid', gap: 1.25}}>\n {steps.map((step) => (\n <Typography key={step} component=\"li\" variant=\"body1\">{t(`install.${variant}.${step}`)}</Typography>\n ))}\n </InstructionsSteps>\n </DialogContent>\n <DialogActions>\n <Button variant=\"contained\" onClick={onClose}>{t('install.gotIt')}</Button>\n </DialogActions>\n </Dialog>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;AAuBA,MAAM,aAAa;AACnB,MAAM,oBAAoB,UAAU,YAAY,SAAS,UAAU;AAGnE,MAAM,mBAAmB,OAAO,OAAO;AAAA,EACnC,CAAC,gBAAgB,SAAS,GAAG,CAAC,SAAS,SAAS,SAAS,OAAO;AAAA,EAChE,CAAC,gBAAgB,UAAU,GAAG,CAAC,SAAS,SAAS,OAAO;AAAA,EACxD,CAAC,gBAAgB,cAAc,GAAG,CAAC,SAAS,SAAS,OAAO;AAChE,CAAC;AAQM,SAAS,uBAAuB,SAAS;AAC5C,SAAO,QAAQ,iBAAiB,OAAO,CAAC;AAC5C;AASA,SAAwB,0BAA0B,OAAO;AAErD,QAAM,EAAC,SAAS,MAAM,SAAS,UAAS,cAAc,OAAO,UAAU;AACvE,QAAM,EAAC,EAAA,IAAK,sBAAsB,UAAU,KAAK;AACjD,QAAM,QAAQ,iBAAiB,OAAO;AACtC,MAAI,CAAC,OAAO;AACR,WAAO;AAAA,EACX;AAEA,8BACK,QAAA,EAAO,MAAY,SAAkB,UAAS,MAAK,WAAS,MACzD,UAAA;AAAA,IAAA,oBAAC,aAAA,EAAa,UAAA,EAAE,WAAW,OAAO,QAAQ,GAAE;AAAA,wBAC3C,eAAA,EAEG,UAAA,oBAAC,mBAAA,EAAkB,WAAU,MAAK,OAAO,EAAC,GAAG,GAAG,IAAI,GAAG,SAAS,QAAQ,KAAK,QACxE,UAAA,MAAM,IAAI,CAAC,SACR,oBAAC,YAAA,EAAsB,WAAU,MAAK,SAAQ,SAAS,UAAA,EAAE,WAAW,OAAO,IAAI,IAAI,EAAE,KAApE,IAAsE,CAC1F,GACL,GACJ;AAAA,IACA,oBAAC,eAAA,EACG,UAAA,oBAAC,QAAA,EAAO,SAAQ,aAAY,SAAS,SAAU,UAAA,EAAE,eAAe,EAAA,CAAE,EAAA,CACtE;AAAA,EAAA,GACJ;AAER;"}
1
+ {"version":3,"file":"InstallInstructionsDialog.js","sources":["../../src/pwa/InstallInstructionsDialog.jsx"],"sourcesContent":["// Los pasos para instalar la app a mano, según el equipo (iOS 26 cambió el camino; Firefox usa su propio menú). Lo usan la\n// invitación (InstallPromptBanner) y la entrada del menú de la cuenta (InstallAppSetting): los dos abren el mismo diálogo.\n// Si la variante no lleva pasos (el navegador tiene su propio diálogo, o no hay nada que ofrecer) no pinta nada.\n//\n// OJO, para quien lo monte: NO dentro de un Menu de MUI que se cierra; el Menu quita a sus hijos al cerrarse y el diálogo\n// nunca aparecería. Va junto al Menu o en un lugar que se quede pintado.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaInstallInstructions con defaultProps y\n// styleOverrides de sus partes: steps.\n//\n// Genérico del paquete: function (regla de la casa).\nimport Button from '@mui/material/Button';\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 Typography from '@mui/material/Typography';\n\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {INSTALL_VARIANT} from '@/pwa/installInstructions.js';\nimport {piecePart, usePieceProps} from '@/theme/themePieces.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaInstallInstructions)\nconst THEME_NAME = 'CalcitaInstallInstructions';\nconst InstructionsSteps = piecePart(THEME_NAME, 'Steps', Typography);\n\n// cada variante con pasos a mano → su título y sus pasos en los textos del paquete (common:install.<variante>)\nconst STEPS_BY_VARIANT = Object.freeze({\n [INSTALL_VARIANT.iosModern]: ['step1', 'step2', 'step3', 'step4'],\n [INSTALL_VARIANT.iosClassic]: ['step1', 'step2', 'step3'],\n [INSTALL_VARIANT.androidFirefox]: ['step1', 'step2', 'step3'],\n});\n\n/**\n * ¿Esta variante se instala con pasos a mano (y no con el diálogo del navegador)?\n *\n * @param {string|null} variant\n * @returns {boolean}\n */\nexport function variantHasInstructions(variant) {\n return Boolean(STEPS_BY_VARIANT[variant]);\n}\n\n/**\n * @param {object} props\n * @param {string|null} props.variant una de INSTALL_VARIANT (useInstallPrompt)\n * @param {boolean} props.open\n * @param {() => void} props.onClose\n * @param {object} [props.texts] los textos que cambian, con la forma de `common` en los textos del paquete\n */\nexport default function InstallInstructionsDialog(props) {\n // lo que el proyecto puso en su tema (components.CalcitaInstallInstructions.defaultProps) y encima lo que llega aquí\n const {variant, open, onClose, texts, ...unknownProps} = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const {t} = useToolkitTranslation('common', texts);\n const steps = STEPS_BY_VARIANT[variant];\n if (!steps) {\n return null;\n }\n\n return (\n <Dialog open={open} onClose={onClose} maxWidth=\"xs\" fullWidth>\n <DialogTitle>{t(`install.${variant}.title`)}</DialogTitle>\n <DialogContent>\n {/* pasos numerados con palabras, sin dibujar los íconos del sistema: cambian entre versiones */}\n <InstructionsSteps component=\"ol\" ownSx={{m: 0, pl: 3, display: 'grid', gap: 1.25}}>\n {steps.map((step) => (\n <Typography key={step} component=\"li\" variant=\"body1\">{t(`install.${variant}.${step}`)}</Typography>\n ))}\n </InstructionsSteps>\n </DialogContent>\n <DialogActions>\n <Button variant=\"contained\" onClick={onClose}>{t('install.gotIt')}</Button>\n </DialogActions>\n </Dialog>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;;AAwBA,MAAM,aAAa;AACnB,MAAM,oBAAoB,UAAU,YAAY,SAAS,UAAU;AAGnE,MAAM,mBAAmB,OAAO,OAAO;AAAA,EACnC,CAAC,gBAAgB,SAAS,GAAG,CAAC,SAAS,SAAS,SAAS,OAAO;AAAA,EAChE,CAAC,gBAAgB,UAAU,GAAG,CAAC,SAAS,SAAS,OAAO;AAAA,EACxD,CAAC,gBAAgB,cAAc,GAAG,CAAC,SAAS,SAAS,OAAO;AAChE,CAAC;AAQM,SAAS,uBAAuB,SAAS;AAC5C,SAAO,QAAQ,iBAAiB,OAAO,CAAC;AAC5C;AASA,SAAwB,0BAA0B,OAAO;AAErD,QAAM,EAAC,SAAS,MAAM,SAAS,OAAO,GAAG,iBAAgB,cAAc,OAAO,UAAU;AACxF,mBAAiB,YAAY,YAAY;AACzC,QAAM,EAAC,EAAA,IAAK,sBAAsB,UAAU,KAAK;AACjD,QAAM,QAAQ,iBAAiB,OAAO;AACtC,MAAI,CAAC,OAAO;AACR,WAAO;AAAA,EACX;AAEA,8BACK,QAAA,EAAO,MAAY,SAAkB,UAAS,MAAK,WAAS,MACzD,UAAA;AAAA,IAAA,oBAAC,aAAA,EAAa,UAAA,EAAE,WAAW,OAAO,QAAQ,GAAE;AAAA,wBAC3C,eAAA,EAEG,UAAA,oBAAC,mBAAA,EAAkB,WAAU,MAAK,OAAO,EAAC,GAAG,GAAG,IAAI,GAAG,SAAS,QAAQ,KAAK,QACxE,UAAA,MAAM,IAAI,CAAC,SACR,oBAAC,YAAA,EAAsB,WAAU,MAAK,SAAQ,SAAS,UAAA,EAAE,WAAW,OAAO,IAAI,IAAI,EAAE,KAApE,IAAsE,CAC1F,GACL,GACJ;AAAA,IACA,oBAAC,eAAA,EACG,UAAA,oBAAC,QAAA,EAAO,SAAQ,aAAY,SAAS,SAAU,UAAA,EAAE,eAAe,EAAA,CAAE,EAAA,CACtE;AAAA,EAAA,GACJ;AAER;"}
@@ -15,12 +15,14 @@ import InstallInstructionsDialog from "./InstallInstructionsDialog.js";
15
15
  import { useInstallPrompt } from "./useInstallPrompt.js";
16
16
  import ComponentTexts from "../toolkit/ComponentTexts.js";
17
17
  import { piecePart, usePieceProps } from "../theme/themePieces.js";
18
+ import { warnUnknownProps } from "../toolkit/unknownProps.js";
18
19
  const THEME_NAME = "CalcitaInstallPromptBanner";
19
20
  const PromptRoot = piecePart(THEME_NAME, "Root", Snackbar);
20
21
  const PromptAlert = piecePart(THEME_NAME, "Alert", Alert);
21
22
  const TAB_BAR_GAP_PIXELS = 8;
22
23
  function InstallPromptBanner(props) {
23
- const { message, onInstallChoice, onShowSteps, onDismiss, texts } = usePieceProps(props, THEME_NAME);
24
+ const { message, onInstallChoice, onShowSteps, onDismiss, texts, ...unknownProps } = usePieceProps(props, THEME_NAME);
25
+ warnUnknownProps(THEME_NAME, unknownProps);
24
26
  const { t } = useToolkitTranslation("common", texts);
25
27
  const { variant, dismissed, promptInstall, dismiss } = useInstallPrompt();
26
28
  const [stepsOpen, setStepsOpen] = useState(false);
@@ -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// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaInstallPromptBanner con defaultProps y\n// styleOverrides de sus partes: root, alert.\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';\nimport {piecePart, usePieceProps} from '@/theme/themePieces.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaInstallPromptBanner)\nconst THEME_NAME = 'CalcitaInstallPromptBanner';\nconst PromptRoot = piecePart(THEME_NAME, 'Root', Snackbar);\nconst PromptAlert = piecePart(THEME_NAME, 'Alert', Alert);\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(props) {\n // lo que el proyecto puso en su tema (components.CalcitaInstallPromptBanner.defaultProps) y encima lo que llega aquí\n const {message, onInstallChoice, onShowSteps, onDismiss, texts} = usePieceProps(props, THEME_NAME);\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 <PromptRoot open anchorOrigin={{vertical: 'bottom', horizontal: 'center'}}\n ownSx={(theme) => (usesMobileTabBar(theme)\n ? aboveMobileTabBarOf(TAB_BAR_GAP_PIXELS)\n : {mb: 'env(safe-area-inset-bottom)'})}>\n <PromptAlert severity=\"info\" icon={<InstallMobileRoundedIcon />} ownSx={{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 </PromptAlert>\n </PromptRoot>\n <InstallInstructionsDialog variant={variant} open={stepsOpen} onClose={handleStepsClose} />\n </>\n </ComponentTexts>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AA8BA,MAAM,aAAa;AACnB,MAAM,aAAa,UAAU,YAAY,QAAQ,QAAQ;AACzD,MAAM,cAAc,UAAU,YAAY,SAAS,KAAK;AAGxD,MAAM,qBAAqB;AAW3B,SAAwB,oBAAoB,OAAO;AAE/C,QAAM,EAAC,SAAS,iBAAiB,aAAa,WAAW,UAAS,cAAc,OAAO,UAAU;AACjG,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,QAAW,MAAI;AAAA,QAAC,cAAc,EAAC,UAAU,UAAU,YAAY,SAAA;AAAA,QACtD,OAAO,CAAC,UAAW,iBAAiB,KAAK,IACnC,oBAAoB,kBAAkB,IACtC,EAAC,IAAI,8BAAA;AAAA,QACjB,UAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YAAY,UAAS;AAAA,YAAO,0BAAO,0BAAA,EAAyB;AAAA,YAAI,OAAO,EAAC,OAAO,QAAQ,YAAY,SAAA;AAAA,YAC7F,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;"}
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// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaInstallPromptBanner con defaultProps y\n// styleOverrides de sus partes: root, alert.\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';\nimport {piecePart, usePieceProps} from '@/theme/themePieces.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaInstallPromptBanner)\nconst THEME_NAME = 'CalcitaInstallPromptBanner';\nconst PromptRoot = piecePart(THEME_NAME, 'Root', Snackbar);\nconst PromptAlert = piecePart(THEME_NAME, 'Alert', Alert);\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(props) {\n // lo que el proyecto puso en su tema (components.CalcitaInstallPromptBanner.defaultProps) y encima lo que llega aquí\n const {message, onInstallChoice, onShowSteps, onDismiss, texts, ...unknownProps} = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\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 <PromptRoot open anchorOrigin={{vertical: 'bottom', horizontal: 'center'}}\n ownSx={(theme) => (usesMobileTabBar(theme)\n ? aboveMobileTabBarOf(TAB_BAR_GAP_PIXELS)\n : {mb: 'env(safe-area-inset-bottom)'})}>\n <PromptAlert severity=\"info\" icon={<InstallMobileRoundedIcon />} ownSx={{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 </PromptAlert>\n </PromptRoot>\n <InstallInstructionsDialog variant={variant} open={stepsOpen} onClose={handleStepsClose} />\n </>\n </ComponentTexts>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;AA+BA,MAAM,aAAa;AACnB,MAAM,aAAa,UAAU,YAAY,QAAQ,QAAQ;AACzD,MAAM,cAAc,UAAU,YAAY,SAAS,KAAK;AAGxD,MAAM,qBAAqB;AAW3B,SAAwB,oBAAoB,OAAO;AAE/C,QAAM,EAAC,SAAS,iBAAiB,aAAa,WAAW,OAAO,GAAG,aAAA,IAAgB,cAAc,OAAO,UAAU;AAClH,mBAAiB,YAAY,YAAY;AACzC,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,QAAW,MAAI;AAAA,QAAC,cAAc,EAAC,UAAU,UAAU,YAAY,SAAA;AAAA,QACtD,OAAO,CAAC,UAAW,iBAAiB,KAAK,IACnC,oBAAoB,kBAAkB,IACtC,EAAC,IAAI,8BAAA;AAAA,QACjB,UAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YAAY,UAAS;AAAA,YAAO,0BAAO,0BAAA,EAAyB;AAAA,YAAI,OAAO,EAAC,OAAO,QAAQ,YAAY,SAAA;AAAA,YAC7F,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;"}
@@ -22,6 +22,7 @@ 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
  import { piecePart, usePieceProps } from "../theme/themePieces.js";
25
+ import { warnUnknownProps } from "../toolkit/unknownProps.js";
25
26
  const THEME_NAME = "CalcitaActivityLog";
26
27
  const ActivityItemIcon = piecePart(THEME_NAME, "ItemIcon", ListItemIcon);
27
28
  const ACCOUNT_EVENT_KIND = Object.freeze({
@@ -53,7 +54,8 @@ const EVENT_LOOK = Object.freeze({
53
54
  const DEFAULT_LOOK = Object.freeze({ Icon: EventNoteOutlinedIcon });
54
55
  const LOADING_HEIGHT_PIXELS = 160;
55
56
  function ActivityLog(props) {
56
- const { events, error = null, texts } = usePieceProps(props, THEME_NAME);
57
+ const { events, error = null, texts, ...unknownProps } = usePieceProps(props, THEME_NAME);
58
+ warnUnknownProps(THEME_NAME, unknownProps);
57
59
  const { t } = useToolkitTranslation("security", texts);
58
60
  const { formatDateTime } = useFormatters();
59
61
  return /* @__PURE__ */ jsx(SectionCard, { title: t("activity.title"), children: /* @__PURE__ */ jsxs(Stack, { spacing: 2, children: [
@@ -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// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaActivityLog con defaultProps y\n// styleOverrides de sus partes: itemIcon.\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';\nimport {piecePart, usePieceProps} from '@/theme/themePieces.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaActivityLog)\nconst THEME_NAME = 'CalcitaActivityLog';\nconst ActivityItemIcon = piecePart(THEME_NAME, 'ItemIcon', ListItemIcon);\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(props) {\n // lo que el proyecto puso en su tema (components.CalcitaActivityLog.defaultProps) y encima lo que llega aquí\n const {events, error = null, texts} = usePieceProps(props, THEME_NAME);\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 <ActivityItemIcon ownSx={{minWidth: 36}}><look.Icon fontSize=\"small\" color={look.color ?? 'action'} /></ActivityItemIcon>\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// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaActivityLog con defaultProps y\n// styleOverrides de sus partes: itemIcon.\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';\nimport {piecePart, usePieceProps} from '@/theme/themePieces.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaActivityLog)\nconst THEME_NAME = 'CalcitaActivityLog';\nconst ActivityItemIcon = piecePart(THEME_NAME, 'ItemIcon', ListItemIcon);\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(props) {\n // lo que el proyecto puso en su tema (components.CalcitaActivityLog.defaultProps) y encima lo que llega aquí\n const {events, error = null, texts, ...unknownProps} = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\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 <ActivityItemIcon ownSx={{minWidth: 36}}><look.Icon fontSize=\"small\" color={look.color ?? 'action'} /></ActivityItemIcon>\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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"}
@@ -17,6 +17,7 @@ import { useToolkitSettings } from "../toolkit/toolkitContext.js";
17
17
  import { useToolkitTranslation } from "../toolkit/useToolkitTranslation.js";
18
18
  import CopyButton from "../ui/actions/CopyButton.js";
19
19
  import { piecePart, usePieceProps } from "../theme/themePieces.js";
20
+ import { warnUnknownProps } from "../toolkit/unknownProps.js";
20
21
  const THEME_NAME = "CalcitaRecoveryCodesDialog";
21
22
  const RecoveryCodes = piecePart(THEME_NAME, "Codes", Box);
22
23
  const RecoveryActions = piecePart(THEME_NAME, "Actions", Stack);
@@ -25,7 +26,8 @@ function todayText() {
25
26
  return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
26
27
  }
27
28
  function RecoveryCodesDialog(props) {
28
- const { codes, account = "", onClose, onCodesSaved, texts } = usePieceProps(props, THEME_NAME);
29
+ const { codes, account = "", onClose, onCodesSaved, texts, ...unknownProps } = usePieceProps(props, THEME_NAME);
30
+ warnUnknownProps(THEME_NAME, unknownProps);
29
31
  const { t } = useToolkitTranslation("security", texts);
30
32
  const { appName } = useToolkitSettings();
31
33
  const [isSaved, setIsSaved] = useState(false);
@@ -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// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaRecoveryCodesDialog con defaultProps y\n// styleOverrides de sus partes: codes, actions.\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';\nimport {piecePart, usePieceProps} from '@/theme/themePieces.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaRecoveryCodesDialog)\nconst THEME_NAME = 'CalcitaRecoveryCodesDialog';\nconst RecoveryCodes = piecePart(THEME_NAME, 'Codes', Box);\nconst RecoveryActions = piecePart(THEME_NAME, 'Actions', Stack);\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(props) {\n // lo que el proyecto puso en su tema (components.CalcitaRecoveryCodesDialog.defaultProps) y encima lo que llega aquí\n const {codes, account = '', onClose, onCodesSaved, texts} = usePieceProps(props, THEME_NAME);\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 <RecoveryCodes component=\"ol\" ownSx={{\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 </RecoveryCodes>\n <RecoveryActions direction=\"row\" spacing={1} ownSx={{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 </RecoveryActions>\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":"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"}
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// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaRecoveryCodesDialog con defaultProps y\n// styleOverrides de sus partes: codes, actions.\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';\nimport {piecePart, usePieceProps} from '@/theme/themePieces.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaRecoveryCodesDialog)\nconst THEME_NAME = 'CalcitaRecoveryCodesDialog';\nconst RecoveryCodes = piecePart(THEME_NAME, 'Codes', Box);\nconst RecoveryActions = piecePart(THEME_NAME, 'Actions', Stack);\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(props) {\n // lo que el proyecto puso en su tema (components.CalcitaRecoveryCodesDialog.defaultProps) y encima lo que llega aquí\n const {codes, account = '', onClose, onCodesSaved, texts, ...unknownProps} = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\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 <RecoveryCodes component=\"ol\" ownSx={{\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 </RecoveryCodes>\n <RecoveryActions direction=\"row\" spacing={1} ownSx={{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 </RecoveryActions>\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":"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"}
@@ -21,6 +21,7 @@ import Tag from "../ui/labels/Tag.js";
21
21
  import SectionCard from "../ui/layout/SectionCard.js";
22
22
  import ComponentTexts from "../toolkit/ComponentTexts.js";
23
23
  import { piecePart, usePieceProps } from "../theme/themePieces.js";
24
+ import { warnUnknownProps } from "../toolkit/unknownProps.js";
24
25
  const THEME_NAME = "CalcitaSessionsCard";
25
26
  const SessionsItem = piecePart(THEME_NAME, "Item", ListItem);
26
27
  const SessionsItemIcon = piecePart(THEME_NAME, "ItemIcon", ListItemIcon);
@@ -48,7 +49,8 @@ function SessionRow({ session }) {
48
49
  ] });
49
50
  }
50
51
  function SessionsCard(props) {
51
- const { sessions, error = null, onCloseOthers, onClosed, texts } = usePieceProps(props, THEME_NAME);
52
+ const { sessions, error = null, onCloseOthers, onClosed, texts, ...unknownProps } = usePieceProps(props, THEME_NAME);
53
+ warnUnknownProps(THEME_NAME, unknownProps);
52
54
  const { t } = useToolkitTranslation("security", texts);
53
55
  const othersCount = (sessions ?? []).filter((session) => !session.isCurrent).length;
54
56
  const confirmation = useConfirmAction({
@@ -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// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaSessionsCard con defaultProps y\n// styleOverrides de sus partes: item, itemIcon, itemHeader, actions.\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';\nimport {piecePart, usePieceProps} from '@/theme/themePieces.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaSessionsCard)\nconst THEME_NAME = 'CalcitaSessionsCard';\nconst SessionsItem = piecePart(THEME_NAME, 'Item', ListItem);\nconst SessionsItemIcon = piecePart(THEME_NAME, 'ItemIcon', ListItemIcon);\nconst SessionsItemHeader = piecePart(THEME_NAME, 'ItemHeader', Stack);\nconst SessionsActions = piecePart(THEME_NAME, 'Actions', Stack);\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 <SessionsItem disableGutters alignItems=\"flex-start\" ownSx={{gap: 1}}>\n <SessionsItemIcon ownSx={{minWidth: 36, mt: 0.5}}><DeviceIcon /></SessionsItemIcon>\n <ListItemText primary={(\n <SessionsItemHeader direction=\"row\" spacing={1} useFlexGap ownSx={{alignItems: 'center', flexWrap: 'wrap'}}>\n <span>{device}</span>\n {session.isCurrent && <Tag label={t('sessions.current')} color=\"success\" />}\n </SessionsItemHeader>\n )} secondary={details.join(' · ')} slotProps={{primary: {sx: {fontWeight: 600}}}} />\n </SessionsItem>\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(props) {\n // lo que el proyecto puso en su tema (components.CalcitaSessionsCard.defaultProps) y encima lo que llega aquí\n const {sessions, error = null, onCloseOthers, onClosed, texts} = usePieceProps(props, THEME_NAME);\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 <SessionsActions spacing={1} ownSx={{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 </SessionsActions>\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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"}
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// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaSessionsCard con defaultProps y\n// styleOverrides de sus partes: item, itemIcon, itemHeader, actions.\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';\nimport {piecePart, usePieceProps} from '@/theme/themePieces.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaSessionsCard)\nconst THEME_NAME = 'CalcitaSessionsCard';\nconst SessionsItem = piecePart(THEME_NAME, 'Item', ListItem);\nconst SessionsItemIcon = piecePart(THEME_NAME, 'ItemIcon', ListItemIcon);\nconst SessionsItemHeader = piecePart(THEME_NAME, 'ItemHeader', Stack);\nconst SessionsActions = piecePart(THEME_NAME, 'Actions', Stack);\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 <SessionsItem disableGutters alignItems=\"flex-start\" ownSx={{gap: 1}}>\n <SessionsItemIcon ownSx={{minWidth: 36, mt: 0.5}}><DeviceIcon /></SessionsItemIcon>\n <ListItemText primary={(\n <SessionsItemHeader direction=\"row\" spacing={1} useFlexGap ownSx={{alignItems: 'center', flexWrap: 'wrap'}}>\n <span>{device}</span>\n {session.isCurrent && <Tag label={t('sessions.current')} color=\"success\" />}\n </SessionsItemHeader>\n )} secondary={details.join(' · ')} slotProps={{primary: {sx: {fontWeight: 600}}}} />\n </SessionsItem>\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(props) {\n // lo que el proyecto puso en su tema (components.CalcitaSessionsCard.defaultProps) y encima lo que llega aquí\n const {sessions, error = null, onCloseOthers, onClosed, texts, ...unknownProps} = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\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 <SessionsActions spacing={1} ownSx={{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 </SessionsActions>\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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"}
@@ -9,6 +9,7 @@ import { toolkitComponents } from "./toolkitComponents.js";
9
9
  import { minimalComponents, minimalTonesOf } from "./components/minimalComponents.js";
10
10
  import { cssTokenDeclarationsOf } from "./cssTokens.js";
11
11
  import { createLogger } from "../observability/logger.js";
12
+ import { warnUnknownProps } from "../toolkit/unknownProps.js";
12
13
  const log = createLogger("theme");
13
14
  let hasWarnedAboutGlassSurfaces = false;
14
15
  const SYSTEM_FONT_FAMILY = 'system-ui, -apple-system, "Segoe UI", Roboto, sans-serif';
@@ -47,8 +48,10 @@ function createToolkitTheme({
47
48
  fields = {},
48
49
  cssTokens = {},
49
50
  components = {},
50
- overrides = {}
51
+ overrides = {},
52
+ ...unknownOptions
51
53
  } = {}) {
54
+ warnUnknownProps("createToolkitTheme", unknownOptions);
52
55
  if (!Object.values(TOP_BAR).includes(bar)) {
53
56
  throw new RangeError(`createToolkitTheme: bar es '${TOP_BAR.capsules}' o '${TOP_BAR.band}' (llegó ${bar}).`);
54
57
  }
@@ -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// 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 {minimalComponents, minimalTonesOf} from '@/theme/components/minimalComponents.js';\nimport {FIELD_TONES} from '@/theme/glassSurface.js';\nimport {cssTokenDeclarationsOf} from '@/theme/cssTokens.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/** Las variables CSS del proyecto declaradas en :root (con el CssBaseline de MUI); sin valores, nada. */\nfunction cssTokenComponentsOf(cssTokens) {\n const declarations = cssTokenDeclarationsOf(cssTokens);\n return Object.keys(declarations).length > 0 ? {MuiCssBaseline: {styleOverrides: {':root': declarations}}} : {};\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 {{light?: {default?: string, paper?: string, fill?: string, floating?: string}, dark?: {default?: string, paper?: string, fill?: string, floating?: string}}} [options.minimalTones]\n * los tonos del minimalista con los colores de la marca, por modo: default (el fondo de la página), paper (las tarjetas,\n * un escalón arriba), fill (el relleno de campos, grupos y botones sin color) y floating (la barra de pestañas del\n * teléfono y lo que flota). Lo que no se pase se queda con los grises de Apple (MINIMAL_TONES). OJO: con el minimalista\n * el fondo de la página sale de aquí y no de la paleta (sin minimalTones, los grises de Apple aunque la paleta traiga\n * su background)\n * @param {{radius?: number, light?: {fill?: string, hoverFill?: string, border?: string, hoverBorder?: string}, dark?: {fill?: string, hoverFill?: string, border?: string, hoverBorder?: string}}} [options.fields]\n * cómo se ven los campos de captura (con vidrio, con el minimalista y dentro de FormSection), por modo: fill (el\n * relleno), hoverFill (con el cursor encima), border y hoverBorder (la orilla); y radius (las esquinas, en px). Lo que no\n * se pase se queda como el estilo lo trae (FIELD_TONES: el gris de Apple; con el minimalista, su relleno). Van en la\n * paleta de cada modo (palette.field), así que también se pueden escribir en colorSchemes y cambian solos con el modo.\n * Ejemplo, campos blancos con borde en claro: {light: {fill: '#FFFFFF', hoverFill: '#FFFFFF', border: '#C4CDD5'}}\n * @param {{panelRadius?: number|string, menuRadius?: number|string, fieldRadius?: number|string, glassBlur?: number|string, glassSaturate?: number|string}} [options.cssTokens]\n * los valores globales de Calcita como variables CSS (--calcita-panel-radius…, ver cssTokens.js): las esquinas de los\n * paneles, los menús y los campos, y el desenfoque del vidrio. Se declaran en :root con el CssBaseline; un número va en\n * px (glassSaturate en %). Sin la opción, cada estilo usa los suyos. También se pueden poner en CSS suelto\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 minimalTones = {},\n fields = {},\n cssTokens = {},\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 de la página y la tarjeta un escalón arriba (los grises de\n // Apple o los tonos de la marca que pasó el proyecto)\n const tones = isMinimal ? minimalTonesOf(minimalTones) : null;\n // los campos de cada modo: lo del estilo (el gris de Apple o, con el minimalista, su relleno), encima lo de `fields` y\n // encima lo que el proyecto escribió en su paleta (palette.field)\n const fieldTonesOf = (mode, scheme) => {\n const lookTones = tones?.[mode]\n ? {...FIELD_TONES[mode], fill: tones[mode].fill, hoverFill: tones[mode].fill}\n : FIELD_TONES[mode] ?? FIELD_TONES.light;\n return {...lookTones, ...fields[mode], ...scheme?.palette?.field};\n };\n const schemes = Object.fromEntries(Object.entries(colorSchemes).map(([mode, scheme]) => [mode, {\n ...scheme,\n palette: {\n ...scheme?.palette,\n ...(isMinimal && {background: tones[mode] ? {default: tones[mode].default, paper: tones[mode].paper} : scheme?.palette?.background}),\n field: fieldTonesOf(mode, scheme),\n },\n }]));\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 // las variables CSS del proyecto en :root (con el CssBaseline), y encima sus propios ajustes de componentes\n components: mergeComponents(mergeComponents(frameworkComponents, cssTokenComponentsOf(cssTokens)), components),\n // sin sombras en el minimalista: lo plano es la idea\n ...(isMinimal && {shadows: Array(SHADOW_LEVELS).fill('none')}),\n // minimalTones: los tonos con que se pintan los rellenos y lo que flota (minimalComponents los lee del tema)\n // fields: las esquinas de los campos (sus tonos van en la paleta: palette.field)\n toolkit: {glass: glassSettings, look, bar, minimalTones: tones, fields: {radius: fields.radius}},\n ...overrides,\n });\n return responsiveFontSizes(baseTheme);\n}\n"],"names":[],"mappings":"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"}
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 {minimalComponents, minimalTonesOf} from '@/theme/components/minimalComponents.js';\nimport {FIELD_TONES} from '@/theme/glassSurface.js';\nimport {cssTokenDeclarationsOf} from '@/theme/cssTokens.js';\nimport {createLogger} from '@/observability/logger.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.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/** Las variables CSS del proyecto declaradas en :root (con el CssBaseline de MUI); sin valores, nada. */\nfunction cssTokenComponentsOf(cssTokens) {\n const declarations = cssTokenDeclarationsOf(cssTokens);\n return Object.keys(declarations).length > 0 ? {MuiCssBaseline: {styleOverrides: {':root': declarations}}} : {};\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 {{light?: {default?: string, paper?: string, fill?: string, floating?: string}, dark?: {default?: string, paper?: string, fill?: string, floating?: string}}} [options.minimalTones]\n * los tonos del minimalista con los colores de la marca, por modo: default (el fondo de la página), paper (las tarjetas,\n * un escalón arriba), fill (el relleno de campos, grupos y botones sin color) y floating (la barra de pestañas del\n * teléfono y lo que flota). Lo que no se pase se queda con los grises de Apple (MINIMAL_TONES). OJO: con el minimalista\n * el fondo de la página sale de aquí y no de la paleta (sin minimalTones, los grises de Apple aunque la paleta traiga\n * su background)\n * @param {{radius?: number, light?: {fill?: string, hoverFill?: string, border?: string, hoverBorder?: string}, dark?: {fill?: string, hoverFill?: string, border?: string, hoverBorder?: string}}} [options.fields]\n * cómo se ven los campos de captura (con vidrio, con el minimalista y dentro de FormSection), por modo: fill (el\n * relleno), hoverFill (con el cursor encima), border y hoverBorder (la orilla); y radius (las esquinas, en px). Lo que no\n * se pase se queda como el estilo lo trae (FIELD_TONES: el gris de Apple; con el minimalista, su relleno). Van en la\n * paleta de cada modo (palette.field), así que también se pueden escribir en colorSchemes y cambian solos con el modo.\n * Ejemplo, campos blancos con borde en claro: {light: {fill: '#FFFFFF', hoverFill: '#FFFFFF', border: '#C4CDD5'}}\n * @param {{panelRadius?: number|string, menuRadius?: number|string, fieldRadius?: number|string, glassBlur?: number|string, glassSaturate?: number|string}} [options.cssTokens]\n * los valores globales de Calcita como variables CSS (--calcita-panel-radius…, ver cssTokens.js): las esquinas de los\n * paneles, los menús y los campos, y el desenfoque del vidrio. Se declaran en :root con el CssBaseline; un número va en\n * px (glassSaturate en %). Sin la opción, cada estilo usa los suyos. También se pueden poner en CSS suelto\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 minimalTones = {},\n fields = {},\n cssTokens = {},\n components = {},\n overrides = {},\n ...unknownOptions\n} = {}) {\n // una opción con otro nombre (glassBar en lugar de bar) se perdía sin decir nada: se avisa mientras se programa\n warnUnknownProps('createToolkitTheme', unknownOptions);\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 de la página y la tarjeta un escalón arriba (los grises de\n // Apple o los tonos de la marca que pasó el proyecto)\n const tones = isMinimal ? minimalTonesOf(minimalTones) : null;\n // los campos de cada modo: lo del estilo (el gris de Apple o, con el minimalista, su relleno), encima lo de `fields` y\n // encima lo que el proyecto escribió en su paleta (palette.field)\n const fieldTonesOf = (mode, scheme) => {\n const lookTones = tones?.[mode]\n ? {...FIELD_TONES[mode], fill: tones[mode].fill, hoverFill: tones[mode].fill}\n : FIELD_TONES[mode] ?? FIELD_TONES.light;\n return {...lookTones, ...fields[mode], ...scheme?.palette?.field};\n };\n const schemes = Object.fromEntries(Object.entries(colorSchemes).map(([mode, scheme]) => [mode, {\n ...scheme,\n palette: {\n ...scheme?.palette,\n ...(isMinimal && {background: tones[mode] ? {default: tones[mode].default, paper: tones[mode].paper} : scheme?.palette?.background}),\n field: fieldTonesOf(mode, scheme),\n },\n }]));\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 // las variables CSS del proyecto en :root (con el CssBaseline), y encima sus propios ajustes de componentes\n components: mergeComponents(mergeComponents(frameworkComponents, cssTokenComponentsOf(cssTokens)), components),\n // sin sombras en el minimalista: lo plano es la idea\n ...(isMinimal && {shadows: Array(SHADOW_LEVELS).fill('none')}),\n // minimalTones: los tonos con que se pintan los rellenos y lo que flota (minimalComponents los lee del tema)\n // fields: las esquinas de los campos (sus tonos van en la paleta: palette.field)\n toolkit: {glass: glassSettings, look, bar, minimalTones: tones, fields: {radius: fields.radius}},\n ...overrides,\n });\n return responsiveFontSizes(baseTheme);\n}\n"],"names":[],"mappings":"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"}
@@ -2,7 +2,9 @@
2
2
  import { jsx } from "react/jsx-runtime";
3
3
  import { useContext, useMemo } from "react";
4
4
  import { ComponentTextsContext } from "./componentTextsContext.js";
5
- function ComponentTexts({ namespace, texts, children }) {
5
+ import { warnUnknownProps } from "./unknownProps.js";
6
+ function ComponentTexts({ namespace, texts, children, ...unknownProps }) {
7
+ warnUnknownProps("ComponentTexts", unknownProps);
6
8
  const parent = useContext(ComponentTextsContext);
7
9
  const layer = useMemo(() => ({ namespace, texts, parent }), [namespace, texts, parent]);
8
10
  return /* @__PURE__ */ jsx(ComponentTextsContext, { value: layer, children });
@@ -1 +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;"}
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';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.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, ...unknownProps}) {\n warnUnknownProps('ComponentTexts', unknownProps);\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":";;;;;AAuBA,SAAwB,eAAe,EAAC,WAAW,OAAO,UAAU,GAAG,gBAAe;AAClF,mBAAiB,kBAAkB,YAAY;AAC/C,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;"}
@@ -5,7 +5,8 @@ import { allowOnlyUnrestricted } from "../access/accessRules.js";
5
5
  import { browserNavigation } from "./browserNavigation.js";
6
6
  import { ToolkitContext } from "./toolkitContext.js";
7
7
  import { useFontSize } from "../ui/navigation/useFontSize.js";
8
- import { DEFAULT_CURRENCY, DEFAULT_LANGUAGES, DEFAULT_LANGUAGE, browserTimeZone, configureToolkit } from "./toolkitSettings.js";
8
+ import { DEFAULT_HOUR_CYCLE, DEFAULT_CURRENCY, DEFAULT_LANGUAGES, DEFAULT_LANGUAGE, browserTimeZone, configureToolkit } from "./toolkitSettings.js";
9
+ import { warnUnknownProps } from "./unknownProps.js";
9
10
  const ToolkitProvider = ({
10
11
  language,
11
12
  languages,
@@ -14,13 +15,16 @@ const ToolkitProvider = ({
14
15
  translate,
15
16
  timeZone,
16
17
  currency,
18
+ hourCycle,
17
19
  appName,
18
20
  navigation,
19
21
  buildId,
20
22
  canAccess,
21
23
  fileTypes,
22
- children
24
+ children,
25
+ ...unknownProps
23
26
  }) => {
27
+ warnUnknownProps("ToolkitProvider", unknownProps);
24
28
  const parent = useContext(ToolkitContext);
25
29
  const settings = useMemo(() => ({
26
30
  language: language ?? parent?.language ?? DEFAULT_LANGUAGE,
@@ -30,12 +34,28 @@ const ToolkitProvider = ({
30
34
  translate: translate ?? parent?.translate ?? null,
31
35
  timeZone: timeZone ?? parent?.timeZone ?? browserTimeZone(),
32
36
  currency: currency ?? parent?.currency ?? DEFAULT_CURRENCY,
37
+ hourCycle: hourCycle ?? parent?.hourCycle ?? DEFAULT_HOUR_CYCLE,
33
38
  appName: appName ?? parent?.appName ?? "",
34
39
  navigation: navigation ?? parent?.navigation ?? browserNavigation,
35
40
  buildId: buildId ?? parent?.buildId ?? "",
36
41
  canAccess: canAccess ?? parent?.canAccess ?? allowOnlyUnrestricted,
37
42
  fileTypes: fileTypes ?? parent?.fileTypes ?? null
38
- }), [parent, language, languages, onLanguageChange, texts, translate, timeZone, currency, appName, navigation, buildId, canAccess, fileTypes]);
43
+ }), [
44
+ parent,
45
+ language,
46
+ languages,
47
+ onLanguageChange,
48
+ texts,
49
+ translate,
50
+ timeZone,
51
+ currency,
52
+ hourCycle,
53
+ appName,
54
+ navigation,
55
+ buildId,
56
+ canAccess,
57
+ fileTypes
58
+ ]);
39
59
  if (!parent) {
40
60
  configureToolkit({
41
61
  language: settings.language,
@@ -43,6 +63,7 @@ const ToolkitProvider = ({
43
63
  translate: settings.translate,
44
64
  timeZone: settings.timeZone,
45
65
  currency: settings.currency,
66
+ hourCycle: settings.hourCycle,
46
67
  appName: settings.appName,
47
68
  buildId: settings.buildId,
48
69
  languages: settings.languages
@@ -1 +1 @@
1
- {"version":3,"file":"ToolkitProvider.js","sources":["../../src/toolkit/ToolkitProvider.jsx"],"sourcesContent":["// El proveedor del paquete: el único lugar donde cada aplicación conecta su idioma, sus textos, su zona horaria y su\n// router. Va por encima de la aplicación, dentro del ThemeProvider de MUI (el tema lo arma cada proyecto; sin tema,\n// los componentes usan los colores por omisión de MUI).\n//\n// <ToolkitProvider language=\"es\" navigation={reactRouterNavigation} texts={{es: {common: {actions: {back: 'Volver'}}}}}>\n//\n// Sin proveedor todo funciona en español, con la zona del navegador y la navegación del navegador.\n//\n// Un proveedor dentro de otro (una parte de la app en otro idioma, una vista previa) toma del de afuera lo que no le digan, y\n// sus componentes leen el suyo: el más cercano. Solo el de la raíz pone la configuración que leen las funciones sueltas\n// (avisos, errores, formatos sueltos): antes el de adentro se la cambiaba a toda la app. Dos islas de React en una página\n// vieja son dos raíces: cada isla pinta con la suya, y las funciones sueltas usan la última que se pintó (en ese caso los\n// componentes de la casa ya formatean con su proveedor; para un aviso, mándelo con el texto ya traducido).\n//\n// Lo renderiza la app: arrow (regla de la casa).\nimport {useContext, useMemo} from 'react';\nimport {allowOnlyUnrestricted} from '@/access/accessRules.js';\nimport {browserNavigation} from '@/toolkit/browserNavigation.js';\nimport {ToolkitContext} from '@/toolkit/toolkitContext.js';\nimport {useFontSize} from '@/ui/navigation/useFontSize.js';\nimport {DEFAULT_CURRENCY, DEFAULT_LANGUAGE, DEFAULT_LANGUAGES, browserTimeZone, configureToolkit} from '@/toolkit/toolkitSettings.js';\n\n/**\n * @param {object} props\n * @param {string} [props.language] idioma activo, uno de `languages` (por omisión es)\n * @param {Array<{code: string, label: string, locale?: string}>} [props.languages] los idiomas que ofrece la app: su código,\n * su nombre en su propio idioma («Português») y su locale de Intl ('pt-BR'). Por omisión español e inglés\n * (DEFAULT_LANGUAGES). Sus traducciones van en `texts`\n * @param {Record<string, {Icon: import('react').ElementType, color?: string, label?: string, hint?: string}>} [props.fileTypes]\n * tipos de archivo del proyecto para las descargas, además de Excel, CSV y PDF (downloads/fileTypes.js)\n * @param {(language: string) => void} [props.onLanguageChange] lo llama LanguageToggle; el proyecto cambia su idioma\n * @param {object} [props.texts] textos propios por idioma, con la forma de locales/ ({es: {table: {...}}})\n * @param {(namespace: string, key: string, values?: object) => string|undefined} [props.translate] traductor del\n * proyecto (por ejemplo i18next); undefined deja el texto del paquete\n * @param {string} [props.timeZone] zona de las fechas (por omisión la del navegador)\n * @param {string} [props.currency] moneda de los montos, código ISO 4217 (por omisión MXN)\n * @param {string} [props.appName] nombre de la aplicación para el título de la pestaña\n * @param {object} [props.navigation] {useNavigate, useLocation, Link}; por omisión la del navegador\n * @param {string} [props.buildId] la compilación del front que corre en esta pestaña; con ella NewBuildNotice sabe si el\n * servidor ya publicó otra (reportPublishedBuild). Vacía (en desarrollo) nunca avisa\n * @param {(access: object|undefined|null) => boolean} [props.canAccess] quién ve qué (menú, rutas, botones): la regla del\n * proyecto, normalmente createAccessChecker({abilities, roles, policies, context}). Sin ella solo se ve lo que no\n * tiene regla\n * @param {import('react').ReactNode} props.children\n */\nexport const ToolkitProvider = ({\n language, languages, onLanguageChange, texts, translate, timeZone, currency, appName, navigation, buildId, canAccess, fileTypes, children,\n}) => {\n // el proveedor de afuera, si este va dentro de otro: de él sale lo que este no diga\n const parent = useContext(ToolkitContext);\n const settings = useMemo(() => ({\n language: language ?? parent?.language ?? DEFAULT_LANGUAGE,\n languages: languages ?? parent?.languages ?? DEFAULT_LANGUAGES,\n onLanguageChange: onLanguageChange ?? parent?.onLanguageChange ?? null,\n texts: texts ?? parent?.texts ?? {},\n translate: translate ?? parent?.translate ?? null,\n timeZone: timeZone ?? parent?.timeZone ?? browserTimeZone(),\n currency: currency ?? parent?.currency ?? DEFAULT_CURRENCY,\n appName: appName ?? parent?.appName ?? '',\n navigation: navigation ?? parent?.navigation ?? browserNavigation,\n buildId: buildId ?? parent?.buildId ?? '',\n canAccess: canAccess ?? parent?.canAccess ?? allowOnlyUnrestricted,\n fileTypes: fileTypes ?? parent?.fileTypes ?? null,\n }), [parent, language, languages, onLanguageChange, texts, translate, timeZone, currency, appName, navigation, buildId, canAccess, fileTypes]);\n\n // solo el de la raíz: un proveedor de adentro le cambiaba el idioma y la zona a las funciones sueltas de toda la app.\n // OJO: se configura durante el render (no en un efecto) para que los formatos y avisos que se llaman en este mismo\n // render ya vean el idioma y la zona nuevos\n if (!parent) {\n configureToolkit({\n language: settings.language, texts: settings.texts, translate: settings.translate, timeZone: settings.timeZone, currency: settings.currency,\n appName: settings.appName, buildId: settings.buildId, languages: settings.languages,\n });\n }\n\n // el tamaño de letra que la persona guardó: se aplica aquí, que siempre está montado. Antes lo aplicaba su botón en la\n // barra; al pasar los ajustes al menú de la cuenta, al recargar se perdía hasta abrir el menú (error pagado)\n useFontSize();\n\n return <ToolkitContext.Provider value={settings}>{children}</ToolkitContext.Provider>;\n};\n"],"names":[],"mappings":";;;;;;;;AA6CO,MAAM,kBAAkB,CAAC;AAAA,EAC5B;AAAA,EAAU;AAAA,EAAW;AAAA,EAAkB;AAAA,EAAO;AAAA,EAAW;AAAA,EAAU;AAAA,EAAU;AAAA,EAAS;AAAA,EAAY;AAAA,EAAS;AAAA,EAAW;AAAA,EAAW;AACrI,MAAM;AAEF,QAAM,SAAS,WAAW,cAAc;AACxC,QAAM,WAAW,QAAQ,OAAO;AAAA,IAC5B,UAAU,YAAY,QAAQ,YAAY;AAAA,IAC1C,WAAW,aAAa,QAAQ,aAAa;AAAA,IAC7C,kBAAkB,oBAAoB,QAAQ,oBAAoB;AAAA,IAClE,OAAO,SAAS,QAAQ,SAAS,CAAA;AAAA,IACjC,WAAW,aAAa,QAAQ,aAAa;AAAA,IAC7C,UAAU,YAAY,QAAQ,YAAY,gBAAA;AAAA,IAC1C,UAAU,YAAY,QAAQ,YAAY;AAAA,IAC1C,SAAS,WAAW,QAAQ,WAAW;AAAA,IACvC,YAAY,cAAc,QAAQ,cAAc;AAAA,IAChD,SAAS,WAAW,QAAQ,WAAW;AAAA,IACvC,WAAW,aAAa,QAAQ,aAAa;AAAA,IAC7C,WAAW,aAAa,QAAQ,aAAa;AAAA,EAAA,IAC7C,CAAC,QAAQ,UAAU,WAAW,kBAAkB,OAAO,WAAW,UAAU,UAAU,SAAS,YAAY,SAAS,WAAW,SAAS,CAAC;AAK7I,MAAI,CAAC,QAAQ;AACT,qBAAiB;AAAA,MACb,UAAU,SAAS;AAAA,MAAU,OAAO,SAAS;AAAA,MAAO,WAAW,SAAS;AAAA,MAAW,UAAU,SAAS;AAAA,MAAU,UAAU,SAAS;AAAA,MACnI,SAAS,SAAS;AAAA,MAAS,SAAS,SAAS;AAAA,MAAS,WAAW,SAAS;AAAA,IAAA,CAC7E;AAAA,EACL;AAIA,cAAA;AAEA,6BAAQ,eAAe,UAAf,EAAwB,OAAO,UAAW,UAAS;AAC/D;"}
1
+ {"version":3,"file":"ToolkitProvider.js","sources":["../../src/toolkit/ToolkitProvider.jsx"],"sourcesContent":["// El proveedor del paquete: el único lugar donde cada aplicación conecta su idioma, sus textos, su zona horaria y su\n// router. Va por encima de la aplicación, dentro del ThemeProvider de MUI (el tema lo arma cada proyecto; sin tema,\n// los componentes usan los colores por omisión de MUI).\n//\n// <ToolkitProvider language=\"es\" navigation={reactRouterNavigation} texts={{es: {common: {actions: {back: 'Volver'}}}}}>\n//\n// Sin proveedor todo funciona en español, con la zona del navegador y la navegación del navegador.\n//\n// Un proveedor dentro de otro (una parte de la app en otro idioma, una vista previa) toma del de afuera lo que no le digan, y\n// sus componentes leen el suyo: el más cercano. Solo el de la raíz pone la configuración que leen las funciones sueltas\n// (avisos, errores, formatos sueltos): antes el de adentro se la cambiaba a toda la app. Dos islas de React en una página\n// vieja son dos raíces: cada isla pinta con la suya, y las funciones sueltas usan la última que se pintó (en ese caso los\n// componentes de la casa ya formatean con su proveedor; para un aviso, mándelo con el texto ya traducido).\n//\n// Lo renderiza la app: arrow (regla de la casa).\nimport {useContext, useMemo} from 'react';\nimport {allowOnlyUnrestricted} from '@/access/accessRules.js';\nimport {browserNavigation} from '@/toolkit/browserNavigation.js';\nimport {ToolkitContext} from '@/toolkit/toolkitContext.js';\nimport {useFontSize} from '@/ui/navigation/useFontSize.js';\nimport {DEFAULT_CURRENCY, DEFAULT_HOUR_CYCLE, DEFAULT_LANGUAGE, DEFAULT_LANGUAGES, browserTimeZone, configureToolkit} from '@/toolkit/toolkitSettings.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\n/**\n * @param {object} props\n * @param {string} [props.language] idioma activo, uno de `languages` (por omisión es)\n * @param {Array<{code: string, label: string, locale?: string}>} [props.languages] los idiomas que ofrece la app: su código,\n * su nombre en su propio idioma («Português») y su locale de Intl ('pt-BR'). Por omisión español e inglés\n * (DEFAULT_LANGUAGES). Sus traducciones van en `texts`\n * @param {Record<string, {Icon: import('react').ElementType, color?: string, label?: string, hint?: string}>} [props.fileTypes]\n * tipos de archivo del proyecto para las descargas, además de Excel, CSV y PDF (downloads/fileTypes.js)\n * @param {(language: string) => void} [props.onLanguageChange] lo llama LanguageToggle; el proyecto cambia su idioma\n * @param {object} [props.texts] textos propios por idioma, con la forma de locales/ ({es: {table: {...}}})\n * @param {(namespace: string, key: string, values?: object) => string|undefined} [props.translate] traductor del\n * proyecto (por ejemplo i18next); undefined deja el texto del paquete\n * @param {string} [props.timeZone] zona de las fechas (por omisión la del navegador)\n * @param {string} [props.currency] moneda de los montos, código ISO 4217 (por omisión MXN)\n * @param {'h23'|'h12'} [props.hourCycle] la hora de 24 horas («15:10», por omisión) o de 12 con a.m. y p.m. («3:10 p.m.»):\n * HOUR_CYCLE.twentyFourHours o HOUR_CYCLE.twelveHours\n * @param {string} [props.appName] nombre de la aplicación para el título de la pestaña\n * @param {object} [props.navigation] {useNavigate, useLocation, Link}; por omisión la del navegador\n * @param {string} [props.buildId] la compilación del front que corre en esta pestaña; con ella NewBuildNotice sabe si el\n * servidor ya publicó otra (reportPublishedBuild). Vacía (en desarrollo) nunca avisa\n * @param {(access: object|undefined|null) => boolean} [props.canAccess] quién ve qué (menú, rutas, botones): la regla del\n * proyecto, normalmente createAccessChecker({abilities, roles, policies, context}). Sin ella solo se ve lo que no\n * tiene regla\n * @param {import('react').ReactNode} props.children\n */\nexport const ToolkitProvider = ({\n language, languages, onLanguageChange, texts, translate, timeZone, currency, hourCycle, appName, navigation, buildId, canAccess, fileTypes,\n children,\n ...unknownProps\n}) => {\n warnUnknownProps('ToolkitProvider', unknownProps);\n // el proveedor de afuera, si este va dentro de otro: de él sale lo que este no diga\n const parent = useContext(ToolkitContext);\n const settings = useMemo(() => ({\n language: language ?? parent?.language ?? DEFAULT_LANGUAGE,\n languages: languages ?? parent?.languages ?? DEFAULT_LANGUAGES,\n onLanguageChange: onLanguageChange ?? parent?.onLanguageChange ?? null,\n texts: texts ?? parent?.texts ?? {},\n translate: translate ?? parent?.translate ?? null,\n timeZone: timeZone ?? parent?.timeZone ?? browserTimeZone(),\n currency: currency ?? parent?.currency ?? DEFAULT_CURRENCY,\n hourCycle: hourCycle ?? parent?.hourCycle ?? DEFAULT_HOUR_CYCLE,\n appName: appName ?? parent?.appName ?? '',\n navigation: navigation ?? parent?.navigation ?? browserNavigation,\n buildId: buildId ?? parent?.buildId ?? '',\n canAccess: canAccess ?? parent?.canAccess ?? allowOnlyUnrestricted,\n fileTypes: fileTypes ?? parent?.fileTypes ?? null,\n }), [parent, language, languages, onLanguageChange, texts, translate, timeZone, currency, hourCycle, appName, navigation, buildId, canAccess,\n fileTypes]);\n\n // solo el de la raíz: un proveedor de adentro le cambiaba el idioma y la zona a las funciones sueltas de toda la app.\n // OJO: se configura durante el render (no en un efecto) para que los formatos y avisos que se llaman en este mismo\n // render ya vean el idioma y la zona nuevos\n if (!parent) {\n configureToolkit({\n language: settings.language, texts: settings.texts, translate: settings.translate, timeZone: settings.timeZone, currency: settings.currency,\n hourCycle: settings.hourCycle, appName: settings.appName, buildId: settings.buildId, languages: settings.languages,\n });\n }\n\n // el tamaño de letra que la persona guardó: se aplica aquí, que siempre está montado. Antes lo aplicaba su botón en la\n // barra; al pasar los ajustes al menú de la cuenta, al recargar se perdía hasta abrir el menú (error pagado)\n useFontSize();\n\n return <ToolkitContext.Provider value={settings}>{children}</ToolkitContext.Provider>;\n};\n"],"names":[],"mappings":";;;;;;;;;AAgDO,MAAM,kBAAkB,CAAC;AAAA,EAC5B;AAAA,EAAU;AAAA,EAAW;AAAA,EAAkB;AAAA,EAAO;AAAA,EAAW;AAAA,EAAU;AAAA,EAAU;AAAA,EAAW;AAAA,EAAS;AAAA,EAAY;AAAA,EAAS;AAAA,EAAW;AAAA,EACjI;AAAA,EACA,GAAG;AACP,MAAM;AACF,mBAAiB,mBAAmB,YAAY;AAEhD,QAAM,SAAS,WAAW,cAAc;AACxC,QAAM,WAAW,QAAQ,OAAO;AAAA,IAC5B,UAAU,YAAY,QAAQ,YAAY;AAAA,IAC1C,WAAW,aAAa,QAAQ,aAAa;AAAA,IAC7C,kBAAkB,oBAAoB,QAAQ,oBAAoB;AAAA,IAClE,OAAO,SAAS,QAAQ,SAAS,CAAA;AAAA,IACjC,WAAW,aAAa,QAAQ,aAAa;AAAA,IAC7C,UAAU,YAAY,QAAQ,YAAY,gBAAA;AAAA,IAC1C,UAAU,YAAY,QAAQ,YAAY;AAAA,IAC1C,WAAW,aAAa,QAAQ,aAAa;AAAA,IAC7C,SAAS,WAAW,QAAQ,WAAW;AAAA,IACvC,YAAY,cAAc,QAAQ,cAAc;AAAA,IAChD,SAAS,WAAW,QAAQ,WAAW;AAAA,IACvC,WAAW,aAAa,QAAQ,aAAa;AAAA,IAC7C,WAAW,aAAa,QAAQ,aAAa;AAAA,EAAA,IAC7C;AAAA,IAAC;AAAA,IAAQ;AAAA,IAAU;AAAA,IAAW;AAAA,IAAkB;AAAA,IAAO;AAAA,IAAW;AAAA,IAAU;AAAA,IAAU;AAAA,IAAW;AAAA,IAAS;AAAA,IAAY;AAAA,IAAS;AAAA,IAC/H;AAAA,EAAA,CAAU;AAKd,MAAI,CAAC,QAAQ;AACT,qBAAiB;AAAA,MACb,UAAU,SAAS;AAAA,MAAU,OAAO,SAAS;AAAA,MAAO,WAAW,SAAS;AAAA,MAAW,UAAU,SAAS;AAAA,MAAU,UAAU,SAAS;AAAA,MACnI,WAAW,SAAS;AAAA,MAAW,SAAS,SAAS;AAAA,MAAS,SAAS,SAAS;AAAA,MAAS,WAAW,SAAS;AAAA,IAAA,CAC5G;AAAA,EACL;AAIA,cAAA;AAEA,6BAAQ,eAAe,UAAf,EAAwB,OAAO,UAAW,UAAS;AAC/D;"}