@stackcraftmx/calcita 0.28.0 → 0.30.1

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 (298) hide show
  1. package/README.md +149 -4
  2. package/dist/DataTable/AuditTrailDialog.js +18 -14
  3. package/dist/DataTable/AuditTrailDialog.js.map +1 -1
  4. package/dist/DataTable/ColumnFilters/HeaderFilterPopover.js +12 -6
  5. package/dist/DataTable/ColumnFilters/HeaderFilterPopover.js.map +1 -1
  6. package/dist/DataTable/ColumnFilters/MultiSelectColumnFilter.js +8 -3
  7. package/dist/DataTable/ColumnFilters/MultiSelectColumnFilter.js.map +1 -1
  8. package/dist/DataTable/ColumnFilters/SortableFilterHeader.js +26 -18
  9. package/dist/DataTable/ColumnFilters/SortableFilterHeader.js.map +1 -1
  10. package/dist/DataTable/EmptyState.js +8 -3
  11. package/dist/DataTable/EmptyState.js.map +1 -1
  12. package/dist/DataTable/FilterChips.js +11 -5
  13. package/dist/DataTable/FilterChips.js.map +1 -1
  14. package/dist/DataTable/GoToPage.js +9 -4
  15. package/dist/DataTable/GoToPage.js.map +1 -1
  16. package/dist/DataTable/ServerTable.js +74 -61
  17. package/dist/DataTable/ServerTable.js.map +1 -1
  18. package/dist/DataTable/columns.js +1 -8
  19. package/dist/DataTable/columns.js.map +1 -1
  20. package/dist/access/PermissionMatrix.js +18 -9
  21. package/dist/access/PermissionMatrix.js.map +1 -1
  22. package/dist/account/AccountSecuritySection.js +8 -3
  23. package/dist/account/AccountSecuritySection.js.map +1 -1
  24. package/dist/account/DemoTotpCodeButton.js +8 -3
  25. package/dist/account/DemoTotpCodeButton.js.map +1 -1
  26. package/dist/account/ProfileScreen.js +19 -13
  27. package/dist/account/ProfileScreen.js.map +1 -1
  28. package/dist/account/SignInScreen.js +6 -2
  29. package/dist/account/SignInScreen.js.map +1 -1
  30. package/dist/appShell/AccountMenu.js +13 -5
  31. package/dist/appShell/AccountMenu.js.map +1 -1
  32. package/dist/appShell/AppLayout.js +100 -71
  33. package/dist/appShell/AppLayout.js.map +1 -1
  34. package/dist/appShell/AppearanceSettings.js +21 -13
  35. package/dist/appShell/AppearanceSettings.js.map +1 -1
  36. package/dist/appShell/AuthLayout.js +26 -13
  37. package/dist/appShell/AuthLayout.js.map +1 -1
  38. package/dist/appShell/BarActions.js +10 -5
  39. package/dist/appShell/BarActions.js.map +1 -1
  40. package/dist/appShell/BarCapsule.js +9 -4
  41. package/dist/appShell/BarCapsule.js.map +1 -1
  42. package/dist/appShell/BarSearch.js +7 -3
  43. package/dist/appShell/BarSearch.js.map +1 -1
  44. package/dist/appShell/HeaderTools.js +20 -15
  45. package/dist/appShell/HeaderTools.js.map +1 -1
  46. package/dist/appShell/MobileTabBar.js +25 -14
  47. package/dist/appShell/MobileTabBar.js.map +1 -1
  48. package/dist/appShell/SettingsMenu.js +7 -3
  49. package/dist/appShell/SettingsMenu.js.map +1 -1
  50. package/dist/appShell/SideMenu.js +53 -35
  51. package/dist/appShell/SideMenu.js.map +1 -1
  52. package/dist/appShell/SideMenuEntry.js +18 -9
  53. package/dist/appShell/SideMenuEntry.js.map +1 -1
  54. package/dist/appShell/SubmenuActionsMenu.js +6 -2
  55. package/dist/appShell/SubmenuActionsMenu.js.map +1 -1
  56. package/dist/appShell/TabBarEditor.js +16 -7
  57. package/dist/appShell/TabBarEditor.js.map +1 -1
  58. package/dist/appShell/TopMenu.js +12 -7
  59. package/dist/appShell/TopMenu.js.map +1 -1
  60. package/dist/appShell/Wordmark.js +23 -4
  61. package/dist/appShell/Wordmark.js.map +1 -1
  62. package/dist/auth/PasswordChecklist.js +14 -7
  63. package/dist/auth/PasswordChecklist.js.map +1 -1
  64. package/dist/calendar/EventCalendar.js +26 -19
  65. package/dist/calendar/EventCalendar.js.map +1 -1
  66. package/dist/calendar/EventDetails.js +28 -13
  67. package/dist/calendar/EventDetails.js.map +1 -1
  68. package/dist/downloads/DownloadManager.js +34 -18
  69. package/dist/downloads/DownloadManager.js.map +1 -1
  70. package/dist/feedback/ConfirmDialog.js +22 -17
  71. package/dist/feedback/ConfirmDialog.js.map +1 -1
  72. package/dist/feedback/ModalWaiting.js +12 -5
  73. package/dist/feedback/ModalWaiting.js.map +1 -1
  74. package/dist/feedback/NewBuildNotice.js +16 -8
  75. package/dist/feedback/NewBuildNotice.js.map +1 -1
  76. package/dist/filepond/FileDropzone.js +39 -31
  77. package/dist/filepond/FileDropzone.js.map +1 -1
  78. package/dist/forms/FormProblemAlert.js +4 -1
  79. package/dist/forms/FormProblemAlert.js.map +1 -1
  80. package/dist/forms/auth/SignInForm.js +6 -2
  81. package/dist/forms/auth/SignInForm.js.map +1 -1
  82. package/dist/forms/security/SecondStepForm.js +6 -2
  83. package/dist/forms/security/SecondStepForm.js.map +1 -1
  84. package/dist/forms/security/TwoStepVerificationCard.js +19 -9
  85. package/dist/forms/security/TwoStepVerificationCard.js.map +1 -1
  86. package/dist/hookForm/FormProblemAlert.js +4 -1
  87. package/dist/hookForm/FormProblemAlert.js.map +1 -1
  88. package/dist/inbox/InboxLayout.js +47 -29
  89. package/dist/inbox/InboxLayout.js.map +1 -1
  90. package/dist/inbox/MessageList.js +53 -32
  91. package/dist/inbox/MessageList.js.map +1 -1
  92. package/dist/inbox/MessageView.js +31 -15
  93. package/dist/inbox/MessageView.js.map +1 -1
  94. package/dist/index.js +4 -0
  95. package/dist/index.js.map +1 -1
  96. package/dist/inputs/ColorInput.js +22 -17
  97. package/dist/inputs/ColorInput.js.map +1 -1
  98. package/dist/inputs/FormSection.js +64 -64
  99. package/dist/inputs/FormSection.js.map +1 -1
  100. package/dist/inputs/SelectInput.js +21 -16
  101. package/dist/inputs/SelectInput.js.map +1 -1
  102. package/dist/inputs/TextInput.js +25 -20
  103. package/dist/inputs/TextInput.js.map +1 -1
  104. package/dist/inputs/WizardLayout.js +21 -15
  105. package/dist/inputs/WizardLayout.js.map +1 -1
  106. package/dist/kanban/KanbanBoard.js +82 -50
  107. package/dist/kanban/KanbanBoard.js.map +1 -1
  108. package/dist/maps/MapView.js +19 -14
  109. package/dist/maps/MapView.js.map +1 -1
  110. package/dist/pwa/InstallAppSetting.js +4 -1
  111. package/dist/pwa/InstallAppSetting.js.map +1 -1
  112. package/dist/pwa/InstallInstructionsDialog.js +6 -2
  113. package/dist/pwa/InstallInstructionsDialog.js.map +1 -1
  114. package/dist/pwa/InstallPromptBanner.js +10 -5
  115. package/dist/pwa/InstallPromptBanner.js.map +1 -1
  116. package/dist/security/ActivityLog.js +6 -2
  117. package/dist/security/ActivityLog.js.map +1 -1
  118. package/dist/security/RecoveryCodesDialog.js +8 -3
  119. package/dist/security/RecoveryCodesDialog.js.map +1 -1
  120. package/dist/security/SessionsCard.js +12 -5
  121. package/dist/security/SessionsCard.js.map +1 -1
  122. package/dist/theme/components/glassComponents.js +19 -16
  123. package/dist/theme/components/glassComponents.js.map +1 -1
  124. package/dist/theme/components/minimalComponents.js +6 -2
  125. package/dist/theme/components/minimalComponents.js.map +1 -1
  126. package/dist/theme/createToolkitTheme.js +8 -1
  127. package/dist/theme/createToolkitTheme.js.map +1 -1
  128. package/dist/theme/cssTokens.js +23 -0
  129. package/dist/theme/cssTokens.js.map +1 -0
  130. package/dist/theme/glassSurface.js +15 -4
  131. package/dist/theme/glassSurface.js.map +1 -1
  132. package/dist/theme/glassToasterStyles.js +2 -3
  133. package/dist/theme/glassToasterStyles.js.map +1 -1
  134. package/dist/theme/themePieces.js +16 -0
  135. package/dist/theme/themePieces.js.map +1 -0
  136. package/dist/types/DataTable/AuditTrailDialog.d.ts +1 -1
  137. package/dist/types/DataTable/ColumnFilters/HeaderFilterPopover.d.ts +1 -1
  138. package/dist/types/DataTable/ColumnFilters/MultiSelectColumnFilter.d.ts +1 -1
  139. package/dist/types/DataTable/ColumnFilters/SortableFilterHeader.d.ts +1 -1
  140. package/dist/types/DataTable/EmptyState.d.ts +1 -1
  141. package/dist/types/DataTable/FilterChips.d.ts +1 -1
  142. package/dist/types/DataTable/GoToPage.d.ts +1 -1
  143. package/dist/types/DataTable/ServerTable.d.ts +1 -1
  144. package/dist/types/access/PermissionMatrix.d.ts +1 -1
  145. package/dist/types/account/AccountSecuritySection.d.ts +1 -1
  146. package/dist/types/account/DemoTotpCodeButton.d.ts +1 -1
  147. package/dist/types/account/ProfileScreen.d.ts +1 -1
  148. package/dist/types/account/SignInScreen.d.ts +1 -1
  149. package/dist/types/appShell/AccountMenu.d.ts +1 -1
  150. package/dist/types/appShell/AppLayout.d.ts +8 -4
  151. package/dist/types/appShell/AppearanceSettings.d.ts +1 -1
  152. package/dist/types/appShell/AuthLayout.d.ts +1 -1
  153. package/dist/types/appShell/BarActions.d.ts +1 -1
  154. package/dist/types/appShell/BarCapsule.d.ts +1 -1
  155. package/dist/types/appShell/BarSearch.d.ts +1 -1
  156. package/dist/types/appShell/HeaderTools.d.ts +1 -1
  157. package/dist/types/appShell/MobileTabBar.d.ts +1 -1
  158. package/dist/types/appShell/SettingsMenu.d.ts +1 -1
  159. package/dist/types/appShell/SideMenu.d.ts +7 -3
  160. package/dist/types/appShell/SideMenuEntry.d.ts +1 -1
  161. package/dist/types/appShell/SubmenuActionsMenu.d.ts +1 -1
  162. package/dist/types/appShell/TabBarEditor.d.ts +1 -1
  163. package/dist/types/appShell/TopMenu.d.ts +1 -1
  164. package/dist/types/appShell/Wordmark.d.ts +15 -3
  165. package/dist/types/auth/PasswordChecklist.d.ts +1 -1
  166. package/dist/types/calendar/EventCalendar.d.ts +1 -1
  167. package/dist/types/calendar/EventDetails.d.ts +1 -1
  168. package/dist/types/feedback/ConfirmDialog.d.ts +1 -1
  169. package/dist/types/feedback/ModalWaiting.d.ts +1 -1
  170. package/dist/types/feedback/NewBuildNotice.d.ts +1 -1
  171. package/dist/types/filepond/FileDropzone.d.ts +1 -1
  172. package/dist/types/forms/auth/SignInForm.d.ts +1 -1
  173. package/dist/types/forms/security/SecondStepForm.d.ts +1 -1
  174. package/dist/types/forms/security/TwoStepVerificationCard.d.ts +1 -1
  175. package/dist/types/inbox/InboxLayout.d.ts +1 -1
  176. package/dist/types/inbox/MessageList.d.ts +1 -1
  177. package/dist/types/inbox/MessageView.d.ts +1 -1
  178. package/dist/types/index.d.ts +1 -0
  179. package/dist/types/inputs/ColorInput.d.ts +1 -1
  180. package/dist/types/inputs/FormSection.d.ts +2 -2
  181. package/dist/types/inputs/SelectInput.d.ts +1 -1
  182. package/dist/types/inputs/TextInput.d.ts +1 -1
  183. package/dist/types/inputs/WizardLayout.d.ts +1 -1
  184. package/dist/types/kanban/KanbanBoard.d.ts +1 -1
  185. package/dist/types/maps/MapView.d.ts +1 -1
  186. package/dist/types/pwa/InstallInstructionsDialog.d.ts +1 -1
  187. package/dist/types/pwa/InstallPromptBanner.d.ts +1 -1
  188. package/dist/types/security/ActivityLog.d.ts +1 -1
  189. package/dist/types/security/RecoveryCodesDialog.d.ts +1 -1
  190. package/dist/types/security/SessionsCard.d.ts +1 -1
  191. package/dist/types/theme/createToolkitTheme.d.ts +12 -1
  192. package/dist/types/theme/cssTokens.d.ts +46 -0
  193. package/dist/types/theme/glassSurface.d.ts +3 -0
  194. package/dist/types/theme/themePieces.d.ts +20 -0
  195. package/dist/types/ui/actions/ActionsMenu.d.ts +1 -1
  196. package/dist/types/ui/actions/DownloadMenu.d.ts +1 -1
  197. package/dist/types/ui/actions/SelectionActions.d.ts +1 -1
  198. package/dist/types/ui/filters/DateRangeFilter.d.ts +1 -1
  199. package/dist/types/ui/filters/PeriodRangeSelector.d.ts +1 -1
  200. package/dist/types/ui/filters/QuickFilters.d.ts +1 -1
  201. package/dist/types/ui/filters/SegmentedControl.d.ts +7 -2
  202. package/dist/types/ui/labels/StatusChip.d.ts +1 -1
  203. package/dist/types/ui/labels/Tag.d.ts +1 -1
  204. package/dist/types/ui/layout/CollapsibleSection.d.ts +1 -1
  205. package/dist/types/ui/layout/ContentDialog.d.ts +1 -1
  206. package/dist/types/ui/layout/DefinitionList.d.ts +1 -1
  207. package/dist/types/ui/layout/DetailHeader.d.ts +1 -1
  208. package/dist/types/ui/layout/ErrorPage.d.ts +1 -1
  209. package/dist/types/ui/layout/GlassPanel.d.ts +1 -1
  210. package/dist/types/ui/layout/PageHeader.d.ts +1 -1
  211. package/dist/types/ui/layout/PageHeading.d.ts +1 -1
  212. package/dist/types/ui/layout/ProfileHeader.d.ts +1 -1
  213. package/dist/types/ui/layout/ScrollableRow.d.ts +1 -1
  214. package/dist/types/ui/layout/SectionCard.d.ts +1 -1
  215. package/dist/types/ui/metrics/BarList.d.ts +1 -1
  216. package/dist/types/ui/metrics/CalendarHeatmap.d.ts +1 -1
  217. package/dist/types/ui/metrics/ChartTooltip.d.ts +1 -1
  218. package/dist/types/ui/metrics/ColumnChart.d.ts +1 -1
  219. package/dist/types/ui/metrics/LineChart.d.ts +1 -1
  220. package/dist/types/ui/metrics/MetricCard.d.ts +1 -1
  221. package/dist/types/ui/metrics/TableToggle.d.ts +1 -1
  222. package/dist/types/ui/navigation/ColorSchemeToggle.d.ts +1 -1
  223. package/dist/types/ui/navigation/FontSizeToggle.d.ts +1 -1
  224. package/dist/types/ui/navigation/LanguageToggle.d.ts +1 -1
  225. package/dist/types/ui/navigation/PageBreadcrumbs.d.ts +1 -1
  226. package/dist/types/ui/timeline/GroupedTimeline.d.ts +1 -1
  227. package/dist/types/upload/FileDropzone.d.ts +1 -1
  228. package/dist/ui/actions/ActionsMenu.js +9 -4
  229. package/dist/ui/actions/ActionsMenu.js.map +1 -1
  230. package/dist/ui/actions/DownloadMenu.js +21 -17
  231. package/dist/ui/actions/DownloadMenu.js.map +1 -1
  232. package/dist/ui/actions/SelectionActions.js +15 -7
  233. package/dist/ui/actions/SelectionActions.js.map +1 -1
  234. package/dist/ui/filters/DateRangeFilter.js +11 -5
  235. package/dist/ui/filters/DateRangeFilter.js.map +1 -1
  236. package/dist/ui/filters/PeriodRangeSelector.js +11 -5
  237. package/dist/ui/filters/PeriodRangeSelector.js.map +1 -1
  238. package/dist/ui/filters/QuickFilters.js +16 -8
  239. package/dist/ui/filters/QuickFilters.js.map +1 -1
  240. package/dist/ui/filters/SegmentedControl.js +12 -5
  241. package/dist/ui/filters/SegmentedControl.js.map +1 -1
  242. package/dist/ui/labels/StatusChip.js +35 -25
  243. package/dist/ui/labels/StatusChip.js.map +1 -1
  244. package/dist/ui/labels/Tag.js +8 -3
  245. package/dist/ui/labels/Tag.js.map +1 -1
  246. package/dist/ui/layout/CollapsibleSection.js +18 -8
  247. package/dist/ui/layout/CollapsibleSection.js.map +1 -1
  248. package/dist/ui/layout/ContentDialog.js +13 -6
  249. package/dist/ui/layout/ContentDialog.js.map +1 -1
  250. package/dist/ui/layout/DefinitionList.js +13 -6
  251. package/dist/ui/layout/DefinitionList.js.map +1 -1
  252. package/dist/ui/layout/DetailHeader.js +14 -6
  253. package/dist/ui/layout/DetailHeader.js.map +1 -1
  254. package/dist/ui/layout/ErrorPage.js +20 -15
  255. package/dist/ui/layout/ErrorPage.js.map +1 -1
  256. package/dist/ui/layout/FullPageLoader.js +4 -1
  257. package/dist/ui/layout/FullPageLoader.js.map +1 -1
  258. package/dist/ui/layout/GlassPanel.js +8 -4
  259. package/dist/ui/layout/GlassPanel.js.map +1 -1
  260. package/dist/ui/layout/PageHeader.js +10 -4
  261. package/dist/ui/layout/PageHeader.js.map +1 -1
  262. package/dist/ui/layout/PageHeading.js +17 -8
  263. package/dist/ui/layout/PageHeading.js.map +1 -1
  264. package/dist/ui/layout/ProfileHeader.js +21 -10
  265. package/dist/ui/layout/ProfileHeader.js.map +1 -1
  266. package/dist/ui/layout/ScrollableRow.js +19 -15
  267. package/dist/ui/layout/ScrollableRow.js.map +1 -1
  268. package/dist/ui/layout/SectionCard.js +10 -4
  269. package/dist/ui/layout/SectionCard.js.map +1 -1
  270. package/dist/ui/metrics/BarList.js +9 -4
  271. package/dist/ui/metrics/BarList.js.map +1 -1
  272. package/dist/ui/metrics/CalendarHeatmap.js +40 -21
  273. package/dist/ui/metrics/CalendarHeatmap.js.map +1 -1
  274. package/dist/ui/metrics/ChartTooltip.js +6 -2
  275. package/dist/ui/metrics/ChartTooltip.js.map +1 -1
  276. package/dist/ui/metrics/ColumnChart.js +22 -18
  277. package/dist/ui/metrics/ColumnChart.js.map +1 -1
  278. package/dist/ui/metrics/LineChart.js +20 -16
  279. package/dist/ui/metrics/LineChart.js.map +1 -1
  280. package/dist/ui/metrics/MetricCard.js +42 -24
  281. package/dist/ui/metrics/MetricCard.js.map +1 -1
  282. package/dist/ui/metrics/TableToggle.js +7 -2
  283. package/dist/ui/metrics/TableToggle.js.map +1 -1
  284. package/dist/ui/metrics/chartParts.js +14 -5
  285. package/dist/ui/metrics/chartParts.js.map +1 -1
  286. package/dist/ui/navigation/ColorSchemeToggle.js +8 -4
  287. package/dist/ui/navigation/ColorSchemeToggle.js.map +1 -1
  288. package/dist/ui/navigation/FontSizeToggle.js +6 -2
  289. package/dist/ui/navigation/FontSizeToggle.js.map +1 -1
  290. package/dist/ui/navigation/LanguageToggle.js +8 -4
  291. package/dist/ui/navigation/LanguageToggle.js.map +1 -1
  292. package/dist/ui/navigation/PageBreadcrumbs.js +12 -6
  293. package/dist/ui/navigation/PageBreadcrumbs.js.map +1 -1
  294. package/dist/ui/timeline/GroupedTimeline.js +26 -13
  295. package/dist/ui/timeline/GroupedTimeline.js.map +1 -1
  296. package/dist/upload/FileDropzone.js +35 -25
  297. package/dist/upload/FileDropzone.js.map +1 -1
  298. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"ConfirmDialog.js","sources":["../../src/feedback/ConfirmDialog.jsx"],"sourcesContent":["// Diálogo de confirmación: toda acción que cambia algo, manda un correo o descarga un archivo pasa por aquí antes de\n// ejecutarse (regla de tablas). Dice en lenguaje ciudadano QUÉ va a pasar y a QUÉ renglón («¿Dar de baja a Ana López?\n// Ya no podrá entrar; se puede reactivar después»).\n//\n// Recibe el estado de la petición por props (pending, error) en vez de hacerla él: core no conoce el API. Lo normal es\n// alimentarlo con useConfirmAction (máquina confirmActionMachine), que ya garantiza que cancelar no ejecuta y que un\n// doble clic no manda dos peticiones. Mientras la petición está en curso no se puede cerrar ni volver a confirmar.\n//\n// children: lo que el diálogo necesita pedir antes de confirmar (p. ej. el motivo de una baja), debajo del texto.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useId} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport Alert from '@mui/material/Alert';\nimport Button from '@mui/material/Button';\nimport CircularProgress from '@mui/material/CircularProgress';\nimport Dialog from '@mui/material/Dialog';\nimport DialogActions from '@mui/material/DialogActions';\nimport DialogContent from '@mui/material/DialogContent';\nimport DialogContentText from '@mui/material/DialogContentText';\nimport DialogTitle from '@mui/material/DialogTitle';\n\n/**\n * @param {object} props\n * @param {boolean} props.open\n * @param {string} props.title texto ya traducido, con el renglón («¿Dar de baja a Ana López?»)\n * @param {import('react').ReactNode} [props.description] qué va a pasar, en palabras normales\n * @param {string} [props.confirmLabel]\n * @param {string} [props.cancelLabel]\n * @param {'primary'|'error'|'warning'} [props.tone]\n * @param {boolean} [props.isPending]\n * @param {boolean} [props.isConfirmDisabled] la acción ya no aplica (p. ej. conflicto: el registro cambió)\n * @param {string|null} [props.errorMessage] mensaje ya traducido (ApiError.message)\n * @param {() => void} props.onConfirm\n * @param {() => void} props.onClose\n * @param {import('react').ReactNode} [props.children]\n * @param {object} [props.texts] los textos que cambian, con la forma de `common` en los textos del paquete\n */\nexport default function ConfirmDialog({\n open, title, description, confirmLabel, cancelLabel, tone = 'primary',\n isPending = false, isConfirmDisabled = false, errorMessage = null, onConfirm, onClose, children, texts,\n}) {\n const {t} = useToolkitTranslation('common', texts);\n // ids propios: una pantalla puede tener varios diálogos montados y los aria-* no deben apuntar al de otro\n const dialogId = useId();\n\n const handleClose = () => {\n // guard clause: no se cierra a media petición\n if (isPending) {\n return;\n }\n onClose();\n };\n\n return (\n <Dialog open={open} onClose={handleClose} maxWidth=\"xs\" fullWidth\n aria-labelledby={`${dialogId}-title`} aria-describedby={`${dialogId}-description`}>\n <DialogTitle id={`${dialogId}-title`}>{title}</DialogTitle>\n <DialogContent>\n {errorMessage && <Alert severity=\"error\" role=\"alert\" sx={{mb: 2}}>{errorMessage}</Alert>}\n {description && (\n <DialogContentText id={`${dialogId}-description`}>{description}</DialogContentText>\n )}\n {children}\n </DialogContent>\n <DialogActions sx={{px: 3, pb: 2.5}}>\n <Button variant=\"outlined\" color=\"inherit\" onClick={handleClose} disabled={isPending}>\n {cancelLabel ?? t('actions.cancel')}\n </Button>\n <Button variant=\"contained\" color={tone} onClick={onConfirm} disabled={isPending || isConfirmDisabled}\n aria-busy={isPending}>\n {isPending ? <CircularProgress size={20} color=\"inherit\" /> : (confirmLabel ?? t('actions.confirm'))}\n </Button>\n </DialogActions>\n </Dialog>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;;AAsCA,SAAwB,cAAc;AAAA,EAClC;AAAA,EAAM;AAAA,EAAO;AAAA,EAAa;AAAA,EAAc;AAAA,EAAa,OAAO;AAAA,EAC5D,YAAY;AAAA,EAAO,oBAAoB;AAAA,EAAO,eAAe;AAAA,EAAM;AAAA,EAAW;AAAA,EAAS;AAAA,EAAU;AACrG,GAAG;AACC,QAAM,EAAC,EAAA,IAAK,sBAAsB,UAAU,KAAK;AAEjD,QAAM,WAAW,MAAA;AAEjB,QAAM,cAAc,MAAM;AAEtB,QAAI,WAAW;AACX;AAAA,IACJ;AACA,YAAA;AAAA,EACJ;AAEA,SACI;AAAA,IAAC;AAAA,IAAA;AAAA,MAAO;AAAA,MAAY,SAAS;AAAA,MAAa,UAAS;AAAA,MAAK,WAAS;AAAA,MACzD,mBAAiB,GAAG,QAAQ;AAAA,MAAU,oBAAkB,GAAG,QAAQ;AAAA,MACvE,UAAA;AAAA,QAAA,oBAAC,aAAA,EAAY,IAAI,GAAG,QAAQ,UAAW,UAAA,OAAM;AAAA,6BAC5C,eAAA,EACI,UAAA;AAAA,UAAA,gBAAgB,oBAAC,OAAA,EAAM,UAAS,SAAQ,MAAK,SAAQ,IAAI,EAAC,IAAI,EAAA,GAAK,UAAA,aAAA,CAAa;AAAA,UAChF,eACG,oBAAC,mBAAA,EAAkB,IAAI,GAAG,QAAQ,gBAAiB,UAAA,aAAY;AAAA,UAElE;AAAA,QAAA,GACL;AAAA,QACA,qBAAC,iBAAc,IAAI,EAAC,IAAI,GAAG,IAAI,OAC3B,UAAA;AAAA,UAAA,oBAAC,QAAA,EAAO,SAAQ,YAAW,OAAM,WAAU,SAAS,aAAa,UAAU,WACtE,UAAA,eAAe,EAAE,gBAAgB,GACtC;AAAA,UACA;AAAA,YAAC;AAAA,YAAA;AAAA,cAAO,SAAQ;AAAA,cAAY,OAAO;AAAA,cAAM,SAAS;AAAA,cAAW,UAAU,aAAa;AAAA,cAC5E,aAAW;AAAA,cACd,UAAA,YAAY,oBAAC,kBAAA,EAAiB,MAAM,IAAI,OAAM,UAAA,CAAU,IAAM,gBAAgB,EAAE,iBAAiB;AAAA,YAAA;AAAA,UAAA;AAAA,QACtG,EAAA,CACJ;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGZ;"}
1
+ {"version":3,"file":"ConfirmDialog.js","sources":["../../src/feedback/ConfirmDialog.jsx"],"sourcesContent":["// Diálogo de confirmación: toda acción que cambia algo, manda un correo o descarga un archivo pasa por aquí antes de\n// ejecutarse (regla de tablas). Dice en lenguaje ciudadano QUÉ va a pasar y a QUÉ renglón («¿Dar de baja a Ana López?\n// Ya no podrá entrar; se puede reactivar después»).\n//\n// Recibe el estado de la petición por props (pending, error) en vez de hacerla él: core no conoce el API. Lo normal es\n// alimentarlo con useConfirmAction (máquina confirmActionMachine), que ya garantiza que cancelar no ejecuta y que un\n// doble clic no manda dos peticiones. Mientras la petición está en curso no se puede cerrar ni volver a confirmar.\n//\n// children: lo que el diálogo necesita pedir antes de confirmar (p. ej. el motivo de una baja), debajo del texto.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaConfirmDialog con defaultProps y\n// styleOverrides de sus partes: problem, actions.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useId} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport Alert from '@mui/material/Alert';\nimport Button from '@mui/material/Button';\nimport CircularProgress from '@mui/material/CircularProgress';\nimport Dialog from '@mui/material/Dialog';\nimport DialogActions from '@mui/material/DialogActions';\nimport DialogContent from '@mui/material/DialogContent';\nimport DialogContentText from '@mui/material/DialogContentText';\nimport DialogTitle from '@mui/material/DialogTitle';\nimport {piecePart, usePieceProps} from '@/theme/themePieces.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaConfirmDialog)\nconst THEME_NAME = 'CalcitaConfirmDialog';\nconst ConfirmProblem = piecePart(THEME_NAME, 'Problem', Alert);\nconst ConfirmActions = piecePart(THEME_NAME, 'Actions', DialogActions);\n\n/**\n * @param {object} props\n * @param {boolean} props.open\n * @param {string} props.title texto ya traducido, con el renglón («¿Dar de baja a Ana López?»)\n * @param {import('react').ReactNode} [props.description] qué va a pasar, en palabras normales\n * @param {string} [props.confirmLabel]\n * @param {string} [props.cancelLabel]\n * @param {'primary'|'error'|'warning'} [props.tone]\n * @param {boolean} [props.isPending]\n * @param {boolean} [props.isConfirmDisabled] la acción ya no aplica (p. ej. conflicto: el registro cambió)\n * @param {string|null} [props.errorMessage] mensaje ya traducido (ApiError.message)\n * @param {() => void} props.onConfirm\n * @param {() => void} props.onClose\n * @param {import('react').ReactNode} [props.children]\n * @param {object} [props.texts] los textos que cambian, con la forma de `common` en los textos del paquete\n */\nexport default function ConfirmDialog(props) {\n // lo que el proyecto puso en su tema (components.CalcitaConfirmDialog.defaultProps) y encima lo que llega aquí\n const {\n open, title, description, confirmLabel, cancelLabel, tone = 'primary',\n isPending = false, isConfirmDisabled = false, errorMessage = null, onConfirm, onClose, children, texts,\n} = usePieceProps(props, THEME_NAME);\n const {t} = useToolkitTranslation('common', texts);\n // ids propios: una pantalla puede tener varios diálogos montados y los aria-* no deben apuntar al de otro\n const dialogId = useId();\n\n const handleClose = () => {\n // guard clause: no se cierra a media petición\n if (isPending) {\n return;\n }\n onClose();\n };\n\n return (\n <Dialog open={open} onClose={handleClose} maxWidth=\"xs\" fullWidth\n aria-labelledby={`${dialogId}-title`} aria-describedby={`${dialogId}-description`}>\n <DialogTitle id={`${dialogId}-title`}>{title}</DialogTitle>\n <DialogContent>\n {errorMessage && <ConfirmProblem severity=\"error\" role=\"alert\" ownSx={{mb: 2}}>{errorMessage}</ConfirmProblem>}\n {description && (\n <DialogContentText id={`${dialogId}-description`}>{description}</DialogContentText>\n )}\n {children}\n </DialogContent>\n <ConfirmActions ownSx={{px: 3, pb: 2.5}}>\n <Button variant=\"outlined\" color=\"inherit\" onClick={handleClose} disabled={isPending}>\n {cancelLabel ?? t('actions.cancel')}\n </Button>\n <Button variant=\"contained\" color={tone} onClick={onConfirm} disabled={isPending || isConfirmDisabled}\n aria-busy={isPending}>\n {isPending ? <CircularProgress size={20} color=\"inherit\" /> : (confirmLabel ?? t('actions.confirm'))}\n </Button>\n </ConfirmActions>\n </Dialog>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;AA2BA,MAAM,aAAa;AACnB,MAAM,iBAAiB,UAAU,YAAY,WAAW,KAAK;AAC7D,MAAM,iBAAiB,UAAU,YAAY,WAAW,aAAa;AAkBrE,SAAwB,cAAc,OAAO;AAEzC,QAAM;AAAA,IACN;AAAA,IAAM;AAAA,IAAO;AAAA,IAAa;AAAA,IAAc;AAAA,IAAa,OAAO;AAAA,IAC5D,YAAY;AAAA,IAAO,oBAAoB;AAAA,IAAO,eAAe;AAAA,IAAM;AAAA,IAAW;AAAA,IAAS;AAAA,IAAU;AAAA,EAAA,IACjG,cAAc,OAAO,UAAU;AAC/B,QAAM,EAAC,EAAA,IAAK,sBAAsB,UAAU,KAAK;AAEjD,QAAM,WAAW,MAAA;AAEjB,QAAM,cAAc,MAAM;AAEtB,QAAI,WAAW;AACX;AAAA,IACJ;AACA,YAAA;AAAA,EACJ;AAEA,SACI;AAAA,IAAC;AAAA,IAAA;AAAA,MAAO;AAAA,MAAY,SAAS;AAAA,MAAa,UAAS;AAAA,MAAK,WAAS;AAAA,MACzD,mBAAiB,GAAG,QAAQ;AAAA,MAAU,oBAAkB,GAAG,QAAQ;AAAA,MACvE,UAAA;AAAA,QAAA,oBAAC,aAAA,EAAY,IAAI,GAAG,QAAQ,UAAW,UAAA,OAAM;AAAA,6BAC5C,eAAA,EACI,UAAA;AAAA,UAAA,gBAAgB,oBAAC,gBAAA,EAAe,UAAS,SAAQ,MAAK,SAAQ,OAAO,EAAC,IAAI,EAAA,GAAK,UAAA,aAAA,CAAa;AAAA,UAC5F,eACG,oBAAC,mBAAA,EAAkB,IAAI,GAAG,QAAQ,gBAAiB,UAAA,aAAY;AAAA,UAElE;AAAA,QAAA,GACL;AAAA,QACA,qBAAC,kBAAe,OAAO,EAAC,IAAI,GAAG,IAAI,OAC/B,UAAA;AAAA,UAAA,oBAAC,QAAA,EAAO,SAAQ,YAAW,OAAM,WAAU,SAAS,aAAa,UAAU,WACtE,UAAA,eAAe,EAAE,gBAAgB,GACtC;AAAA,UACA;AAAA,YAAC;AAAA,YAAA;AAAA,cAAO,SAAQ;AAAA,cAAY,OAAO;AAAA,cAAM,SAAS;AAAA,cAAW,UAAU,aAAa;AAAA,cAC5E,aAAW;AAAA,cACd,UAAA,YAAY,oBAAC,kBAAA,EAAiB,MAAM,IAAI,OAAM,UAAA,CAAU,IAAM,gBAAgB,EAAE,iBAAiB;AAAA,YAAA;AAAA,UAAA;AAAA,QACtG,EAAA,CACJ;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGZ;"}
@@ -7,13 +7,20 @@ import DialogContent from "@mui/material/DialogContent";
7
7
  import DialogContentText from "@mui/material/DialogContentText";
8
8
  import DialogTitle from "@mui/material/DialogTitle";
9
9
  import Stack from "@mui/material/Stack";
10
- function ModalWaiting({ open, title, text, texts }) {
10
+ import { piecePart, usePieceProps } from "../theme/themePieces.js";
11
+ const THEME_NAME = "CalcitaModalWaiting";
12
+ const WaitingRoot = piecePart(THEME_NAME, "Root", Dialog);
13
+ const WaitingTitle = piecePart(THEME_NAME, "Title", DialogTitle);
14
+ const WaitingBody = piecePart(THEME_NAME, "Body", Stack);
15
+ const WaitingText = piecePart(THEME_NAME, "Text", DialogContentText);
16
+ function ModalWaiting(props) {
17
+ const { open, title, text, texts } = usePieceProps(props, THEME_NAME);
11
18
  const { t } = useToolkitTranslation("common", texts);
12
- return /* @__PURE__ */ jsxs(Dialog, { open, disableEscapeKeyDown: true, "aria-busy": "true", sx: { zIndex: (theme) => theme.zIndex.tooltip + 10 }, children: [
13
- /* @__PURE__ */ jsx(DialogTitle, { sx: { textAlign: "center" }, children: title ?? t("waiting.title") }),
14
- /* @__PURE__ */ jsx(DialogContent, { children: /* @__PURE__ */ jsxs(Stack, { spacing: 2, sx: { alignItems: "center" }, children: [
19
+ return /* @__PURE__ */ jsxs(WaitingRoot, { open, disableEscapeKeyDown: true, "aria-busy": "true", ownSx: { zIndex: (theme) => theme.zIndex.tooltip + 10 }, children: [
20
+ /* @__PURE__ */ jsx(WaitingTitle, { ownSx: { textAlign: "center" }, children: title ?? t("waiting.title") }),
21
+ /* @__PURE__ */ jsx(DialogContent, { children: /* @__PURE__ */ jsxs(WaitingBody, { spacing: 2, ownSx: { alignItems: "center" }, children: [
15
22
  /* @__PURE__ */ jsx(CircularProgress, { size: 28 }),
16
- /* @__PURE__ */ jsx(DialogContentText, { sx: { textAlign: "center" }, children: text ?? t("waiting.text") })
23
+ /* @__PURE__ */ jsx(WaitingText, { ownSx: { textAlign: "center" }, children: text ?? t("waiting.text") })
17
24
  ] }) })
18
25
  ] });
19
26
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ModalWaiting.js","sources":["../../src/feedback/ModalWaiting.jsx"],"sourcesContent":["// Bloqueo general \"espere un momento\" como diálogo modal, para una acción que no se puede interrumpir (p. ej. generar\n// un archivo grande).\n//\n// OJO zIndex: HASTA ARRIBA de todo, encima de modal (1300), snackbar (1400) y tooltip (1500), para que ningún elemento\n// de la interfaz se le monte mientras bloquea. No se cierra con Escape a propósito.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport CircularProgress from '@mui/material/CircularProgress';\nimport Dialog from '@mui/material/Dialog';\nimport DialogContent from '@mui/material/DialogContent';\nimport DialogContentText from '@mui/material/DialogContentText';\nimport DialogTitle from '@mui/material/DialogTitle';\nimport Stack from '@mui/material/Stack';\n\n/**\n * @param {object} props\n * @param {boolean} props.open\n * @param {string} [props.title] ya traducido; por omisión common:waiting.title\n * @param {string} [props.text] ya traducido; por omisión common:waiting.text\n * @param {object} [props.texts] los textos que cambian, con la forma de `common` en los textos del paquete\n */\nexport default function ModalWaiting({open, title, text, texts}) {\n const {t} = useToolkitTranslation('common', texts);\n\n return (\n <Dialog open={open} disableEscapeKeyDown aria-busy=\"true\" sx={{zIndex: (theme) => theme.zIndex.tooltip + 10}}>\n <DialogTitle sx={{textAlign: 'center'}}>{title ?? t('waiting.title')}</DialogTitle>\n <DialogContent>\n <Stack spacing={2} sx={{alignItems: 'center'}}>\n <CircularProgress size={28} />\n <DialogContentText sx={{textAlign: 'center'}}>{text ?? t('waiting.text')}</DialogContentText>\n </Stack>\n </DialogContent>\n </Dialog>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;AAsBA,SAAwB,aAAa,EAAC,MAAM,OAAO,MAAM,SAAQ;AAC7D,QAAM,EAAC,EAAA,IAAK,sBAAsB,UAAU,KAAK;AAEjD,8BACK,QAAA,EAAO,MAAY,sBAAoB,MAAC,aAAU,QAAO,IAAI,EAAC,QAAQ,CAAC,UAAU,MAAM,OAAO,UAAU,MACrG,UAAA;AAAA,IAAA,oBAAC,aAAA,EAAY,IAAI,EAAC,WAAW,YAAY,UAAA,SAAS,EAAE,eAAe,EAAA,CAAE;AAAA,IACrE,oBAAC,eAAA,EACG,UAAA,qBAAC,OAAA,EAAM,SAAS,GAAG,IAAI,EAAC,YAAY,SAAA,GAChC,UAAA;AAAA,MAAA,oBAAC,kBAAA,EAAiB,MAAM,GAAA,CAAI;AAAA,MAC5B,oBAAC,mBAAA,EAAkB,IAAI,EAAC,WAAW,YAAY,UAAA,QAAQ,EAAE,cAAc,EAAA,CAAE;AAAA,IAAA,EAAA,CAC7E,EAAA,CACJ;AAAA,EAAA,GACJ;AAER;"}
1
+ {"version":3,"file":"ModalWaiting.js","sources":["../../src/feedback/ModalWaiting.jsx"],"sourcesContent":["// Bloqueo general \"espere un momento\" como diálogo modal, para una acción que no se puede interrumpir (p. ej. generar\n// un archivo grande).\n//\n// OJO zIndex: HASTA ARRIBA de todo, encima de modal (1300), snackbar (1400) y tooltip (1500), para que ningún elemento\n// de la interfaz se le monte mientras bloquea. No se cierra con Escape a propósito.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaModalWaiting con defaultProps y\n// styleOverrides de sus partes: root, title, body, text.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport CircularProgress from '@mui/material/CircularProgress';\nimport Dialog from '@mui/material/Dialog';\nimport DialogContent from '@mui/material/DialogContent';\nimport DialogContentText from '@mui/material/DialogContentText';\nimport DialogTitle from '@mui/material/DialogTitle';\nimport Stack from '@mui/material/Stack';\nimport {piecePart, usePieceProps} from '@/theme/themePieces.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaModalWaiting)\nconst THEME_NAME = 'CalcitaModalWaiting';\nconst WaitingRoot = piecePart(THEME_NAME, 'Root', Dialog);\nconst WaitingTitle = piecePart(THEME_NAME, 'Title', DialogTitle);\nconst WaitingBody = piecePart(THEME_NAME, 'Body', Stack);\nconst WaitingText = piecePart(THEME_NAME, 'Text', DialogContentText);\n\n/**\n * @param {object} props\n * @param {boolean} props.open\n * @param {string} [props.title] ya traducido; por omisión common:waiting.title\n * @param {string} [props.text] ya traducido; por omisión common:waiting.text\n * @param {object} [props.texts] los textos que cambian, con la forma de `common` en los textos del paquete\n */\nexport default function ModalWaiting(props) {\n // lo que el proyecto puso en su tema (components.CalcitaModalWaiting.defaultProps) y encima lo que llega aquí\n const {open, title, text, texts} = usePieceProps(props, THEME_NAME);\n const {t} = useToolkitTranslation('common', texts);\n\n return (\n <WaitingRoot open={open} disableEscapeKeyDown aria-busy=\"true\" ownSx={{zIndex: (theme) => theme.zIndex.tooltip + 10}}>\n <WaitingTitle ownSx={{textAlign: 'center'}}>{title ?? t('waiting.title')}</WaitingTitle>\n <DialogContent>\n <WaitingBody spacing={2} ownSx={{alignItems: 'center'}}>\n <CircularProgress size={28} />\n <WaitingText ownSx={{textAlign: 'center'}}>{text ?? t('waiting.text')}</WaitingText>\n </WaitingBody>\n </DialogContent>\n </WaitingRoot>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;AAoBA,MAAM,aAAa;AACnB,MAAM,cAAc,UAAU,YAAY,QAAQ,MAAM;AACxD,MAAM,eAAe,UAAU,YAAY,SAAS,WAAW;AAC/D,MAAM,cAAc,UAAU,YAAY,QAAQ,KAAK;AACvD,MAAM,cAAc,UAAU,YAAY,QAAQ,iBAAiB;AASnE,SAAwB,aAAa,OAAO;AAExC,QAAM,EAAC,MAAM,OAAO,MAAM,UAAS,cAAc,OAAO,UAAU;AAClE,QAAM,EAAC,EAAA,IAAK,sBAAsB,UAAU,KAAK;AAEjD,8BACK,aAAA,EAAY,MAAY,sBAAoB,MAAC,aAAU,QAAO,OAAO,EAAC,QAAQ,CAAC,UAAU,MAAM,OAAO,UAAU,MAC7G,UAAA;AAAA,IAAA,oBAAC,cAAA,EAAa,OAAO,EAAC,WAAW,YAAY,UAAA,SAAS,EAAE,eAAe,EAAA,CAAE;AAAA,IACzE,oBAAC,eAAA,EACG,UAAA,qBAAC,aAAA,EAAY,SAAS,GAAG,OAAO,EAAC,YAAY,SAAA,GACzC,UAAA;AAAA,MAAA,oBAAC,kBAAA,EAAiB,MAAM,GAAA,CAAI;AAAA,MAC5B,oBAAC,aAAA,EAAY,OAAO,EAAC,WAAW,YAAY,UAAA,QAAQ,EAAE,cAAc,EAAA,CAAE;AAAA,IAAA,EAAA,CAC1E,EAAA,CACJ;AAAA,EAAA,GACJ;AAER;"}
@@ -11,6 +11,13 @@ import { aboveMobileTabBarOf, GLASS_PANEL_GAP_PIXELS } from "../appShell/shellSi
11
11
  import { minimalFloatingStylesOf } from "../theme/components/minimalComponents.js";
12
12
  import { isGlassTheme, glassStylesOf } from "../theme/glassSurface.js";
13
13
  import { APP_BAR_HEIGHT } from "../ui/layout/appBarHeight.js";
14
+ import { piecePart, usePieceProps } from "../theme/themePieces.js";
15
+ const THEME_NAME = "CalcitaNewBuildNotice";
16
+ const NoticeFloating = piecePart(THEME_NAME, "Floating", Box);
17
+ const NoticeIcon = piecePart(THEME_NAME, "Icon", RefreshRoundedIcon);
18
+ const NoticeMessage = piecePart(THEME_NAME, "Message", Typography);
19
+ const NoticeReloadButton = piecePart(THEME_NAME, "ReloadButton", Button);
20
+ const NoticeBanner = piecePart(THEME_NAME, "Banner", Alert);
14
21
  const NEW_BUILD_NOTICE_VARIANT = Object.freeze({ ribbon: "ribbon", floating: "floating" });
15
22
  const FLOATING_GAP_PIXELS = 8;
16
23
  const FLOATING_ENTER_MILLISECONDS = 280;
@@ -45,7 +52,8 @@ function floatingSxOf(theme) {
45
52
  "@media (prefers-reduced-motion: reduce)": { animation: "none" }
46
53
  };
47
54
  }
48
- function NewBuildNotice({ message, reloadLabel, onReload = reloadPage, variant = NEW_BUILD_NOTICE_VARIANT.ribbon, texts }) {
55
+ function NewBuildNotice(props) {
56
+ const { message, reloadLabel, onReload = reloadPage, variant = NEW_BUILD_NOTICE_VARIANT.ribbon, texts } = usePieceProps(props, THEME_NAME);
49
57
  const { t } = useToolkitTranslation("common", texts);
50
58
  const isNewBuildPublished = useNewBuildPublished();
51
59
  if (!isNewBuildPublished) {
@@ -53,21 +61,21 @@ function NewBuildNotice({ message, reloadLabel, onReload = reloadPage, variant =
53
61
  }
54
62
  const buttonLabel = reloadLabel ?? t("newBuild.reload");
55
63
  if (variant === NEW_BUILD_NOTICE_VARIANT.floating) {
56
- return /* @__PURE__ */ jsxs(Box, { role: "status", "aria-live": "polite", "aria-label": message ?? t("newBuild.message"), sx: floatingSxOf, children: [
57
- /* @__PURE__ */ jsx(RefreshRoundedIcon, { fontSize: "small", sx: { color: "text.secondary" } }),
58
- /* @__PURE__ */ jsx(Typography, { variant: "body2", noWrap: true, sx: { fontWeight: 500, minWidth: 0 }, children: message ?? t("newBuild.short") }),
59
- /* @__PURE__ */ jsx(Button, { size: "small", variant: "contained", onClick: onReload, sx: { flexShrink: 0 }, children: buttonLabel })
64
+ return /* @__PURE__ */ jsxs(NoticeFloating, { role: "status", "aria-live": "polite", "aria-label": message ?? t("newBuild.message"), ownSx: floatingSxOf, children: [
65
+ /* @__PURE__ */ jsx(NoticeIcon, { fontSize: "small", ownSx: { color: "text.secondary" } }),
66
+ /* @__PURE__ */ jsx(NoticeMessage, { variant: "body2", noWrap: true, ownSx: { fontWeight: 500, minWidth: 0 }, children: message ?? t("newBuild.short") }),
67
+ /* @__PURE__ */ jsx(NoticeReloadButton, { size: "small", variant: "contained", onClick: onReload, ownSx: { flexShrink: 0 }, children: buttonLabel })
60
68
  ] });
61
69
  }
62
70
  return /* @__PURE__ */ jsx(
63
- Alert,
71
+ NoticeBanner,
64
72
  {
65
73
  severity: "warning",
66
74
  square: true,
67
75
  role: "status",
68
76
  "aria-live": "polite",
69
- sx: { py: 0, px: { xs: 2, md: 3 }, alignItems: "center" },
70
- action: /* @__PURE__ */ jsx(Button, { size: "small", variant: "outlined", color: "inherit", onClick: onReload, sx: { flexShrink: 0 }, children: buttonLabel }),
77
+ ownSx: { py: 0, px: { xs: 2, md: 3 }, alignItems: "center" },
78
+ action: /* @__PURE__ */ jsx(NoticeReloadButton, { size: "small", variant: "outlined", color: "inherit", onClick: onReload, ownSx: { flexShrink: 0 }, children: buttonLabel }),
71
79
  children: message ?? t("newBuild.message")
72
80
  }
73
81
  );
@@ -1 +1 @@
1
- {"version":3,"file":"NewBuildNotice.js","sources":["../../src/feedback/NewBuildNotice.jsx"],"sourcesContent":["// Aviso «Hay una versión nueva» con su botón de recargar.\n//\n// Sale cuando el servidor anuncia una compilación del front distinta a la que corre en la pestaña\n// (observability/publishedBuild.js) y se queda hasta que la persona recarga. No recarga solo: la persona puede estar a\n// media captura y decide cuándo.\n//\n// Dos formas, el mismo aviso:\n// - «ribbon» (por omisión): una franja a todo lo ancho que el proyecto pone donde se vea siempre, normalmente dentro de\n// su AppBar, debajo de la barra. Se arma con el Alert de MUI, que ya trae sus colores para claro y oscuro.\n// - «floating»: una cápsula de vidrio que flota aparte, sin tocar la barra, como el reproductor chico que Apple pone\n// encima de las pestañas en el iPhone. En escritorio, centrada debajo de la barra de arriba; en el teléfono, encima de\n// la barra de pestañas. Es la que usa AppLayout con vidrio: dentro de la cápsula de la barra la franja la hacía crecer al\n// doble, la curva cortaba el texto y el botón se salía por la orilla (lo reportaron).\n// Sin compilación nueva no pinta nada ni ocupa lugar.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport Alert from '@mui/material/Alert';\nimport Box from '@mui/material/Box';\nimport Button from '@mui/material/Button';\nimport Typography from '@mui/material/Typography';\nimport RefreshRoundedIcon from '@mui/icons-material/RefreshRounded';\n\nimport {useNewBuildPublished} from '@/observability/publishedBuild.js';\nimport {GLASS_PANEL_GAP_PIXELS, aboveMobileTabBarOf} from '@/appShell/shellSizes.js';\nimport {minimalFloatingStylesOf} from '@/theme/components/minimalComponents.js';\nimport {glassStylesOf, isGlassTheme} from '@/theme/glassSurface.js';\nimport {APP_BAR_HEIGHT} from '@/ui/layout/appBarHeight.js';\n\n/** Las formas del aviso: franja (dentro de la barra del proyecto) o cápsula flotante (la del marco con vidrio). */\nexport const NEW_BUILD_NOTICE_VARIANT = Object.freeze({ribbon: 'ribbon', floating: 'floating'});\n\n// lo que se separa la cápsula de la barra (px) y lo que tarda en aparecer (ms)\nconst FLOATING_GAP_PIXELS = 8;\nconst FLOATING_ENTER_MILLISECONDS = 280;\n\n/** Lo que hace el botón si el proyecto no dice otra cosa: recargar la página, que trae la compilación nueva. */\nfunction reloadPage() {\n window.location.reload();\n}\n\n/**\n * La cápsula flotante: fija a la ventana y centrada. En escritorio queda debajo de la barra de arriba (su alto real,\n * APP_BAR_HEIGHT); en el teléfono, encima de la barra de pestañas (su alto, sus separaciones y la zona del gesto de inicio).\n *\n * Va centrada en la VENTANA, debajo del centro de la barra (que va de orilla a orilla): centrada en el contenido quedaba\n * corrida a la derecha respecto a la barra (lo reportaron).\n *\n * @param {object} theme\n * @returns {object}\n */\nfunction floatingSxOf(theme) {\n return {\n // con vidrio, cápsula de vidrio; con el minimalista (barra en cápsulas), sólida del gris de lo que flota\n ...(isGlassTheme(theme) ? glassStylesOf(theme) : minimalFloatingStylesOf(theme)),\n position: 'fixed',\n left: '50%',\n transform: 'translateX(-50%)',\n zIndex: theme.zIndex.appBar,\n display: 'flex',\n alignItems: 'center',\n gap: 1.5,\n width: 'max-content',\n maxWidth: `calc(100% - ${GLASS_PANEL_GAP_PIXELS * 2}px)`,\n borderRadius: '999px',\n py: 0.75,\n pl: 2,\n pr: 0.75,\n top: `calc(${APP_BAR_HEIGHT} + ${FLOATING_GAP_PIXELS}px)`,\n ...aboveMobileTabBarOf(FLOATING_GAP_PIXELS),\n // aparece subiendo un poco y aclarándose; sin movimiento si la persona lo pidió así en su equipo\n '@keyframes calcitaNoticeEnter': {\n from: {opacity: 0, transform: 'translate(-50%, 8px) scale(0.96)'},\n to: {opacity: 1, transform: 'translate(-50%, 0) scale(1)'},\n },\n animation: `calcitaNoticeEnter ${FLOATING_ENTER_MILLISECONDS}ms ${theme.transitions.easing.easeOut}`,\n '@media (prefers-reduced-motion: reduce)': {animation: 'none'},\n };\n}\n\n/**\n * @param {object} [props]\n * @param {string} [props.message] ya traducido; por omisión el del paquete (en la cápsula, el corto: common:newBuild.short)\n * @param {string} [props.reloadLabel] ya traducido; por omisión «Recargar»\n * @param {() => void} [props.onReload] por omisión recarga la página\n * @param {'ribbon'|'floating'} [props.variant] franja o cápsula flotante (NEW_BUILD_NOTICE_VARIANT); por omisión franja\n * @param {object} [props.texts] los textos que cambian, con la forma de `common` en los textos del paquete\n */\nexport default function NewBuildNotice({message, reloadLabel, onReload = reloadPage, variant = NEW_BUILD_NOTICE_VARIANT.ribbon, texts}) {\n const {t} = useToolkitTranslation('common', texts);\n const isNewBuildPublished = useNewBuildPublished();\n\n // guard clause: la compilación de la pestaña sigue siendo la publicada; el aviso no ocupa lugar\n if (!isNewBuildPublished) {\n return null;\n }\n\n const buttonLabel = reloadLabel ?? t('newBuild.reload');\n\n if (variant === NEW_BUILD_NOTICE_VARIANT.floating) {\n // el texto largo va para el lector de pantalla; a la vista, el corto (una cápsula de un renglón)\n return (\n <Box role=\"status\" aria-live=\"polite\" aria-label={message ?? t('newBuild.message')} sx={floatingSxOf}>\n <RefreshRoundedIcon fontSize=\"small\" sx={{color: 'text.secondary'}} />\n <Typography variant=\"body2\" noWrap sx={{fontWeight: 500, minWidth: 0}}>{message ?? t('newBuild.short')}</Typography>\n <Button size=\"small\" variant=\"contained\" onClick={onReload} sx={{flexShrink: 0}}>{buttonLabel}</Button>\n </Box>\n );\n }\n\n return (\n <Alert severity=\"warning\" square role=\"status\" aria-live=\"polite\"\n sx={{py: 0, px: {xs: 2, md: 3}, alignItems: 'center'}}\n action={(\n <Button size=\"small\" variant=\"outlined\" color=\"inherit\" onClick={onReload} sx={{flexShrink: 0}}>\n {buttonLabel}\n </Button>\n )}>\n {message ?? t('newBuild.message')}\n </Alert>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;AA8BO,MAAM,2BAA2B,OAAO,OAAO,EAAC,QAAQ,UAAU,UAAU,YAAW;AAG9F,MAAM,sBAAsB;AAC5B,MAAM,8BAA8B;AAGpC,SAAS,aAAa;AAClB,SAAO,SAAS,OAAA;AACpB;AAYA,SAAS,aAAa,OAAO;AACzB,SAAO;AAAA;AAAA,IAEH,GAAI,aAAa,KAAK,IAAI,cAAc,KAAK,IAAI,wBAAwB,KAAK;AAAA,IAC9E,UAAU;AAAA,IACV,MAAM;AAAA,IACN,WAAW;AAAA,IACX,QAAQ,MAAM,OAAO;AAAA,IACrB,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,KAAK;AAAA,IACL,OAAO;AAAA,IACP,UAAU,eAAe,yBAAyB,CAAC;AAAA,IACnD,cAAc;AAAA,IACd,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,KAAK,QAAQ,cAAc,MAAM,mBAAmB;AAAA,IACpD,GAAG,oBAAoB,mBAAmB;AAAA;AAAA,IAE1C,iCAAiC;AAAA,MAC7B,MAAM,EAAC,SAAS,GAAG,WAAW,mCAAA;AAAA,MAC9B,IAAI,EAAC,SAAS,GAAG,WAAW,8BAAA;AAAA,IAA6B;AAAA,IAE7D,WAAW,sBAAsB,2BAA2B,MAAM,MAAM,YAAY,OAAO,OAAO;AAAA,IAClG,2CAA2C,EAAC,WAAW,OAAA;AAAA,EAAM;AAErE;AAUA,SAAwB,eAAe,EAAC,SAAS,aAAa,WAAW,YAAY,UAAU,yBAAyB,QAAQ,SAAQ;AACpI,QAAM,EAAC,EAAA,IAAK,sBAAsB,UAAU,KAAK;AACjD,QAAM,sBAAsB,qBAAA;AAG5B,MAAI,CAAC,qBAAqB;AACtB,WAAO;AAAA,EACX;AAEA,QAAM,cAAc,eAAe,EAAE,iBAAiB;AAEtD,MAAI,YAAY,yBAAyB,UAAU;AAE/C,WACI,qBAAC,KAAA,EAAI,MAAK,UAAS,aAAU,UAAS,cAAY,WAAW,EAAE,kBAAkB,GAAG,IAAI,cACpF,UAAA;AAAA,MAAA,oBAAC,sBAAmB,UAAS,SAAQ,IAAI,EAAC,OAAO,oBAAmB;AAAA,0BACnE,YAAA,EAAW,SAAQ,SAAQ,QAAM,MAAC,IAAI,EAAC,YAAY,KAAK,UAAU,KAAK,UAAA,WAAW,EAAE,gBAAgB,GAAE;AAAA,MACvG,oBAAC,QAAA,EAAO,MAAK,SAAQ,SAAQ,aAAY,SAAS,UAAU,IAAI,EAAC,YAAY,EAAA,GAAK,UAAA,YAAA,CAAY;AAAA,IAAA,GAClG;AAAA,EAER;AAEA,SACI;AAAA,IAAC;AAAA,IAAA;AAAA,MAAM,UAAS;AAAA,MAAU,QAAM;AAAA,MAAC,MAAK;AAAA,MAAS,aAAU;AAAA,MAClD,IAAI,EAAC,IAAI,GAAG,IAAI,EAAC,IAAI,GAAG,IAAI,KAAI,YAAY,SAAA;AAAA,MAC5C,QACI,oBAAC,QAAA,EAAO,MAAK,SAAQ,SAAQ,YAAW,OAAM,WAAU,SAAS,UAAU,IAAI,EAAC,YAAY,KACvF,UAAA,aACL;AAAA,MAEN,UAAA,WAAW,EAAE,kBAAkB;AAAA,IAAA;AAAA,EAAA;AAG5C;"}
1
+ {"version":3,"file":"NewBuildNotice.js","sources":["../../src/feedback/NewBuildNotice.jsx"],"sourcesContent":["// Aviso «Hay una versión nueva» con su botón de recargar.\n//\n// Sale cuando el servidor anuncia una compilación del front distinta a la que corre en la pestaña\n// (observability/publishedBuild.js) y se queda hasta que la persona recarga. No recarga solo: la persona puede estar a\n// media captura y decide cuándo.\n//\n// Dos formas, el mismo aviso:\n// - «ribbon» (por omisión): una franja a todo lo ancho que el proyecto pone donde se vea siempre, normalmente dentro de\n// su AppBar, debajo de la barra. Se arma con el Alert de MUI, que ya trae sus colores para claro y oscuro.\n// - «floating»: una cápsula de vidrio que flota aparte, sin tocar la barra, como el reproductor chico que Apple pone\n// encima de las pestañas en el iPhone. En escritorio, centrada debajo de la barra de arriba; en el teléfono, encima de\n// la barra de pestañas. Es la que usa AppLayout con vidrio: dentro de la cápsula de la barra la franja la hacía crecer al\n// doble, la curva cortaba el texto y el botón se salía por la orilla (lo reportaron).\n// Sin compilación nueva no pinta nada ni ocupa lugar.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaNewBuildNotice con defaultProps y\n// styleOverrides de sus partes: floating, icon, message, reloadButton, banner.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport Alert from '@mui/material/Alert';\nimport Box from '@mui/material/Box';\nimport Button from '@mui/material/Button';\nimport Typography from '@mui/material/Typography';\nimport RefreshRoundedIcon from '@mui/icons-material/RefreshRounded';\n\nimport {useNewBuildPublished} from '@/observability/publishedBuild.js';\nimport {GLASS_PANEL_GAP_PIXELS, aboveMobileTabBarOf} from '@/appShell/shellSizes.js';\nimport {minimalFloatingStylesOf} from '@/theme/components/minimalComponents.js';\nimport {glassStylesOf, isGlassTheme} from '@/theme/glassSurface.js';\nimport {APP_BAR_HEIGHT} from '@/ui/layout/appBarHeight.js';\nimport {piecePart, usePieceProps} from '@/theme/themePieces.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaNewBuildNotice)\nconst THEME_NAME = 'CalcitaNewBuildNotice';\nconst NoticeFloating = piecePart(THEME_NAME, 'Floating', Box);\nconst NoticeIcon = piecePart(THEME_NAME, 'Icon', RefreshRoundedIcon);\nconst NoticeMessage = piecePart(THEME_NAME, 'Message', Typography);\nconst NoticeReloadButton = piecePart(THEME_NAME, 'ReloadButton', Button);\nconst NoticeBanner = piecePart(THEME_NAME, 'Banner', Alert);\n\n/** Las formas del aviso: franja (dentro de la barra del proyecto) o cápsula flotante (la del marco con vidrio). */\nexport const NEW_BUILD_NOTICE_VARIANT = Object.freeze({ribbon: 'ribbon', floating: 'floating'});\n\n// lo que se separa la cápsula de la barra (px) y lo que tarda en aparecer (ms)\nconst FLOATING_GAP_PIXELS = 8;\nconst FLOATING_ENTER_MILLISECONDS = 280;\n\n/** Lo que hace el botón si el proyecto no dice otra cosa: recargar la página, que trae la compilación nueva. */\nfunction reloadPage() {\n window.location.reload();\n}\n\n/**\n * La cápsula flotante: fija a la ventana y centrada. En escritorio queda debajo de la barra de arriba (su alto real,\n * APP_BAR_HEIGHT); en el teléfono, encima de la barra de pestañas (su alto, sus separaciones y la zona del gesto de inicio).\n *\n * Va centrada en la VENTANA, debajo del centro de la barra (que va de orilla a orilla): centrada en el contenido quedaba\n * corrida a la derecha respecto a la barra (lo reportaron).\n *\n * @param {object} theme\n * @returns {object}\n */\nfunction floatingSxOf(theme) {\n return {\n // con vidrio, cápsula de vidrio; con el minimalista (barra en cápsulas), sólida del gris de lo que flota\n ...(isGlassTheme(theme) ? glassStylesOf(theme) : minimalFloatingStylesOf(theme)),\n position: 'fixed',\n left: '50%',\n transform: 'translateX(-50%)',\n zIndex: theme.zIndex.appBar,\n display: 'flex',\n alignItems: 'center',\n gap: 1.5,\n width: 'max-content',\n maxWidth: `calc(100% - ${GLASS_PANEL_GAP_PIXELS * 2}px)`,\n borderRadius: '999px',\n py: 0.75,\n pl: 2,\n pr: 0.75,\n top: `calc(${APP_BAR_HEIGHT} + ${FLOATING_GAP_PIXELS}px)`,\n ...aboveMobileTabBarOf(FLOATING_GAP_PIXELS),\n // aparece subiendo un poco y aclarándose; sin movimiento si la persona lo pidió así en su equipo\n '@keyframes calcitaNoticeEnter': {\n from: {opacity: 0, transform: 'translate(-50%, 8px) scale(0.96)'},\n to: {opacity: 1, transform: 'translate(-50%, 0) scale(1)'},\n },\n animation: `calcitaNoticeEnter ${FLOATING_ENTER_MILLISECONDS}ms ${theme.transitions.easing.easeOut}`,\n '@media (prefers-reduced-motion: reduce)': {animation: 'none'},\n };\n}\n\n/**\n * @param {object} [props]\n * @param {string} [props.message] ya traducido; por omisión el del paquete (en la cápsula, el corto: common:newBuild.short)\n * @param {string} [props.reloadLabel] ya traducido; por omisión «Recargar»\n * @param {() => void} [props.onReload] por omisión recarga la página\n * @param {'ribbon'|'floating'} [props.variant] franja o cápsula flotante (NEW_BUILD_NOTICE_VARIANT); por omisión franja\n * @param {object} [props.texts] los textos que cambian, con la forma de `common` en los textos del paquete\n */\nexport default function NewBuildNotice(props) {\n // lo que el proyecto puso en su tema (components.CalcitaNewBuildNotice.defaultProps) y encima lo que llega aquí\n const {message, reloadLabel, onReload = reloadPage, variant = NEW_BUILD_NOTICE_VARIANT.ribbon, texts} = usePieceProps(props, THEME_NAME);\n const {t} = useToolkitTranslation('common', texts);\n const isNewBuildPublished = useNewBuildPublished();\n\n // guard clause: la compilación de la pestaña sigue siendo la publicada; el aviso no ocupa lugar\n if (!isNewBuildPublished) {\n return null;\n }\n\n const buttonLabel = reloadLabel ?? t('newBuild.reload');\n\n if (variant === NEW_BUILD_NOTICE_VARIANT.floating) {\n // el texto largo va para el lector de pantalla; a la vista, el corto (una cápsula de un renglón)\n return (\n <NoticeFloating role=\"status\" aria-live=\"polite\" aria-label={message ?? t('newBuild.message')} ownSx={floatingSxOf}>\n <NoticeIcon fontSize=\"small\" ownSx={{color: 'text.secondary'}} />\n <NoticeMessage variant=\"body2\" noWrap ownSx={{fontWeight: 500, minWidth: 0}}>{message ?? t('newBuild.short')}</NoticeMessage>\n <NoticeReloadButton size=\"small\" variant=\"contained\" onClick={onReload} ownSx={{flexShrink: 0}}>{buttonLabel}</NoticeReloadButton>\n </NoticeFloating>\n );\n }\n\n return (\n <NoticeBanner severity=\"warning\" square role=\"status\" aria-live=\"polite\"\n ownSx={{py: 0, px: {xs: 2, md: 3}, alignItems: 'center'}}\n action={(\n <NoticeReloadButton size=\"small\" variant=\"outlined\" color=\"inherit\" onClick={onReload} ownSx={{flexShrink: 0}}>\n {buttonLabel}\n </NoticeReloadButton>\n )}>\n {message ?? t('newBuild.message')}\n </NoticeBanner>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;AAkCA,MAAM,aAAa;AACnB,MAAM,iBAAiB,UAAU,YAAY,YAAY,GAAG;AAC5D,MAAM,aAAa,UAAU,YAAY,QAAQ,kBAAkB;AACnE,MAAM,gBAAgB,UAAU,YAAY,WAAW,UAAU;AACjE,MAAM,qBAAqB,UAAU,YAAY,gBAAgB,MAAM;AACvE,MAAM,eAAe,UAAU,YAAY,UAAU,KAAK;AAGnD,MAAM,2BAA2B,OAAO,OAAO,EAAC,QAAQ,UAAU,UAAU,YAAW;AAG9F,MAAM,sBAAsB;AAC5B,MAAM,8BAA8B;AAGpC,SAAS,aAAa;AAClB,SAAO,SAAS,OAAA;AACpB;AAYA,SAAS,aAAa,OAAO;AACzB,SAAO;AAAA;AAAA,IAEH,GAAI,aAAa,KAAK,IAAI,cAAc,KAAK,IAAI,wBAAwB,KAAK;AAAA,IAC9E,UAAU;AAAA,IACV,MAAM;AAAA,IACN,WAAW;AAAA,IACX,QAAQ,MAAM,OAAO;AAAA,IACrB,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,KAAK;AAAA,IACL,OAAO;AAAA,IACP,UAAU,eAAe,yBAAyB,CAAC;AAAA,IACnD,cAAc;AAAA,IACd,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,KAAK,QAAQ,cAAc,MAAM,mBAAmB;AAAA,IACpD,GAAG,oBAAoB,mBAAmB;AAAA;AAAA,IAE1C,iCAAiC;AAAA,MAC7B,MAAM,EAAC,SAAS,GAAG,WAAW,mCAAA;AAAA,MAC9B,IAAI,EAAC,SAAS,GAAG,WAAW,8BAAA;AAAA,IAA6B;AAAA,IAE7D,WAAW,sBAAsB,2BAA2B,MAAM,MAAM,YAAY,OAAO,OAAO;AAAA,IAClG,2CAA2C,EAAC,WAAW,OAAA;AAAA,EAAM;AAErE;AAUA,SAAwB,eAAe,OAAO;AAE1C,QAAM,EAAC,SAAS,aAAa,WAAW,YAAY,UAAU,yBAAyB,QAAQ,MAAA,IAAS,cAAc,OAAO,UAAU;AACvI,QAAM,EAAC,EAAA,IAAK,sBAAsB,UAAU,KAAK;AACjD,QAAM,sBAAsB,qBAAA;AAG5B,MAAI,CAAC,qBAAqB;AACtB,WAAO;AAAA,EACX;AAEA,QAAM,cAAc,eAAe,EAAE,iBAAiB;AAEtD,MAAI,YAAY,yBAAyB,UAAU;AAE/C,WACI,qBAAC,gBAAA,EAAe,MAAK,UAAS,aAAU,UAAS,cAAY,WAAW,EAAE,kBAAkB,GAAG,OAAO,cAClG,UAAA;AAAA,MAAA,oBAAC,cAAW,UAAS,SAAQ,OAAO,EAAC,OAAO,oBAAmB;AAAA,0BAC9D,eAAA,EAAc,SAAQ,SAAQ,QAAM,MAAC,OAAO,EAAC,YAAY,KAAK,UAAU,KAAK,UAAA,WAAW,EAAE,gBAAgB,GAAE;AAAA,MAC7G,oBAAC,oBAAA,EAAmB,MAAK,SAAQ,SAAQ,aAAY,SAAS,UAAU,OAAO,EAAC,YAAY,EAAA,GAAK,UAAA,YAAA,CAAY;AAAA,IAAA,GACjH;AAAA,EAER;AAEA,SACI;AAAA,IAAC;AAAA,IAAA;AAAA,MAAa,UAAS;AAAA,MAAU,QAAM;AAAA,MAAC,MAAK;AAAA,MAAS,aAAU;AAAA,MACzD,OAAO,EAAC,IAAI,GAAG,IAAI,EAAC,IAAI,GAAG,IAAI,KAAI,YAAY,SAAA;AAAA,MAC/C,QACI,oBAAC,oBAAA,EAAmB,MAAK,SAAQ,SAAQ,YAAW,OAAM,WAAU,SAAS,UAAU,OAAO,EAAC,YAAY,KACtG,UAAA,aACL;AAAA,MAEN,UAAA,WAAW,EAAE,kBAAkB;AAAA,IAAA;AAAA,EAAA;AAG5C;"}
@@ -15,6 +15,13 @@ import { toastError } from "../feedback/toast.js";
15
15
  import { accentTextStylesOf } from "../theme/glassSurface.js";
16
16
  import { useFormatters } from "../toolkit/useFormatters.js";
17
17
  import { resolveAccept, parseFileSize, FILE_REJECTION } from "../upload/fileRules.js";
18
+ import { piecePart, usePieceProps } from "../theme/themePieces.js";
19
+ const THEME_NAME = "CalcitaPondDropzone";
20
+ const PondRoot = piecePart(THEME_NAME, "Root", Box);
21
+ const PondSummary = piecePart(THEME_NAME, "Summary", Typography);
22
+ const PondLimits = piecePart(THEME_NAME, "Limits", Typography);
23
+ const PondActions = piecePart(THEME_NAME, "Actions", Stack);
24
+ const PondExtra = piecePart(THEME_NAME, "Extra", Box);
18
25
  registerPlugin(FilePondPluginFileValidateType, FilePondPluginFileValidateSize);
19
26
  const AUTO_REJECT_DELAY_MILLISECONDS = 1800;
20
27
  const MAX_FILES_WARNING = "max files";
@@ -72,32 +79,33 @@ function pondThemeSxOf(theme) {
72
79
  "& .filepond--file, & .filepond--file-action-button": { color: palette.primary.contrastText }
73
80
  };
74
81
  }
75
- function FileDropzone({
76
- value = null,
77
- onChange,
78
- accept,
79
- maxSize,
80
- multiple = false,
81
- maxFiles,
82
- maxTotalSize,
83
- name,
84
- hint,
85
- buttonLabel,
86
- disabled = false,
87
- busy = false,
88
- busyModal = false,
89
- autoReject = true,
90
- validate = null,
91
- server = null,
92
- instantUpload = false,
93
- apiRef = null,
94
- onProcessProgress,
95
- onReject,
96
- actions,
97
- children,
98
- sx,
99
- texts
100
- }) {
82
+ function FileDropzone(props) {
83
+ const {
84
+ value = null,
85
+ onChange,
86
+ accept,
87
+ maxSize,
88
+ multiple = false,
89
+ maxFiles,
90
+ maxTotalSize,
91
+ name,
92
+ hint,
93
+ buttonLabel,
94
+ disabled = false,
95
+ busy = false,
96
+ busyModal = false,
97
+ autoReject = true,
98
+ validate = null,
99
+ server = null,
100
+ instantUpload = false,
101
+ apiRef = null,
102
+ onProcessProgress,
103
+ onReject,
104
+ actions,
105
+ children,
106
+ sx,
107
+ texts
108
+ } = usePieceProps(props, THEME_NAME);
101
109
  const { t } = useToolkitTranslation("common", texts);
102
110
  const { formatFileSize } = useFormatters();
103
111
  const generatedId = useId();
@@ -207,7 +215,7 @@ function FileDropzone({
207
215
  const totalBytes = multiple ? files.reduce((sum, file) => sum + (file?.size ?? 0), 0) : 0;
208
216
  const hintText = hint ?? (multiple ? t("upload.hintMultiple") : t("upload.hint"));
209
217
  const chooseText = buttonLabel ?? (multiple ? t("upload.chooseMultiple") : t("upload.choose"));
210
- return /* @__PURE__ */ jsxs(Box, { sx: [(theme) => pondThemeSxOf(theme), ...Array.isArray(sx) ? sx : [sx]], children: [
218
+ return /* @__PURE__ */ jsxs(PondRoot, { ownSx: (theme) => pondThemeSxOf(theme), sx, children: [
211
219
  /* @__PURE__ */ jsx(
212
220
  FilePond,
213
221
  {
@@ -243,10 +251,10 @@ function FileDropzone({
243
251
  ...pondLabelsOf(t)
244
252
  }
245
253
  ),
246
- multiple && files.length > 0 && /* @__PURE__ */ jsx(Typography, { variant: "body2", sx: { textAlign: "center", mt: 1, fontWeight: 500 }, children: maxTotalBytes ? t("upload.summaryOf", { count: files.length, total: formatFileSize(totalBytes), max: formatFileSize(maxTotalBytes) }) : t("upload.summary", { count: files.length, total: formatFileSize(totalBytes) }) }),
247
- limitHints.length > 0 && /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "text.secondary", sx: { display: "block", textAlign: "center", mt: 0.5 }, children: limitHints.join(" · ") }),
248
- actions && /* @__PURE__ */ jsx(Stack, { direction: { xs: "column", sm: "row" }, spacing: 2, sx: { justifyContent: "center", alignItems: { xs: "stretch", sm: "center" }, mt: 2 }, children: actions }),
249
- children && /* @__PURE__ */ jsx(Box, { sx: { mt: 3 }, children }),
254
+ multiple && files.length > 0 && /* @__PURE__ */ jsx(PondSummary, { variant: "body2", ownSx: { textAlign: "center", mt: 1, fontWeight: 500 }, children: maxTotalBytes ? t("upload.summaryOf", { count: files.length, total: formatFileSize(totalBytes), max: formatFileSize(maxTotalBytes) }) : t("upload.summary", { count: files.length, total: formatFileSize(totalBytes) }) }),
255
+ limitHints.length > 0 && /* @__PURE__ */ jsx(PondLimits, { variant: "caption", color: "text.secondary", ownSx: { display: "block", textAlign: "center", mt: 0.5 }, children: limitHints.join(" · ") }),
256
+ actions && /* @__PURE__ */ jsx(PondActions, { direction: { xs: "column", sm: "row" }, spacing: 2, ownSx: { justifyContent: "center", alignItems: { xs: "stretch", sm: "center" }, mt: 2 }, children: actions }),
257
+ children && /* @__PURE__ */ jsx(PondExtra, { ownSx: { mt: 3 }, children }),
250
258
  busyModal && /* @__PURE__ */ jsx(ModalWaiting, { open: busy, title: busyModal.title, text: busyModal.text })
251
259
  ] });
252
260
  }
@@ -1 +1 @@
1
- {"version":3,"file":"FileDropzone.js","sources":["../../src/filepond/FileDropzone.jsx"],"sourcesContent":["// La zona para soltar archivos con FilePond por dentro (react-filepond): el MISMO uso que la de MUI (FileDropzone de la\n// puerta principal) más varios archivos, animaciones, el error dentro de cada archivo y, si se quiere, la subida de cada\n// archivo con su propia barra de avance (server). Cambiar de una a otra es cambiar el import.\n//\n// Solo ELIGE archivos (controlado: value / onChange). Enviarlos es de la pantalla: arma su FormData y los sube\n// (uploadWithProgress + uploadMachine). Huecos: actions (botones abajo) y children (lo que va debajo).\n//\n// Configuración, además de la de la zona de MUI (accept, maxSize, hint, buttonLabel, disabled, busy, busyModal, onReject):\n// multiple varios archivos: value / onChange hablan en ARREGLOS de File\n// maxFiles tope de archivos (solo con multiple)\n// maxTotalSize tope de lo que pesan todos juntos (solo con multiple), con unidades como maxSize\n// validate revisión del CONTENIDO al entrar: async (file) => true | 'mensaje de rechazo' (un .xml bien formado\n// puede no ser una factura); lo que no pasa no entra a la lista\n// server subir cada archivo por su cuenta con su barra (createPondServer, o un process propio)\n// instantUpload con server, sube al soltar (por omisión NO: regla de la casa, sube cuando la persona oprime el botón,\n// que llama apiRef.current.processFiles())\n// apiRef la API de FilePond para ese botón (processFiles, getFiles, removeFiles…). OJO: processFiles() regresa\n// una promesa que FALLA si algún archivo no subió; el porqué ya se ve en su tarjeta, pero hay que\n// atraparla (.catch) o queda un error suelto en la consola\n// onProcessProgress({done, total}) el «subidos 2 de 5» del modo server\n// autoReject (por omisión sí) lo que no es de un tipo permitido enseña su error un momento y se quita solo\n//\n// OJO, trampa pagada (react-filepond#198): react-filepond no siempre refresca sus funciones después de montar, y puede\n// quedarse para siempre con la del primer render: quitar el archivo no apagaba el botón porque se comparaba contra un\n// value viejo. Blindaje: las funciones que recibe FilePond no cambian nunca (useCallback sin dependencias) y todo lo que\n// necesitan de afuera lo leen de UN ref que cada render vuelve a apuntar (latest).\n//\n// OJO: FilePond va NO controlado. Pasarle los archivos como prop revive archivos recién quitados; la verdad es FilePond y\n// value es su espejo. Lo único que se manda de afuera hacia adentro es LIMPIAR (value vacío vacía la zona).\n//\n// Los colores salen del tema (claro y oscuro) con selectores que le ganan a la hoja de FilePond sin importar el orden.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useCallback, useEffect, useId, useMemo, useRef} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport Box from '@mui/material/Box';\nimport Stack from '@mui/material/Stack';\nimport Typography from '@mui/material/Typography';\nimport {FileStatus} from 'filepond';\nimport FilePondPluginFileValidateSize from 'filepond-plugin-file-validate-size';\nimport FilePondPluginFileValidateType from 'filepond-plugin-file-validate-type';\nimport {FilePond, registerPlugin} from 'react-filepond';\n// la hoja de FilePond la carga el empaquetador del proyecto junto con la zona (en los tipos se quita: ver\n// scripts/stripStyleImportsFromTypes.js)\nimport 'filepond/dist/filepond.min.css';\n\nimport ModalWaiting from '@/feedback/ModalWaiting.jsx';\nimport {toastError} from '@/feedback/toast.jsx';\nimport {accentTextStylesOf} from '@/theme/glassSurface.js';\nimport {useFormatters} from '@/toolkit/useFormatters.js';\nimport {FILE_REJECTION, parseFileSize, resolveAccept} from '@/upload/fileRules.js';\n\nregisterPlugin(FilePondPluginFileValidateType, FilePondPluginFileValidateSize);\n\n// cuánto se ve el error de un archivo de tipo no permitido antes de quitarse solo: lo suficiente para leer por qué\nconst AUTO_REJECT_DELAY_MILLISECONDS = 1800;\n// el aviso de FilePond cuando se sueltan más archivos de los permitidos\nconst MAX_FILES_WARNING = 'max files';\n\n/**\n * El tipo de un archivo para el complemento de tipos: el paquete habla de EXTENSIONES (.xlsx) y FilePond, por omisión,\n * del tipo MIME. Imagen, audio y video van por su tipo (para que 'image/*' funcione); lo demás, por su extensión.\n *\n * @param {File} file\n * @returns {Promise<string>}\n */\nfunction detectFileType(file) {\n const mimeType = (file.type ?? '').toLowerCase();\n if (/^(image|audio|video)\\//.test(mimeType)) {\n return Promise.resolve(mimeType);\n }\n const dotPosition = file.name.lastIndexOf('.');\n return Promise.resolve(dotPosition >= 0 ? file.name.slice(dotPosition).toLowerCase() : mimeType);\n}\n\n/**\n * ¿Los mismos archivos en el mismo orden?\n *\n * @param {File[]} firstFiles\n * @param {File[]} secondFiles\n * @returns {boolean}\n */\nfunction sameFiles(firstFiles, secondFiles) {\n return firstFiles.length === secondFiles.length && firstFiles.every((file, position) => file === secondFiles[position]);\n}\n\n/**\n * value (File | File[] | null) como arreglo.\n *\n * @param {File|File[]|null} value\n * @param {boolean} multiple\n * @returns {File[]}\n */\nfunction filesOf(value, multiple) {\n if (multiple) {\n return Array.isArray(value) ? value : (value ? [value] : []);\n }\n return value ? [value] : [];\n}\n\n/**\n * Los textos que FilePond pinta dentro de cada tarjeta (vienen en inglés), desde los textos del paquete.\n *\n * El de «no se pudo subir» enseña el mensaje del servidor cuando lo hay (createPondServer manda el título del problema\n * RFC 9457): «No se pudo subir» a secas no le dice a la persona qué corregir.\n *\n * @param {(key: string) => string} t traductor de common\n * @returns {object} props de FilePond\n */\nfunction pondLabelsOf(t) {\n return {\n labelFileLoading: t('upload.pond.loading'),\n labelFileLoadError: t('upload.pond.loadError'),\n labelFileWaitingForSize: t('upload.pond.waitingForSize'),\n labelFileSizeNotAvailable: t('upload.pond.sizeNotAvailable'),\n labelInvalidField: t('upload.pond.invalidField'),\n labelFileProcessing: t('upload.pond.processing'),\n labelFileProcessingComplete: t('upload.pond.processingComplete'),\n labelFileProcessingAborted: t('upload.pond.processingAborted'),\n labelFileProcessingError: (error) => error?.body || t('upload.pond.processingError'),\n labelFileProcessingRevertError: t('upload.pond.revertError'),\n labelFileRemoveError: t('upload.pond.removeError'),\n labelTapToCancel: t('upload.pond.tapToCancel'),\n labelTapToRetry: t('upload.pond.tapToRetry'),\n labelTapToUndo: t('upload.pond.tapToUndo'),\n labelButtonRemoveItem: t('upload.pond.remove'),\n labelButtonAbortItemLoad: t('upload.pond.abortLoad'),\n labelButtonRetryItemLoad: t('upload.pond.retryLoad'),\n labelButtonAbortItemProcessing: t('upload.pond.abortProcessing'),\n labelButtonUndoItemProcessing: t('upload.pond.undoProcessing'),\n labelButtonRetryItemProcessing: t('upload.pond.retryProcessing'),\n labelButtonProcessItem: t('upload.pond.processItem'),\n };\n}\n\n/**\n * Los colores de FilePond desde el tema. El «& » los anida dentro de la caja: pesan más que los de la hoja de FilePond\n * aunque esa hoja cargue después.\n *\n * @param {object} theme tema de MUI (con o sin variables CSS)\n * @returns {object}\n */\nfunction pondThemeSxOf(theme) {\n const {palette} = theme.vars ?? theme;\n return {\n '& .filepond--root': {fontFamily: theme.typography.fontFamily, mb: 0},\n '& .filepond--panel-root': {bgcolor: palette.background.paper, border: `2px dashed ${palette.divider}`, borderRadius: 2},\n '& .filepond--drop-label': {color: palette.text.primary},\n // la liga «Elegir archivos»: del color del tema pero legible (accentTextStylesOf); el naranja tal cual se perdía en oscuro\n '& .filepond--label-action': {...accentTextStylesOf(theme), textDecorationColor: 'currentColor'},\n '& .filepond--item-panel': {bgcolor: palette.primary.main},\n '& .filepond--file, & .filepond--file-action-button': {color: palette.primary.contrastText},\n };\n}\n\n/**\n * @param {object} props\n * @param {File|File[]|null} [props.value]\n * @param {(value: File|File[]|null) => void} props.onChange\n * @param {string|string[]} [props.accept]\n * @param {number|string} [props.maxSize] por archivo\n * @param {boolean} [props.multiple]\n * @param {number} [props.maxFiles]\n * @param {number|string} [props.maxTotalSize]\n * @param {string} [props.name] nombre del campo; por omisión uno propio por zona (dos zonas no se pisan)\n * @param {string} [props.hint]\n * @param {string} [props.buttonLabel]\n * @param {boolean} [props.disabled]\n * @param {boolean} [props.busy]\n * @param {boolean|{title?: string, text?: string}} [props.busyModal]\n * @param {boolean} [props.autoReject]\n * @param {((file: File) => Promise<true|string>)|null} [props.validate]\n * @param {object|null} [props.server] createPondServer(...) o {process} propio\n * @param {boolean} [props.instantUpload]\n * @param {{current: object|null}|null} [props.apiRef]\n * @param {(progress: {done: number, total: number}) => void} [props.onProcessProgress]\n * @param {(file: File|null, reason: string, message?: string) => void} [props.onReject] reason de FILE_REJECTION\n * @param {import('react').ReactNode} [props.actions]\n * @param {import('react').ReactNode} [props.children]\n * @param {object} [props.sx]\n * @param {object} [props.texts] los textos que cambian, con la forma de `common` en los textos del paquete\n */\nexport default function FileDropzone({\n value = null, onChange, accept, maxSize, multiple = false, maxFiles, maxTotalSize, name, hint, buttonLabel, disabled = false,\n busy = false, busyModal = false, autoReject = true, validate = null, server = null, instantUpload = false, apiRef = null,\n onProcessProgress, onReject, actions, children, sx, texts,\n}) {\n const {t} = useToolkitTranslation('common', texts);\n const {formatFileSize} = useFormatters();\n const generatedId = useId();\n const pondName = name ?? `file-dropzone-${generatedId}`;\n // el candado: apagado o subiendo. Va también en cada allow* de FilePond, no solo en lo visual\n const isLocked = disabled || busy;\n\n const resolvedAccept = useMemo(() => resolveAccept(accept), [accept]);\n const acceptedTypes = useMemo(() => (resolvedAccept ? resolvedAccept.split(',') : null), [resolvedAccept]);\n const maxBytes = parseFileSize(maxSize);\n const maxTotalBytes = multiple ? parseFileSize(maxTotalSize) : null;\n const typeErrorLabel = t('upload.invalidTitle');\n const totalErrorLabel = t('upload.totalTooLargeShort');\n const files = useMemo(() => filesOf(value, multiple), [value, multiple]);\n\n const pondRef = useRef(null);\n // lo que las funciones fijas necesitan de afuera, siempre al día (ver la trampa del encabezado)\n const latest = useRef({});\n latest.current = {files, multiple, onChange, onReject, autoReject, validate, onProcessProgress, maxFiles, typeErrorLabel, totalErrorLabel, t};\n\n useEffect(() => {\n if (apiRef) {\n apiRef.current = pondRef.current;\n }\n });\n\n // value desde FilePond. Los archivos con error se quedan en pantalla (en rojo) pero no cuentan: con un archivo malo\n // el botón de enviar no se prende. Y un removeFiles() hecho desde aquí no debe avisar un cambio fantasma\n const syncFromPond = useCallback(() => {\n const {files: currentFiles, multiple: isMultiple, onChange: emitChange} = latest.current;\n const pondItems = pondRef.current?.getFiles?.() ?? [];\n const nextFiles = pondItems.filter((item) => item.status !== FileStatus.LOAD_ERROR).map((item) => item.file);\n if (isMultiple) {\n if (!sameFiles(nextFiles, currentFiles)) {\n emitChange(nextFiles);\n }\n return;\n }\n const nextFile = nextFiles[0] ?? null;\n if (nextFile !== (currentFiles[0] ?? null)) {\n emitChange(nextFile);\n }\n }, []);\n\n const handleError = useCallback((error, item) => {\n // el archivo acaba de quedar con error: se saca de value\n syncFromPond();\n const {onReject: reject, autoReject: shouldAutoReject, typeErrorLabel: typeLabel, totalErrorLabel: totalLabel} = latest.current;\n // el motivo se reconoce por el texto, que es nuestro: comparación estable\n let reason = FILE_REJECTION.size;\n if (error?.main === typeLabel) {\n reason = FILE_REJECTION.type;\n } else if (error?.main === totalLabel) {\n reason = FILE_REJECTION.total;\n }\n // el de tipo no permitido se va solo después de que se lea su error; el de peso se queda para saber cuál pesa de más\n if (reason === FILE_REJECTION.type && shouldAutoReject && item?.id) {\n setTimeout(() => {\n if (pondRef.current?.getFile?.(item.id)) {\n pondRef.current.removeFile(item.id);\n }\n }, AUTO_REJECT_DELAY_MILLISECONDS);\n }\n if (reject && item?.file) {\n reject(item.file, reason);\n }\n }, [syncFromPond]);\n\n // la revisión del contenido, ANTES de que el archivo entre a la lista\n const handleBeforeAdd = useCallback(async (item) => {\n const {validate: judge, onReject: reject, typeErrorLabel: fallbackMessage} = latest.current;\n if (!judge) {\n return true;\n }\n let verdict;\n try {\n verdict = await judge(item.file);\n } catch {\n verdict = false;\n }\n if (verdict === true) {\n return true;\n }\n const message = typeof verdict === 'string' ? verdict : fallbackMessage;\n if (reject) {\n reject(item.file, FILE_REJECTION.content, message);\n } else {\n toastError(`${item.file?.name}: ${message}`);\n }\n return false;\n }, []);\n\n // el «subidos X de N» del modo server: cada archivo que termina su subida avisa la cuenta\n const handleProcessed = useCallback(() => {\n const report = latest.current.onProcessProgress;\n if (!report) {\n return;\n }\n const pondItems = pondRef.current?.getFiles?.() ?? [];\n report({done: pondItems.filter((item) => item.status === FileStatus.PROCESSING_COMPLETE).length, total: pondItems.length});\n }, []);\n\n // lo que pasa de maxFiles ni siquiera entra (FilePond corta lo soltado y solo avisa): el aviso lo damos nosotros\n const handleWarning = useCallback((warning) => {\n const {maxFiles: maxFileCount, onReject: reject, t: translate} = latest.current;\n if (maxFileCount && String(warning?.body ?? '').toLowerCase().includes(MAX_FILES_WARNING)) {\n if (reject) {\n reject(null, FILE_REJECTION.count);\n } else {\n toastError(translate('upload.tooManyFiles', {count: maxFileCount}));\n }\n }\n }, []);\n\n // limpiar desde afuera: value vacío (p. ej. después de subir bien) vacía la zona\n useEffect(() => {\n const pondItems = pondRef.current?.getFiles?.() ?? [];\n if (files.length === 0 && pondItems.length > 0) {\n pondRef.current.removeFiles();\n }\n }, [files]);\n\n const limitHints = [\n maxBytes && t('upload.maxSizeHint', {max: formatFileSize(maxBytes)}),\n maxTotalBytes && t('upload.maxTotalSizeHint', {max: formatFileSize(maxTotalBytes)}),\n multiple && maxFiles && t('upload.maxFilesHint', {count: maxFiles}),\n ].filter(Boolean);\n // con varios archivos se dice cuántos van y cuánto pesan juntos: nadie suma tarjeta por tarjeta\n const totalBytes = multiple ? files.reduce((sum, file) => sum + (file?.size ?? 0), 0) : 0;\n const hintText = hint ?? (multiple ? t('upload.hintMultiple') : t('upload.hint'));\n const chooseText = buttonLabel ?? (multiple ? t('upload.chooseMultiple') : t('upload.choose'));\n\n return (\n <Box sx={[(theme) => pondThemeSxOf(theme), ...(Array.isArray(sx) ? sx : [sx])]}>\n <FilePond\n ref={pondRef}\n name={pondName}\n server={server}\n instantUpload={instantUpload}\n beforeAddFile={handleBeforeAdd}\n onprocessfile={handleProcessed}\n onupdatefiles={syncFromPond}\n allowMultiple={multiple}\n maxFiles={multiple ? (maxFiles ?? null) : 1}\n disabled={isLocked}\n allowDrop={!isLocked}\n allowBrowse={!isLocked}\n allowPaste={!isLocked}\n allowRemove={!isLocked}\n allowReplace={!isLocked}\n credits={false}\n acceptedFileTypes={acceptedTypes}\n fileValidateTypeDetectType={detectFileType}\n fileValidateTypeLabelExpectedTypes={resolvedAccept ?? ''}\n maxFileSize={maxBytes ? String(maxBytes) : null}\n maxTotalFileSize={maxTotalBytes ? String(maxTotalBytes) : null}\n labelIdle={`${hintText} — <span class=\"filepond--label-action\">${chooseText}</span>`}\n labelFileTypeNotAllowed={typeErrorLabel}\n labelMaxFileSizeExceeded={t('upload.tooLargeShort')}\n labelMaxFileSize={maxBytes ? t('upload.maxSizeHint', {max: formatFileSize(maxBytes)}) : ''}\n labelMaxTotalFileSizeExceeded={totalErrorLabel}\n labelMaxTotalFileSize={maxTotalBytes ? t('upload.maxTotalSizeHint', {max: formatFileSize(maxTotalBytes)}) : ''}\n onwarning={handleWarning}\n onerror={handleError}\n {...pondLabelsOf(t)}\n />\n {multiple && files.length > 0 && (\n <Typography variant=\"body2\" sx={{textAlign: 'center', mt: 1, fontWeight: 500}}>\n {maxTotalBytes\n ? t('upload.summaryOf', {count: files.length, total: formatFileSize(totalBytes), max: formatFileSize(maxTotalBytes)})\n : t('upload.summary', {count: files.length, total: formatFileSize(totalBytes)})}\n </Typography>\n )}\n {limitHints.length > 0 && (\n <Typography variant=\"caption\" color=\"text.secondary\" sx={{display: 'block', textAlign: 'center', mt: 0.5}}>\n {limitHints.join(' · ')}\n </Typography>\n )}\n {actions && (\n <Stack direction={{xs: 'column', sm: 'row'}} spacing={2} sx={{justifyContent: 'center', alignItems: {xs: 'stretch', sm: 'center'}, mt: 2}}>\n {actions}\n </Stack>\n )}\n {children && <Box sx={{mt: 3}}>{children}</Box>}\n {busyModal && <ModalWaiting open={busy} title={busyModal.title} text={busyModal.text} />}\n </Box>\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"FileDropzone.js","sources":["../../src/filepond/FileDropzone.jsx"],"sourcesContent":["// La zona para soltar archivos con FilePond por dentro (react-filepond): el MISMO uso que la de MUI (FileDropzone de la\n// puerta principal) más varios archivos, animaciones, el error dentro de cada archivo y, si se quiere, la subida de cada\n// archivo con su propia barra de avance (server). Cambiar de una a otra es cambiar el import.\n//\n// Solo ELIGE archivos (controlado: value / onChange). Enviarlos es de la pantalla: arma su FormData y los sube\n// (uploadWithProgress + uploadMachine). Huecos: actions (botones abajo) y children (lo que va debajo).\n//\n// Configuración, además de la de la zona de MUI (accept, maxSize, hint, buttonLabel, disabled, busy, busyModal, onReject):\n// multiple varios archivos: value / onChange hablan en ARREGLOS de File\n// maxFiles tope de archivos (solo con multiple)\n// maxTotalSize tope de lo que pesan todos juntos (solo con multiple), con unidades como maxSize\n// validate revisión del CONTENIDO al entrar: async (file) => true | 'mensaje de rechazo' (un .xml bien formado\n// puede no ser una factura); lo que no pasa no entra a la lista\n// server subir cada archivo por su cuenta con su barra (createPondServer, o un process propio)\n// instantUpload con server, sube al soltar (por omisión NO: regla de la casa, sube cuando la persona oprime el botón,\n// que llama apiRef.current.processFiles())\n// apiRef la API de FilePond para ese botón (processFiles, getFiles, removeFiles…). OJO: processFiles() regresa\n// una promesa que FALLA si algún archivo no subió; el porqué ya se ve en su tarjeta, pero hay que\n// atraparla (.catch) o queda un error suelto en la consola\n// onProcessProgress({done, total}) el «subidos 2 de 5» del modo server\n// autoReject (por omisión sí) lo que no es de un tipo permitido enseña su error un momento y se quita solo\n//\n// OJO, trampa pagada (react-filepond#198): react-filepond no siempre refresca sus funciones después de montar, y puede\n// quedarse para siempre con la del primer render: quitar el archivo no apagaba el botón porque se comparaba contra un\n// value viejo. Blindaje: las funciones que recibe FilePond no cambian nunca (useCallback sin dependencias) y todo lo que\n// necesitan de afuera lo leen de UN ref que cada render vuelve a apuntar (latest).\n//\n// OJO: FilePond va NO controlado. Pasarle los archivos como prop revive archivos recién quitados; la verdad es FilePond y\n// value es su espejo. Lo único que se manda de afuera hacia adentro es LIMPIAR (value vacío vacía la zona).\n//\n// Los colores salen del tema (claro y oscuro) con selectores que le ganan a la hoja de FilePond sin importar el orden.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaPondDropzone con defaultProps y\n// styleOverrides de sus partes: root, summary, limits, actions, extra.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useCallback, useEffect, useId, useMemo, useRef} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport Box from '@mui/material/Box';\nimport Stack from '@mui/material/Stack';\nimport Typography from '@mui/material/Typography';\nimport {FileStatus} from 'filepond';\nimport FilePondPluginFileValidateSize from 'filepond-plugin-file-validate-size';\nimport FilePondPluginFileValidateType from 'filepond-plugin-file-validate-type';\nimport {FilePond, registerPlugin} from 'react-filepond';\n// la hoja de FilePond la carga el empaquetador del proyecto junto con la zona (en los tipos se quita: ver\n// scripts/stripStyleImportsFromTypes.js)\nimport 'filepond/dist/filepond.min.css';\n\nimport ModalWaiting from '@/feedback/ModalWaiting.jsx';\nimport {toastError} from '@/feedback/toast.jsx';\nimport {accentTextStylesOf} from '@/theme/glassSurface.js';\nimport {useFormatters} from '@/toolkit/useFormatters.js';\nimport {FILE_REJECTION, parseFileSize, resolveAccept} from '@/upload/fileRules.js';\nimport {piecePart, usePieceProps} from '@/theme/themePieces.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaPondDropzone)\nconst THEME_NAME = 'CalcitaPondDropzone';\n// la raíz: el estilo de FilePond de Calcita abajo, el tema del proyecto encima y el sx de la pantalla al final\nconst PondRoot = piecePart(THEME_NAME, 'Root', Box);\nconst PondSummary = piecePart(THEME_NAME, 'Summary', Typography);\nconst PondLimits = piecePart(THEME_NAME, 'Limits', Typography);\nconst PondActions = piecePart(THEME_NAME, 'Actions', Stack);\nconst PondExtra = piecePart(THEME_NAME, 'Extra', Box);\n\nregisterPlugin(FilePondPluginFileValidateType, FilePondPluginFileValidateSize);\n\n// cuánto se ve el error de un archivo de tipo no permitido antes de quitarse solo: lo suficiente para leer por qué\nconst AUTO_REJECT_DELAY_MILLISECONDS = 1800;\n// el aviso de FilePond cuando se sueltan más archivos de los permitidos\nconst MAX_FILES_WARNING = 'max files';\n\n/**\n * El tipo de un archivo para el complemento de tipos: el paquete habla de EXTENSIONES (.xlsx) y FilePond, por omisión,\n * del tipo MIME. Imagen, audio y video van por su tipo (para que 'image/*' funcione); lo demás, por su extensión.\n *\n * @param {File} file\n * @returns {Promise<string>}\n */\nfunction detectFileType(file) {\n const mimeType = (file.type ?? '').toLowerCase();\n if (/^(image|audio|video)\\//.test(mimeType)) {\n return Promise.resolve(mimeType);\n }\n const dotPosition = file.name.lastIndexOf('.');\n return Promise.resolve(dotPosition >= 0 ? file.name.slice(dotPosition).toLowerCase() : mimeType);\n}\n\n/**\n * ¿Los mismos archivos en el mismo orden?\n *\n * @param {File[]} firstFiles\n * @param {File[]} secondFiles\n * @returns {boolean}\n */\nfunction sameFiles(firstFiles, secondFiles) {\n return firstFiles.length === secondFiles.length && firstFiles.every((file, position) => file === secondFiles[position]);\n}\n\n/**\n * value (File | File[] | null) como arreglo.\n *\n * @param {File|File[]|null} value\n * @param {boolean} multiple\n * @returns {File[]}\n */\nfunction filesOf(value, multiple) {\n if (multiple) {\n return Array.isArray(value) ? value : (value ? [value] : []);\n }\n return value ? [value] : [];\n}\n\n/**\n * Los textos que FilePond pinta dentro de cada tarjeta (vienen en inglés), desde los textos del paquete.\n *\n * El de «no se pudo subir» enseña el mensaje del servidor cuando lo hay (createPondServer manda el título del problema\n * RFC 9457): «No se pudo subir» a secas no le dice a la persona qué corregir.\n *\n * @param {(key: string) => string} t traductor de common\n * @returns {object} props de FilePond\n */\nfunction pondLabelsOf(t) {\n return {\n labelFileLoading: t('upload.pond.loading'),\n labelFileLoadError: t('upload.pond.loadError'),\n labelFileWaitingForSize: t('upload.pond.waitingForSize'),\n labelFileSizeNotAvailable: t('upload.pond.sizeNotAvailable'),\n labelInvalidField: t('upload.pond.invalidField'),\n labelFileProcessing: t('upload.pond.processing'),\n labelFileProcessingComplete: t('upload.pond.processingComplete'),\n labelFileProcessingAborted: t('upload.pond.processingAborted'),\n labelFileProcessingError: (error) => error?.body || t('upload.pond.processingError'),\n labelFileProcessingRevertError: t('upload.pond.revertError'),\n labelFileRemoveError: t('upload.pond.removeError'),\n labelTapToCancel: t('upload.pond.tapToCancel'),\n labelTapToRetry: t('upload.pond.tapToRetry'),\n labelTapToUndo: t('upload.pond.tapToUndo'),\n labelButtonRemoveItem: t('upload.pond.remove'),\n labelButtonAbortItemLoad: t('upload.pond.abortLoad'),\n labelButtonRetryItemLoad: t('upload.pond.retryLoad'),\n labelButtonAbortItemProcessing: t('upload.pond.abortProcessing'),\n labelButtonUndoItemProcessing: t('upload.pond.undoProcessing'),\n labelButtonRetryItemProcessing: t('upload.pond.retryProcessing'),\n labelButtonProcessItem: t('upload.pond.processItem'),\n };\n}\n\n/**\n * Los colores de FilePond desde el tema. El «& » los anida dentro de la caja: pesan más que los de la hoja de FilePond\n * aunque esa hoja cargue después.\n *\n * @param {object} theme tema de MUI (con o sin variables CSS)\n * @returns {object}\n */\nfunction pondThemeSxOf(theme) {\n const {palette} = theme.vars ?? theme;\n return {\n '& .filepond--root': {fontFamily: theme.typography.fontFamily, mb: 0},\n '& .filepond--panel-root': {bgcolor: palette.background.paper, border: `2px dashed ${palette.divider}`, borderRadius: 2},\n '& .filepond--drop-label': {color: palette.text.primary},\n // la liga «Elegir archivos»: del color del tema pero legible (accentTextStylesOf); el naranja tal cual se perdía en oscuro\n '& .filepond--label-action': {...accentTextStylesOf(theme), textDecorationColor: 'currentColor'},\n '& .filepond--item-panel': {bgcolor: palette.primary.main},\n '& .filepond--file, & .filepond--file-action-button': {color: palette.primary.contrastText},\n };\n}\n\n/**\n * @param {object} props\n * @param {File|File[]|null} [props.value]\n * @param {(value: File|File[]|null) => void} props.onChange\n * @param {string|string[]} [props.accept]\n * @param {number|string} [props.maxSize] por archivo\n * @param {boolean} [props.multiple]\n * @param {number} [props.maxFiles]\n * @param {number|string} [props.maxTotalSize]\n * @param {string} [props.name] nombre del campo; por omisión uno propio por zona (dos zonas no se pisan)\n * @param {string} [props.hint]\n * @param {string} [props.buttonLabel]\n * @param {boolean} [props.disabled]\n * @param {boolean} [props.busy]\n * @param {boolean|{title?: string, text?: string}} [props.busyModal]\n * @param {boolean} [props.autoReject]\n * @param {((file: File) => Promise<true|string>)|null} [props.validate]\n * @param {object|null} [props.server] createPondServer(...) o {process} propio\n * @param {boolean} [props.instantUpload]\n * @param {{current: object|null}|null} [props.apiRef]\n * @param {(progress: {done: number, total: number}) => void} [props.onProcessProgress]\n * @param {(file: File|null, reason: string, message?: string) => void} [props.onReject] reason de FILE_REJECTION\n * @param {import('react').ReactNode} [props.actions]\n * @param {import('react').ReactNode} [props.children]\n * @param {object} [props.sx]\n * @param {object} [props.texts] los textos que cambian, con la forma de `common` en los textos del paquete\n */\nexport default function FileDropzone(props) {\n // lo que el proyecto puso en su tema (components.CalcitaPondDropzone.defaultProps) y encima lo que llega aquí\n const {\n value = null, onChange, accept, maxSize, multiple = false, maxFiles, maxTotalSize, name, hint, buttonLabel, disabled = false,\n busy = false, busyModal = false, autoReject = true, validate = null, server = null, instantUpload = false, apiRef = null,\n onProcessProgress, onReject, actions, children, sx, texts,\n} = usePieceProps(props, THEME_NAME);\n const {t} = useToolkitTranslation('common', texts);\n const {formatFileSize} = useFormatters();\n const generatedId = useId();\n const pondName = name ?? `file-dropzone-${generatedId}`;\n // el candado: apagado o subiendo. Va también en cada allow* de FilePond, no solo en lo visual\n const isLocked = disabled || busy;\n\n const resolvedAccept = useMemo(() => resolveAccept(accept), [accept]);\n const acceptedTypes = useMemo(() => (resolvedAccept ? resolvedAccept.split(',') : null), [resolvedAccept]);\n const maxBytes = parseFileSize(maxSize);\n const maxTotalBytes = multiple ? parseFileSize(maxTotalSize) : null;\n const typeErrorLabel = t('upload.invalidTitle');\n const totalErrorLabel = t('upload.totalTooLargeShort');\n const files = useMemo(() => filesOf(value, multiple), [value, multiple]);\n\n const pondRef = useRef(null);\n // lo que las funciones fijas necesitan de afuera, siempre al día (ver la trampa del encabezado)\n const latest = useRef({});\n latest.current = {files, multiple, onChange, onReject, autoReject, validate, onProcessProgress, maxFiles, typeErrorLabel, totalErrorLabel, t};\n\n useEffect(() => {\n if (apiRef) {\n apiRef.current = pondRef.current;\n }\n });\n\n // value desde FilePond. Los archivos con error se quedan en pantalla (en rojo) pero no cuentan: con un archivo malo\n // el botón de enviar no se prende. Y un removeFiles() hecho desde aquí no debe avisar un cambio fantasma\n const syncFromPond = useCallback(() => {\n const {files: currentFiles, multiple: isMultiple, onChange: emitChange} = latest.current;\n const pondItems = pondRef.current?.getFiles?.() ?? [];\n const nextFiles = pondItems.filter((item) => item.status !== FileStatus.LOAD_ERROR).map((item) => item.file);\n if (isMultiple) {\n if (!sameFiles(nextFiles, currentFiles)) {\n emitChange(nextFiles);\n }\n return;\n }\n const nextFile = nextFiles[0] ?? null;\n if (nextFile !== (currentFiles[0] ?? null)) {\n emitChange(nextFile);\n }\n }, []);\n\n const handleError = useCallback((error, item) => {\n // el archivo acaba de quedar con error: se saca de value\n syncFromPond();\n const {onReject: reject, autoReject: shouldAutoReject, typeErrorLabel: typeLabel, totalErrorLabel: totalLabel} = latest.current;\n // el motivo se reconoce por el texto, que es nuestro: comparación estable\n let reason = FILE_REJECTION.size;\n if (error?.main === typeLabel) {\n reason = FILE_REJECTION.type;\n } else if (error?.main === totalLabel) {\n reason = FILE_REJECTION.total;\n }\n // el de tipo no permitido se va solo después de que se lea su error; el de peso se queda para saber cuál pesa de más\n if (reason === FILE_REJECTION.type && shouldAutoReject && item?.id) {\n setTimeout(() => {\n if (pondRef.current?.getFile?.(item.id)) {\n pondRef.current.removeFile(item.id);\n }\n }, AUTO_REJECT_DELAY_MILLISECONDS);\n }\n if (reject && item?.file) {\n reject(item.file, reason);\n }\n }, [syncFromPond]);\n\n // la revisión del contenido, ANTES de que el archivo entre a la lista\n const handleBeforeAdd = useCallback(async (item) => {\n const {validate: judge, onReject: reject, typeErrorLabel: fallbackMessage} = latest.current;\n if (!judge) {\n return true;\n }\n let verdict;\n try {\n verdict = await judge(item.file);\n } catch {\n verdict = false;\n }\n if (verdict === true) {\n return true;\n }\n const message = typeof verdict === 'string' ? verdict : fallbackMessage;\n if (reject) {\n reject(item.file, FILE_REJECTION.content, message);\n } else {\n toastError(`${item.file?.name}: ${message}`);\n }\n return false;\n }, []);\n\n // el «subidos X de N» del modo server: cada archivo que termina su subida avisa la cuenta\n const handleProcessed = useCallback(() => {\n const report = latest.current.onProcessProgress;\n if (!report) {\n return;\n }\n const pondItems = pondRef.current?.getFiles?.() ?? [];\n report({done: pondItems.filter((item) => item.status === FileStatus.PROCESSING_COMPLETE).length, total: pondItems.length});\n }, []);\n\n // lo que pasa de maxFiles ni siquiera entra (FilePond corta lo soltado y solo avisa): el aviso lo damos nosotros\n const handleWarning = useCallback((warning) => {\n const {maxFiles: maxFileCount, onReject: reject, t: translate} = latest.current;\n if (maxFileCount && String(warning?.body ?? '').toLowerCase().includes(MAX_FILES_WARNING)) {\n if (reject) {\n reject(null, FILE_REJECTION.count);\n } else {\n toastError(translate('upload.tooManyFiles', {count: maxFileCount}));\n }\n }\n }, []);\n\n // limpiar desde afuera: value vacío (p. ej. después de subir bien) vacía la zona\n useEffect(() => {\n const pondItems = pondRef.current?.getFiles?.() ?? [];\n if (files.length === 0 && pondItems.length > 0) {\n pondRef.current.removeFiles();\n }\n }, [files]);\n\n const limitHints = [\n maxBytes && t('upload.maxSizeHint', {max: formatFileSize(maxBytes)}),\n maxTotalBytes && t('upload.maxTotalSizeHint', {max: formatFileSize(maxTotalBytes)}),\n multiple && maxFiles && t('upload.maxFilesHint', {count: maxFiles}),\n ].filter(Boolean);\n // con varios archivos se dice cuántos van y cuánto pesan juntos: nadie suma tarjeta por tarjeta\n const totalBytes = multiple ? files.reduce((sum, file) => sum + (file?.size ?? 0), 0) : 0;\n const hintText = hint ?? (multiple ? t('upload.hintMultiple') : t('upload.hint'));\n const chooseText = buttonLabel ?? (multiple ? t('upload.chooseMultiple') : t('upload.choose'));\n\n return (\n <PondRoot ownSx={(theme) => pondThemeSxOf(theme)} sx={sx}>\n <FilePond\n ref={pondRef}\n name={pondName}\n server={server}\n instantUpload={instantUpload}\n beforeAddFile={handleBeforeAdd}\n onprocessfile={handleProcessed}\n onupdatefiles={syncFromPond}\n allowMultiple={multiple}\n maxFiles={multiple ? (maxFiles ?? null) : 1}\n disabled={isLocked}\n allowDrop={!isLocked}\n allowBrowse={!isLocked}\n allowPaste={!isLocked}\n allowRemove={!isLocked}\n allowReplace={!isLocked}\n credits={false}\n acceptedFileTypes={acceptedTypes}\n fileValidateTypeDetectType={detectFileType}\n fileValidateTypeLabelExpectedTypes={resolvedAccept ?? ''}\n maxFileSize={maxBytes ? String(maxBytes) : null}\n maxTotalFileSize={maxTotalBytes ? String(maxTotalBytes) : null}\n labelIdle={`${hintText} — <span class=\"filepond--label-action\">${chooseText}</span>`}\n labelFileTypeNotAllowed={typeErrorLabel}\n labelMaxFileSizeExceeded={t('upload.tooLargeShort')}\n labelMaxFileSize={maxBytes ? t('upload.maxSizeHint', {max: formatFileSize(maxBytes)}) : ''}\n labelMaxTotalFileSizeExceeded={totalErrorLabel}\n labelMaxTotalFileSize={maxTotalBytes ? t('upload.maxTotalSizeHint', {max: formatFileSize(maxTotalBytes)}) : ''}\n onwarning={handleWarning}\n onerror={handleError}\n {...pondLabelsOf(t)}\n />\n {multiple && files.length > 0 && (\n <PondSummary variant=\"body2\" ownSx={{textAlign: 'center', mt: 1, fontWeight: 500}}>\n {maxTotalBytes\n ? t('upload.summaryOf', {count: files.length, total: formatFileSize(totalBytes), max: formatFileSize(maxTotalBytes)})\n : t('upload.summary', {count: files.length, total: formatFileSize(totalBytes)})}\n </PondSummary>\n )}\n {limitHints.length > 0 && (\n <PondLimits variant=\"caption\" color=\"text.secondary\" ownSx={{display: 'block', textAlign: 'center', mt: 0.5}}>\n {limitHints.join(' · ')}\n </PondLimits>\n )}\n {actions && (\n <PondActions direction={{xs: 'column', sm: 'row'}} spacing={2} ownSx={{justifyContent: 'center', alignItems: {xs: 'stretch', sm: 'center'}, mt: 2}}>\n {actions}\n </PondActions>\n )}\n {children && <PondExtra ownSx={{mt: 3}}>{children}</PondExtra>}\n {busyModal && <ModalWaiting open={busy} title={busyModal.title} text={busyModal.text} />}\n </PondRoot>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -2,12 +2,15 @@
2
2
  import { jsx } from "react/jsx-runtime";
3
3
  import { useFormikContext } from "formik";
4
4
  import Alert from "@mui/material/Alert";
5
+ import { piecePart } from "../theme/themePieces.js";
6
+ const THEME_NAME = "CalcitaFormProblemAlert";
7
+ const ProblemAlert = piecePart(THEME_NAME, "Root", Alert);
5
8
  function FormProblemAlert({ sx }) {
6
9
  const { status } = useFormikContext();
7
10
  if (!status) {
8
11
  return null;
9
12
  }
10
- return /* @__PURE__ */ jsx(Alert, { severity: "error", role: "alert", sx, children: status });
13
+ return /* @__PURE__ */ jsx(ProblemAlert, { severity: "error", role: "alert", sx, children: status });
11
14
  }
12
15
  export {
13
16
  FormProblemAlert as default
@@ -1 +1 @@
1
- {"version":3,"file":"FormProblemAlert.js","sources":["../../src/forms/FormProblemAlert.jsx"],"sourcesContent":["// El aviso de arriba del formulario de formik: lo que el servidor dijo que no es de un campo (sin conexión, un 409, un 500)\n// o el error de un campo que el formulario no tiene. applyServerProblem lo deja en el status de formik; aquí se pinta.\n//\n// Sin mensaje no pinta nada ni ocupa lugar.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useFormikContext} from 'formik';\nimport Alert from '@mui/material/Alert';\n\n/**\n * @param {{sx?: object}} props\n */\nexport default function FormProblemAlert({sx}) {\n const {status} = useFormikContext();\n\n // guard clause: sin aviso del servidor\n if (!status) {\n return null;\n }\n return <Alert severity=\"error\" role=\"alert\" sx={sx}>{status}</Alert>;\n}\n"],"names":[],"mappings":";;;;AAYA,SAAwB,iBAAiB,EAAC,MAAK;AAC3C,QAAM,EAAC,OAAA,IAAU,iBAAA;AAGjB,MAAI,CAAC,QAAQ;AACT,WAAO;AAAA,EACX;AACA,6BAAQ,OAAA,EAAM,UAAS,SAAQ,MAAK,SAAQ,IAAS,UAAA,QAAO;AAChE;"}
1
+ {"version":3,"file":"FormProblemAlert.js","sources":["../../src/forms/FormProblemAlert.jsx"],"sourcesContent":["// El aviso de arriba del formulario de formik: lo que el servidor dijo que no es de un campo (sin conexión, un 409, un 500)\n// o el error de un campo que el formulario no tiene. applyServerProblem lo deja en el status de formik; aquí se pinta.\n//\n// Sin mensaje no pinta nada ni ocupa lugar.\n//\n// Se cambia desde el tema del proyecto: components.CalcitaFormProblemAlert con styleOverrides.root.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useFormikContext} from 'formik';\nimport Alert from '@mui/material/Alert';\nimport {piecePart} from '@/theme/themePieces.js';\n\n// el nombre de la pieza en el tema y su parte (theme.components.CalcitaFormProblemAlert): el estilo del proyecto va\n// después del de MUI y el sx de la pantalla al final\nconst THEME_NAME = 'CalcitaFormProblemAlert';\nconst ProblemAlert = piecePart(THEME_NAME, 'Root', Alert);\n\n/**\n * @param {{sx?: object}} props\n */\nexport default function FormProblemAlert({sx}) {\n const {status} = useFormikContext();\n\n // guard clause: sin aviso del servidor\n if (!status) {\n return null;\n }\n return <ProblemAlert severity=\"error\" role=\"alert\" sx={sx}>{status}</ProblemAlert>;\n}\n"],"names":[],"mappings":";;;;;AAcA,MAAM,aAAa;AACnB,MAAM,eAAe,UAAU,YAAY,QAAQ,KAAK;AAKxD,SAAwB,iBAAiB,EAAC,MAAK;AAC3C,QAAM,EAAC,OAAA,IAAU,iBAAA;AAGjB,MAAI,CAAC,QAAQ;AACT,WAAO;AAAA,EACX;AACA,6BAAQ,cAAA,EAAa,UAAS,SAAQ,MAAK,SAAQ,IAAS,UAAA,QAAO;AACvE;"}
@@ -13,11 +13,15 @@ import FormTextField from "../FormTextField.js";
13
13
  import SubmitButton from "../SubmitButton.js";
14
14
  import { applyServerProblem } from "../applyServerProblem.js";
15
15
  import * as Yup from "yup";
16
+ import { piecePart, usePieceProps } from "../../theme/themePieces.js";
17
+ const THEME_NAME = "CalcitaSignInForm";
18
+ const SignInFormFooter = piecePart(THEME_NAME, "Footer", Stack);
16
19
  function RememberMeField({ label }) {
17
20
  const [field] = useField({ name: "remember", type: "checkbox" });
18
21
  return /* @__PURE__ */ jsx(FormControlLabel, { label, control: /* @__PURE__ */ jsx(Checkbox, { ...field }) });
19
22
  }
20
- function SignInForm({ onSubmit, forgotPasswordPath, showRememberMe = true, initialEmail = "", texts }) {
23
+ function SignInForm(props) {
24
+ const { onSubmit, forgotPasswordPath, showRememberMe = true, initialEmail = "", texts } = usePieceProps(props, THEME_NAME);
21
25
  const { t } = useToolkitTranslation("auth", texts);
22
26
  const { Link: RouterLink } = useToolkitNavigation();
23
27
  const schema = useMemo(() => Yup.object({
@@ -35,7 +39,7 @@ function SignInForm({ onSubmit, forgotPasswordPath, showRememberMe = true, initi
35
39
  /* @__PURE__ */ jsx(FormProblemAlert, {}),
36
40
  /* @__PURE__ */ jsx(FormTextField, { name: "email", label: t("signIn.email"), type: "email", autoComplete: "username", autoFocus: true }),
37
41
  /* @__PURE__ */ jsx(FormTextField, { name: "password", label: t("signIn.password"), type: "password", autoComplete: "current-password" }),
38
- /* @__PURE__ */ jsxs(Stack, { direction: "row", sx: { alignItems: "center", justifyContent: "space-between", flexWrap: "wrap", gap: 1 }, children: [
42
+ /* @__PURE__ */ jsxs(SignInFormFooter, { direction: "row", ownSx: { alignItems: "center", justifyContent: "space-between", flexWrap: "wrap", gap: 1 }, children: [
39
43
  showRememberMe ? /* @__PURE__ */ jsx(RememberMeField, { label: t("signIn.remember") }) : /* @__PURE__ */ jsx("span", {}),
40
44
  forgotPasswordPath && /* @__PURE__ */ jsx(Link, { component: RouterLink, to: forgotPasswordPath, variant: "body2", children: t("signIn.forgot") })
41
45
  ] }),
@@ -1 +1 @@
1
- {"version":3,"file":"SignInForm.js","sources":["../../../src/forms/auth/SignInForm.jsx"],"sourcesContent":["// El formulario de iniciar sesión (formik): correo, contraseña, «mantener la sesión abierta» y la liga a «¿Olvidó su\n// contraseña?». La pantalla solo dice qué hacer con los datos:\n//\n// <AuthLayout title={t('auth:signIn.title')}>\n// <SignInForm onSubmit={(values) => signIn(values)} forgotPasswordPath=\"/forgot-password\" />\n// </AuthLayout>\n//\n// Si onSubmit truena con el problema del servidor (RFC 9457; un 401 «correo o contraseña incorrectos», un 429 «espere\n// un minuto»), el aviso sale arriba del formulario y lo de cada campo, en su campo (applyServerProblem).\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useMemo} from 'react';\nimport {Form, Formik, useField} from 'formik';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {useToolkitNavigation} from '@/toolkit/toolkitContext.js';\nimport Checkbox from '@mui/material/Checkbox';\nimport FormControlLabel from '@mui/material/FormControlLabel';\nimport Link from '@mui/material/Link';\nimport Stack from '@mui/material/Stack';\n\nimport FormProblemAlert from '@/forms/FormProblemAlert.jsx';\nimport FormTextField from '@/forms/FormTextField.jsx';\nimport SubmitButton from '@/forms/SubmitButton.jsx';\nimport {applyServerProblem} from '@/forms/applyServerProblem.js';\nimport {Yup} from '@/forms/validation.js';\n\n/** «Mantener la sesión abierta», conectado a formik. */\nfunction RememberMeField({label}) {\n const [field] = useField({name: 'remember', type: 'checkbox'});\n return <FormControlLabel label={label} control={<Checkbox {...field} />} />;\n}\n\n/**\n * @param {object} props\n * @param {(values: {email: string, password: string, remember: boolean}) => Promise<unknown>} props.onSubmit entra; si\n * el servidor dice que no, truena con su problema\n * @param {string} [props.forgotPasswordPath] sin ella no sale la liga de «¿Olvidó su contraseña?»\n * @param {boolean} [props.showRememberMe] por omisión sí\n * @param {string} [props.initialEmail]\n * @param {object} [props.texts] los textos que cambian, con la forma de `auth` en los textos del paquete\n */\nexport default function SignInForm({onSubmit, forgotPasswordPath, showRememberMe = true, initialEmail = '', texts}) {\n const {t} = useToolkitTranslation('auth', texts);\n const {Link: RouterLink} = useToolkitNavigation();\n // el esquema se arma al pintar y no al importar: yup toma sus mensajes al armarlo (buildYupLocale ya corrió)\n const schema = useMemo(() => Yup.object({\n email: Yup.string().trim().required().email(),\n password: Yup.string().required(),\n }), []);\n\n const submit = async (values, formikHelpers) => {\n try {\n await onSubmit({...values, email: values.email.trim()});\n } catch (error) {\n applyServerProblem(error, {...formikHelpers, values});\n }\n };\n\n return (\n <Formik initialValues={{email: initialEmail, password: '', remember: false}} validationSchema={schema} onSubmit={submit}>\n <Form noValidate>\n <Stack spacing={2}>\n <FormProblemAlert />\n <FormTextField name=\"email\" label={t('signIn.email')} type=\"email\" autoComplete=\"username\" autoFocus />\n <FormTextField name=\"password\" label={t('signIn.password')} type=\"password\" autoComplete=\"current-password\" />\n <Stack direction=\"row\" sx={{alignItems: 'center', justifyContent: 'space-between', flexWrap: 'wrap', gap: 1}}>\n {showRememberMe ? <RememberMeField label={t('signIn.remember')} /> : <span />}\n {forgotPasswordPath && (\n <Link component={RouterLink} to={forgotPasswordPath} variant=\"body2\">{t('signIn.forgot')}</Link>\n )}\n </Stack>\n <SubmitButton fullWidth>{t('signIn.submit')}</SubmitButton>\n </Stack>\n </Form>\n </Formik>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;AA2BA,SAAS,gBAAgB,EAAC,SAAQ;AAC9B,QAAM,CAAC,KAAK,IAAI,SAAS,EAAC,MAAM,YAAY,MAAM,YAAW;AAC7D,SAAO,oBAAC,oBAAiB,OAAc,6BAAU,UAAA,EAAU,GAAG,OAAO,GAAI;AAC7E;AAWA,SAAwB,WAAW,EAAC,UAAU,oBAAoB,iBAAiB,MAAM,eAAe,IAAI,SAAQ;AAChH,QAAM,EAAC,EAAA,IAAK,sBAAsB,QAAQ,KAAK;AAC/C,QAAM,EAAC,MAAM,WAAA,IAAc,qBAAA;AAE3B,QAAM,SAAS,QAAQ,MAAM,IAAI,OAAO;AAAA,IACpC,OAAO,IAAI,OAAA,EAAS,OAAO,SAAA,EAAW,MAAA;AAAA,IACtC,UAAU,IAAI,OAAA,EAAS,SAAA;AAAA,EAAS,CACnC,GAAG,CAAA,CAAE;AAEN,QAAM,SAAS,OAAO,QAAQ,kBAAkB;AAC5C,QAAI;AACA,YAAM,SAAS,EAAC,GAAG,QAAQ,OAAO,OAAO,MAAM,KAAA,GAAO;AAAA,IAC1D,SAAS,OAAO;AACZ,yBAAmB,OAAO,EAAC,GAAG,eAAe,QAAO;AAAA,IACxD;AAAA,EACJ;AAEA,SACI,oBAAC,UAAO,eAAe,EAAC,OAAO,cAAc,UAAU,IAAI,UAAU,MAAA,GAAQ,kBAAkB,QAAQ,UAAU,QAC7G,UAAA,oBAAC,MAAA,EAAK,YAAU,MACZ,UAAA,qBAAC,OAAA,EAAM,SAAS,GACZ,UAAA;AAAA,IAAA,oBAAC,kBAAA,EAAiB;AAAA,IAClB,oBAAC,eAAA,EAAc,MAAK,SAAQ,OAAO,EAAE,cAAc,GAAG,MAAK,SAAQ,cAAa,YAAW,WAAS,KAAA,CAAC;AAAA,IACrG,oBAAC,eAAA,EAAc,MAAK,YAAW,OAAO,EAAE,iBAAiB,GAAG,MAAK,YAAW,cAAa,mBAAA,CAAmB;AAAA,IAC5G,qBAAC,OAAA,EAAM,WAAU,OAAM,IAAI,EAAC,YAAY,UAAU,gBAAgB,iBAAiB,UAAU,QAAQ,KAAK,KACrG,UAAA;AAAA,MAAA,iBAAiB,oBAAC,mBAAgB,OAAO,EAAE,iBAAiB,GAAG,wBAAM,QAAA,CAAA,CAAK;AAAA,MAC1E,sBACG,oBAAC,MAAA,EAAK,WAAW,YAAY,IAAI,oBAAoB,SAAQ,SAAS,UAAA,EAAE,eAAe,EAAA,CAAE;AAAA,IAAA,GAEjG;AAAA,wBACC,cAAA,EAAa,WAAS,MAAE,UAAA,EAAE,eAAe,EAAA,CAAE;AAAA,EAAA,EAAA,CAChD,GACJ,GACJ;AAER;"}
1
+ {"version":3,"file":"SignInForm.js","sources":["../../../src/forms/auth/SignInForm.jsx"],"sourcesContent":["// El formulario de iniciar sesión (formik): correo, contraseña, «mantener la sesión abierta» y la liga a «¿Olvidó su\n// contraseña?». La pantalla solo dice qué hacer con los datos:\n//\n// <AuthLayout title={t('auth:signIn.title')}>\n// <SignInForm onSubmit={(values) => signIn(values)} forgotPasswordPath=\"/forgot-password\" />\n// </AuthLayout>\n//\n// Si onSubmit truena con el problema del servidor (RFC 9457; un 401 «correo o contraseña incorrectos», un 429 «espere\n// un minuto»), el aviso sale arriba del formulario y lo de cada campo, en su campo (applyServerProblem).\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaSignInForm con defaultProps y\n// styleOverrides de sus partes: footer.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useMemo} from 'react';\nimport {Form, Formik, useField} from 'formik';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {useToolkitNavigation} from '@/toolkit/toolkitContext.js';\nimport Checkbox from '@mui/material/Checkbox';\nimport FormControlLabel from '@mui/material/FormControlLabel';\nimport Link from '@mui/material/Link';\nimport Stack from '@mui/material/Stack';\n\nimport FormProblemAlert from '@/forms/FormProblemAlert.jsx';\nimport FormTextField from '@/forms/FormTextField.jsx';\nimport SubmitButton from '@/forms/SubmitButton.jsx';\nimport {applyServerProblem} from '@/forms/applyServerProblem.js';\nimport {Yup} from '@/forms/validation.js';\nimport {piecePart, usePieceProps} from '@/theme/themePieces.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaSignInForm)\nconst THEME_NAME = 'CalcitaSignInForm';\nconst SignInFormFooter = piecePart(THEME_NAME, 'Footer', Stack);\n\n/** «Mantener la sesión abierta», conectado a formik. */\nfunction RememberMeField({label}) {\n const [field] = useField({name: 'remember', type: 'checkbox'});\n return <FormControlLabel label={label} control={<Checkbox {...field} />} />;\n}\n\n/**\n * @param {object} props\n * @param {(values: {email: string, password: string, remember: boolean}) => Promise<unknown>} props.onSubmit entra; si\n * el servidor dice que no, truena con su problema\n * @param {string} [props.forgotPasswordPath] sin ella no sale la liga de «¿Olvidó su contraseña?»\n * @param {boolean} [props.showRememberMe] por omisión sí\n * @param {string} [props.initialEmail]\n * @param {object} [props.texts] los textos que cambian, con la forma de `auth` en los textos del paquete\n */\nexport default function SignInForm(props) {\n // lo que el proyecto puso en su tema (components.CalcitaSignInForm.defaultProps) y encima lo que llega aquí\n const {onSubmit, forgotPasswordPath, showRememberMe = true, initialEmail = '', texts} = usePieceProps(props, THEME_NAME);\n const {t} = useToolkitTranslation('auth', texts);\n const {Link: RouterLink} = useToolkitNavigation();\n // el esquema se arma al pintar y no al importar: yup toma sus mensajes al armarlo (buildYupLocale ya corrió)\n const schema = useMemo(() => Yup.object({\n email: Yup.string().trim().required().email(),\n password: Yup.string().required(),\n }), []);\n\n const submit = async (values, formikHelpers) => {\n try {\n await onSubmit({...values, email: values.email.trim()});\n } catch (error) {\n applyServerProblem(error, {...formikHelpers, values});\n }\n };\n\n return (\n <Formik initialValues={{email: initialEmail, password: '', remember: false}} validationSchema={schema} onSubmit={submit}>\n <Form noValidate>\n <Stack spacing={2}>\n <FormProblemAlert />\n <FormTextField name=\"email\" label={t('signIn.email')} type=\"email\" autoComplete=\"username\" autoFocus />\n <FormTextField name=\"password\" label={t('signIn.password')} type=\"password\" autoComplete=\"current-password\" />\n <SignInFormFooter direction=\"row\" ownSx={{alignItems: 'center', justifyContent: 'space-between', flexWrap: 'wrap', gap: 1}}>\n {showRememberMe ? <RememberMeField label={t('signIn.remember')} /> : <span />}\n {forgotPasswordPath && (\n <Link component={RouterLink} to={forgotPasswordPath} variant=\"body2\">{t('signIn.forgot')}</Link>\n )}\n </SignInFormFooter>\n <SubmitButton fullWidth>{t('signIn.submit')}</SubmitButton>\n </Stack>\n </Form>\n </Formik>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;AA+BA,MAAM,aAAa;AACnB,MAAM,mBAAmB,UAAU,YAAY,UAAU,KAAK;AAG9D,SAAS,gBAAgB,EAAC,SAAQ;AAC9B,QAAM,CAAC,KAAK,IAAI,SAAS,EAAC,MAAM,YAAY,MAAM,YAAW;AAC7D,SAAO,oBAAC,oBAAiB,OAAc,6BAAU,UAAA,EAAU,GAAG,OAAO,GAAI;AAC7E;AAWA,SAAwB,WAAW,OAAO;AAEtC,QAAM,EAAC,UAAU,oBAAoB,iBAAiB,MAAM,eAAe,IAAI,MAAA,IAAS,cAAc,OAAO,UAAU;AACvH,QAAM,EAAC,EAAA,IAAK,sBAAsB,QAAQ,KAAK;AAC/C,QAAM,EAAC,MAAM,WAAA,IAAc,qBAAA;AAE3B,QAAM,SAAS,QAAQ,MAAM,IAAI,OAAO;AAAA,IACpC,OAAO,IAAI,OAAA,EAAS,OAAO,SAAA,EAAW,MAAA;AAAA,IACtC,UAAU,IAAI,OAAA,EAAS,SAAA;AAAA,EAAS,CACnC,GAAG,CAAA,CAAE;AAEN,QAAM,SAAS,OAAO,QAAQ,kBAAkB;AAC5C,QAAI;AACA,YAAM,SAAS,EAAC,GAAG,QAAQ,OAAO,OAAO,MAAM,KAAA,GAAO;AAAA,IAC1D,SAAS,OAAO;AACZ,yBAAmB,OAAO,EAAC,GAAG,eAAe,QAAO;AAAA,IACxD;AAAA,EACJ;AAEA,SACI,oBAAC,UAAO,eAAe,EAAC,OAAO,cAAc,UAAU,IAAI,UAAU,MAAA,GAAQ,kBAAkB,QAAQ,UAAU,QAC7G,UAAA,oBAAC,MAAA,EAAK,YAAU,MACZ,UAAA,qBAAC,OAAA,EAAM,SAAS,GACZ,UAAA;AAAA,IAAA,oBAAC,kBAAA,EAAiB;AAAA,IAClB,oBAAC,eAAA,EAAc,MAAK,SAAQ,OAAO,EAAE,cAAc,GAAG,MAAK,SAAQ,cAAa,YAAW,WAAS,KAAA,CAAC;AAAA,IACrG,oBAAC,eAAA,EAAc,MAAK,YAAW,OAAO,EAAE,iBAAiB,GAAG,MAAK,YAAW,cAAa,mBAAA,CAAmB;AAAA,IAC5G,qBAAC,kBAAA,EAAiB,WAAU,OAAM,OAAO,EAAC,YAAY,UAAU,gBAAgB,iBAAiB,UAAU,QAAQ,KAAK,KACnH,UAAA;AAAA,MAAA,iBAAiB,oBAAC,mBAAgB,OAAO,EAAE,iBAAiB,GAAG,wBAAM,QAAA,CAAA,CAAK;AAAA,MAC1E,sBACG,oBAAC,MAAA,EAAK,WAAW,YAAY,IAAI,oBAAoB,SAAQ,SAAS,UAAA,EAAE,eAAe,EAAA,CAAE;AAAA,IAAA,GAEjG;AAAA,wBACC,cAAA,EAAa,WAAS,MAAE,UAAA,EAAE,eAAe,EAAA,CAAE;AAAA,EAAA,EAAA,CAChD,GACJ,GACJ;AAER;"}
@@ -16,13 +16,17 @@ import { applyServerProblem } from "../applyServerProblem.js";
16
16
  import * as Yup from "yup";
17
17
  import { TOTP_CODE_MIN_LENGTH, TOTP_CODE_MAX_LENGTH, TOTP_CODE_PATTERN, RECOVERY_CODE_MIN_LENGTH, RECOVERY_CODE_MAX_LENGTH, RECOVERY_CODE_PATTERN, normalizeTotpCode, normalizeRecoveryCode } from "./securityRules.js";
18
18
  import { useToolkitTranslation } from "../../toolkit/useToolkitTranslation.js";
19
+ import { piecePart, usePieceProps } from "../../theme/themePieces.js";
20
+ const THEME_NAME = "CalcitaSecondStepForm";
21
+ const SecondStepActions = piecePart(THEME_NAME, "Actions", Stack);
19
22
  const SECOND_STEP_METHOD = Object.freeze({ passkey: "passkey", totp: "totp", recoveryCode: "recoveryCode" });
20
23
  const UNAUTHENTICATED_STATUS = 401;
21
24
  const ACCOUNT_LOCKED_CODE = "account_locked";
22
25
  function isOutOfAttempts(error) {
23
26
  return error?.code === ACCOUNT_LOCKED_CODE || error?.status === UNAUTHENTICATED_STATUS && !Number.isInteger(error?.problem?.remaining_attempts);
24
27
  }
25
- function SecondStepForm({ methods, canUsePasskeys = false, onUsePasskey, onSubmitCode, onCancel, footer, texts }) {
28
+ function SecondStepForm(props) {
29
+ const { methods, canUsePasskeys = false, onUsePasskey, onSubmitCode, onCancel, footer, texts } = usePieceProps(props, THEME_NAME);
26
30
  const { t } = useToolkitTranslation("security", texts);
27
31
  const hasApp = methods.includes(SECOND_STEP_METHOD.totp);
28
32
  const hasRecovery = methods.includes(SECOND_STEP_METHOD.recoveryCode);
@@ -100,7 +104,7 @@ function SecondStepForm({ methods, canUsePasskeys = false, onUsePasskey, onSubmi
100
104
  footer
101
105
  ] }) }, method),
102
106
  !hasApp && !hasRecovery && footer,
103
- /* @__PURE__ */ jsxs(Stack, { spacing: 0.5, sx: { alignItems: "center" }, children: [
107
+ /* @__PURE__ */ jsxs(SecondStepActions, { spacing: 0.5, ownSx: { alignItems: "center" }, children: [
104
108
  hasApp && hasRecovery && /* @__PURE__ */ jsx(
105
109
  Link,
106
110
  {