@stackcraftmx/calcita 0.32.2 → 0.47.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +263 -2
- package/cli/packageVersions.js +12 -3
- package/cli/projectFiles.js +5 -2
- package/cli/templates/module/__Name__Page.test.jsx.tpl +24 -0
- package/cli/templates/project/README.md.tpl +14 -1
- package/cli/templates/project/src/app/appRouter.jsx.tpl +5 -2
- package/cli/templates/project/src/core/accountConnector.js.tpl +4 -2
- package/cli/templates/project/src/modules/auth/SignInPage.test.jsx.tpl +20 -0
- package/cli/templates/project/src/modules/home/HomePage.test.jsx.tpl +16 -0
- package/cli/templates/project/src/testing/browserGaps.js.tpl +70 -0
- package/cli/templates/project/src/testing/openScreen.jsx.tpl +58 -0
- package/cli/templates/project/src/testing/setup.js.tpl +25 -0
- package/cli/templates/project/vite.config.js.tpl +12 -0
- package/dist/DataTable/GoToPage.js +69 -13
- package/dist/DataTable/GoToPage.js.map +1 -1
- package/dist/DataTable/ServerTable.js +2 -0
- package/dist/DataTable/ServerTable.js.map +1 -1
- package/dist/DataTable/ServerTableFilterDrawer.js +52 -0
- package/dist/DataTable/ServerTableFilterDrawer.js.map +1 -0
- package/dist/DataTable/filterContext.js.map +1 -1
- package/dist/DataTable/goToPageOptions.js +17 -0
- package/dist/DataTable/goToPageOptions.js.map +1 -0
- package/dist/date-pickers.js +28 -2
- package/dist/date-pickers.js.map +1 -1
- package/dist/datePickers/DateInput.js +15 -34
- package/dist/datePickers/DateInput.js.map +1 -1
- package/dist/datePickers/DateTimeInput.js +75 -0
- package/dist/datePickers/DateTimeInput.js.map +1 -0
- package/dist/datePickers/FormDateTimeField.js +12 -0
- package/dist/datePickers/FormDateTimeField.js.map +1 -0
- package/dist/datePickers/FormMonthField.js +12 -0
- package/dist/datePickers/FormMonthField.js.map +1 -0
- package/dist/datePickers/FormTimeField.js +12 -0
- package/dist/datePickers/FormTimeField.js.map +1 -0
- package/dist/datePickers/MonthInput.js +58 -0
- package/dist/datePickers/MonthInput.js.map +1 -0
- package/dist/datePickers/PickerFrame.js +27 -0
- package/dist/datePickers/PickerFrame.js.map +1 -0
- package/dist/datePickers/TimeInput.js +55 -0
- package/dist/datePickers/TimeInput.js.map +1 -0
- package/dist/datePickers/dateTimeRules.js +24 -0
- package/dist/datePickers/dateTimeRules.js.map +1 -0
- package/dist/datePickers/pickerValues.js +106 -0
- package/dist/datePickers/pickerValues.js.map +1 -0
- package/dist/datePickers/useCurrentMinute.js +28 -0
- package/dist/datePickers/useCurrentMinute.js.map +1 -0
- package/dist/datePickers/usePickerField.js +22 -0
- package/dist/datePickers/usePickerField.js.map +1 -0
- package/dist/dictionaries/dictionaries.js +3 -1
- package/dist/dictionaries/dictionaries.js.map +1 -1
- package/dist/forms/FormAttachmentsField.js +12 -0
- package/dist/forms/FormAttachmentsField.js.map +1 -0
- package/dist/forms/FormCheckboxGroupField.js +12 -0
- package/dist/forms/FormCheckboxGroupField.js.map +1 -0
- package/dist/forms/FormFileField.js +12 -0
- package/dist/forms/FormFileField.js.map +1 -0
- package/dist/forms/FormFileUploadField.js +12 -0
- package/dist/forms/FormFileUploadField.js.map +1 -0
- package/dist/forms/FormImageField.js +12 -0
- package/dist/forms/FormImageField.js.map +1 -0
- package/dist/forms/FormMaskedField.js +12 -0
- package/dist/forms/FormMaskedField.js.map +1 -0
- package/dist/forms/FormRepeatingFields.js +54 -0
- package/dist/forms/FormRepeatingFields.js.map +1 -0
- package/dist/forms/FormTransferListField.js +12 -0
- package/dist/forms/FormTransferListField.js.map +1 -0
- package/dist/forms/applyServerProblem.js +10 -3
- package/dist/forms/applyServerProblem.js.map +1 -1
- package/dist/forms/listRules.js +111 -0
- package/dist/forms/listRules.js.map +1 -0
- package/dist/forms/serverProblemFields.js +17 -0
- package/dist/forms/serverProblemFields.js.map +1 -1
- package/dist/forms.js +51 -29
- package/dist/forms.js.map +1 -1
- package/dist/hookForm/FormAttachmentsField.js +12 -0
- package/dist/hookForm/FormAttachmentsField.js.map +1 -0
- package/dist/hookForm/FormCheckboxGroupField.js +12 -0
- package/dist/hookForm/FormCheckboxGroupField.js.map +1 -0
- package/dist/hookForm/FormFileField.js +12 -0
- package/dist/hookForm/FormFileField.js.map +1 -0
- package/dist/hookForm/FormFileUploadField.js +12 -0
- package/dist/hookForm/FormFileUploadField.js.map +1 -0
- package/dist/hookForm/FormImageField.js +12 -0
- package/dist/hookForm/FormImageField.js.map +1 -0
- package/dist/hookForm/FormMaskedField.js +12 -0
- package/dist/hookForm/FormMaskedField.js.map +1 -0
- package/dist/hookForm/FormRepeatingFields.js +44 -0
- package/dist/hookForm/FormRepeatingFields.js.map +1 -0
- package/dist/hookForm/FormTransferListField.js +12 -0
- package/dist/hookForm/FormTransferListField.js.map +1 -0
- package/dist/hookForm/applyServerProblem.js +8 -3
- package/dist/hookForm/applyServerProblem.js.map +1 -1
- package/dist/index.js +188 -100
- package/dist/index.js.map +1 -1
- package/dist/inputs/CatalogInput.js +74 -7
- package/dist/inputs/CatalogInput.js.map +1 -1
- package/dist/inputs/CheckboxGroupInput.js +100 -0
- package/dist/inputs/CheckboxGroupInput.js.map +1 -0
- package/dist/inputs/MaskedInput.js +86 -0
- package/dist/inputs/MaskedInput.js.map +1 -0
- package/dist/inputs/NumberInput.js +12 -1
- package/dist/inputs/NumberInput.js.map +1 -1
- package/dist/inputs/RepeatingFields.js +177 -0
- package/dist/inputs/RepeatingFields.js.map +1 -0
- package/dist/inputs/SelectInput.js +43 -5
- package/dist/inputs/SelectInput.js.map +1 -1
- package/dist/inputs/TransferListInput.js +215 -0
- package/dist/inputs/TransferListInput.js.map +1 -0
- package/dist/inputs/inputMasks.js +84 -0
- package/dist/inputs/inputMasks.js.map +1 -0
- package/dist/inputs/selectAllState.js +20 -0
- package/dist/inputs/selectAllState.js.map +1 -0
- package/dist/inputs/useRowKeys.js +26 -0
- package/dist/inputs/useRowKeys.js.map +1 -0
- package/dist/legal/CookieNotice.js +79 -0
- package/dist/legal/CookieNotice.js.map +1 -0
- package/dist/legal/TermsAcceptanceDialog.js +129 -0
- package/dist/legal/TermsAcceptanceDialog.js.map +1 -0
- package/dist/legal/consentStorage.js +18 -0
- package/dist/legal/consentStorage.js.map +1 -0
- package/dist/legal/useCookieConsent.js +17 -0
- package/dist/legal/useCookieConsent.js.map +1 -0
- package/dist/locales/en.js +233 -1
- package/dist/locales/en.js.map +1 -1
- package/dist/locales/es.js +233 -1
- package/dist/locales/es.js.map +1 -1
- package/dist/mexico/CfdiSummary.js +81 -0
- package/dist/mexico/CfdiSummary.js.map +1 -0
- package/dist/mexico/EfirmaFields.js +167 -0
- package/dist/mexico/EfirmaFields.js.map +1 -0
- package/dist/mexico/certificateReader.js +79 -0
- package/dist/mexico/certificateReader.js.map +1 -0
- package/dist/mexico/cfdiReader.js +81 -0
- package/dist/mexico/cfdiReader.js.map +1 -0
- package/dist/mexico/derReader.js +81 -0
- package/dist/mexico/derReader.js.map +1 -0
- package/dist/mexico/efirmaKey.js +51 -0
- package/dist/mexico/efirmaKey.js.map +1 -0
- package/dist/mexico/mexicoIdentifiers.js +86 -0
- package/dist/mexico/mexicoIdentifiers.js.map +1 -0
- package/dist/mexico/mexicoRules.js +16 -0
- package/dist/mexico/mexicoRules.js.map +1 -0
- package/dist/mexico.js +32 -0
- package/dist/mexico.js.map +1 -0
- package/dist/react-hook-form.js +40 -17
- package/dist/react-hook-form.js.map +1 -1
- package/dist/rich-text.js +19 -0
- package/dist/rich-text.js.map +1 -0
- package/dist/richText/FormRichTextField.js +12 -0
- package/dist/richText/FormRichTextField.js.map +1 -0
- package/dist/richText/RichTextEditor.js +108 -0
- package/dist/richText/RichTextEditor.js.map +1 -0
- package/dist/richText/RichTextLinkPopover.js +57 -0
- package/dist/richText/RichTextLinkPopover.js.map +1 -0
- package/dist/richText/RichTextToolbar.js +189 -0
- package/dist/richText/RichTextToolbar.js.map +1 -0
- package/dist/richText/RichTextView.js +26 -0
- package/dist/richText/RichTextView.js.map +1 -0
- package/dist/richText/richTextContentSx.js +42 -0
- package/dist/richText/richTextContentSx.js.map +1 -0
- package/dist/richText/richTextExtensions.js +44 -0
- package/dist/richText/richTextExtensions.js.map +1 -0
- package/dist/richText/richTextRules.js +46 -0
- package/dist/richText/richTextRules.js.map +1 -0
- package/dist/table.js +2 -0
- package/dist/table.js.map +1 -1
- package/dist/theme/components/glassComponents.js +27 -0
- package/dist/theme/components/glassComponents.js.map +1 -1
- package/dist/theme/components/minimalComponents.js +12 -0
- package/dist/theme/components/minimalComponents.js.map +1 -1
- package/dist/types/DataTable/ServerTableFilterDrawer.d.ts +21 -0
- package/dist/types/DataTable/filterContext.d.ts +2 -1
- package/dist/types/DataTable/goToPageOptions.d.ts +15 -0
- package/dist/types/DataTable/index.d.ts +1 -0
- package/dist/types/date-pickers.d.ts +10 -1
- package/dist/types/datePickers/DateInput.d.ts +6 -4
- package/dist/types/datePickers/DateTimeInput.d.ts +50 -0
- package/dist/types/datePickers/FormDateTimeField.d.ts +7 -0
- package/dist/types/datePickers/FormMonthField.d.ts +7 -0
- package/dist/types/datePickers/FormTimeField.d.ts +7 -0
- package/dist/types/datePickers/MonthInput.d.ts +39 -0
- package/dist/types/datePickers/PickerFrame.d.ts +22 -0
- package/dist/types/datePickers/TimeInput.d.ts +38 -0
- package/dist/types/datePickers/dateTimeRules.d.ts +31 -0
- package/dist/types/datePickers/pickerValues.d.ts +114 -0
- package/dist/types/datePickers/useCurrentMinute.d.ts +5 -0
- package/dist/types/datePickers/usePickerField.d.ts +24 -0
- package/dist/types/dictionaries/dictionaries.d.ts +6 -3
- package/dist/types/forms/FormAttachmentsField.d.ts +7 -0
- package/dist/types/forms/FormCheckboxGroupField.d.ts +7 -0
- package/dist/types/forms/FormFileField.d.ts +7 -0
- package/dist/types/forms/FormFileUploadField.d.ts +7 -0
- package/dist/types/forms/FormImageField.d.ts +7 -0
- package/dist/types/forms/FormMaskedField.d.ts +7 -0
- package/dist/types/forms/FormRepeatingFields.d.ts +25 -0
- package/dist/types/forms/FormTransferListField.d.ts +7 -0
- package/dist/types/forms/index.d.ts +10 -1
- package/dist/types/forms/listRules.d.ts +48 -0
- package/dist/types/forms/serverProblemFields.d.ts +13 -0
- package/dist/types/forms.d.ts +1 -1
- package/dist/types/hookForm/FormAttachmentsField.d.ts +8 -0
- package/dist/types/hookForm/FormCheckboxGroupField.d.ts +8 -0
- package/dist/types/hookForm/FormFileField.d.ts +8 -0
- package/dist/types/hookForm/FormFileUploadField.d.ts +8 -0
- package/dist/types/hookForm/FormImageField.d.ts +8 -0
- package/dist/types/hookForm/FormMaskedField.d.ts +9 -0
- package/dist/types/hookForm/FormRepeatingFields.d.ts +27 -0
- package/dist/types/hookForm/FormTransferListField.d.ts +8 -0
- package/dist/types/index.d.ts +25 -3
- package/dist/types/inputs/CatalogInput.d.ts +7 -1
- package/dist/types/inputs/CheckboxGroupInput.d.ts +39 -0
- package/dist/types/inputs/MaskedInput.d.ts +35 -0
- package/dist/types/inputs/RepeatingFields.d.ts +52 -0
- package/dist/types/inputs/SelectInput.d.ts +6 -1
- package/dist/types/inputs/TransferListInput.d.ts +43 -0
- package/dist/types/inputs/inputMasks.d.ts +52 -0
- package/dist/types/inputs/selectAllState.d.ts +11 -0
- package/dist/types/inputs/useRowKeys.d.ts +11 -0
- package/dist/types/legal/CookieNotice.d.ts +30 -0
- package/dist/types/legal/TermsAcceptanceDialog.d.ts +32 -0
- package/dist/types/legal/consentStorage.d.ts +16 -0
- package/dist/types/legal/useCookieConsent.d.ts +13 -0
- package/dist/types/locales/en.d.ts +232 -0
- package/dist/types/locales/es.d.ts +232 -0
- package/dist/types/mexico/CfdiSummary.d.ts +17 -0
- package/dist/types/mexico/EfirmaFields.d.ts +38 -0
- package/dist/types/mexico/certificateReader.d.ts +37 -0
- package/dist/types/mexico/cfdiReader.d.ts +95 -0
- package/dist/types/mexico/derReader.d.ts +54 -0
- package/dist/types/mexico/efirmaKey.d.ts +26 -0
- package/dist/types/mexico/mexicoIdentifiers.d.ts +61 -0
- package/dist/types/mexico/mexicoRules.d.ts +29 -0
- package/dist/types/mexico.d.ts +11 -0
- package/dist/types/react-hook-form.d.ts +10 -1
- package/dist/types/rich-text.d.ts +5 -0
- package/dist/types/richText/FormRichTextField.d.ts +7 -0
- package/dist/types/richText/RichTextEditor.d.ts +42 -0
- package/dist/types/richText/RichTextLinkPopover.d.ts +17 -0
- package/dist/types/richText/RichTextToolbar.d.ts +15 -0
- package/dist/types/richText/RichTextView.d.ts +13 -0
- package/dist/types/richText/richTextContentSx.d.ts +7 -0
- package/dist/types/richText/richTextExtensions.d.ts +15 -0
- package/dist/types/richText/richTextRules.d.ts +41 -0
- package/dist/types/table.d.ts +1 -1
- package/dist/types/ui/actions/ActionsMenu.d.ts +18 -3
- package/dist/types/ui/actions/CopyButton.d.ts +7 -4
- package/dist/types/ui/actions/tableRowText.d.ts +6 -0
- package/dist/types/ui/cards/RecordCard.d.ts +65 -0
- package/dist/types/ui/cards/RecordCardSkeleton.d.ts +17 -0
- package/dist/types/ui/countdown/Countdown.d.ts +31 -0
- package/dist/types/ui/countdown/countdownParts.d.ts +39 -0
- package/dist/types/ui/countdown/useCountdown.d.ts +14 -0
- package/dist/types/ui/documents/PdfViewerDialog.d.ts +21 -0
- package/dist/types/ui/filters/FilterDrawer.d.ts +34 -0
- package/dist/types/ui/filters/filterDrawerFields.d.ts +139 -0
- package/dist/types/ui/help/HelpButton.d.ts +15 -0
- package/dist/types/ui/help/HelpCarouselDialog.d.ts +35 -0
- package/dist/types/ui/help/useHelpCarousel.d.ts +21 -0
- package/dist/types/ui/index.d.ts +10 -0
- package/dist/types/ui/labels/IconBadge.d.ts +34 -0
- package/dist/types/ui/navigation/RouteTabs.d.ts +44 -0
- package/dist/types/ui/navigation/routeTabMatching.d.ts +27 -0
- package/dist/types/upload/AttachmentsInput.d.ts +62 -0
- package/dist/types/upload/FileChip.d.ts +18 -0
- package/dist/types/upload/FileDropzone.d.ts +3 -0
- package/dist/types/upload/FileInput.d.ts +48 -0
- package/dist/types/upload/FileUploadInput.d.ts +80 -0
- package/dist/types/upload/ImageInput.d.ts +53 -0
- package/dist/types/upload/TemplateDownloadButton.d.ts +39 -0
- package/dist/types/upload/fileAppearance.d.ts +8 -0
- package/dist/types/upload/fileRules.d.ts +38 -0
- package/dist/types/upload/transferProgress.d.ts +18 -0
- package/dist/types/upload/uploadMachine.d.ts +6 -2
- package/dist/types/upload/uploadWithProgress.d.ts +6 -2
- package/dist/types/upload/useFilePicker.d.ts +39 -0
- package/dist/ui/actions/ActionsMenu.js +98 -15
- package/dist/ui/actions/ActionsMenu.js.map +1 -1
- package/dist/ui/actions/CopyButton.js +31 -11
- package/dist/ui/actions/CopyButton.js.map +1 -1
- package/dist/ui/actions/tableRowText.js +7 -0
- package/dist/ui/actions/tableRowText.js.map +1 -0
- package/dist/ui/cards/RecordCard.js +145 -0
- package/dist/ui/cards/RecordCard.js.map +1 -0
- package/dist/ui/cards/RecordCardSkeleton.js +33 -0
- package/dist/ui/cards/RecordCardSkeleton.js.map +1 -0
- package/dist/ui/countdown/Countdown.js +38 -0
- package/dist/ui/countdown/Countdown.js.map +1 -0
- package/dist/ui/countdown/countdownParts.js +34 -0
- package/dist/ui/countdown/countdownParts.js.map +1 -0
- package/dist/ui/countdown/useCountdown.js +35 -0
- package/dist/ui/countdown/useCountdown.js.map +1 -0
- package/dist/ui/documents/PdfViewerDialog.js +81 -0
- package/dist/ui/documents/PdfViewerDialog.js.map +1 -0
- package/dist/ui/filters/FilterDrawer.js +211 -0
- package/dist/ui/filters/FilterDrawer.js.map +1 -0
- package/dist/ui/filters/filterDrawerFields.js +119 -0
- package/dist/ui/filters/filterDrawerFields.js.map +1 -0
- package/dist/ui/help/HelpButton.js +17 -0
- package/dist/ui/help/HelpButton.js.map +1 -0
- package/dist/ui/help/HelpCarouselDialog.js +154 -0
- package/dist/ui/help/HelpCarouselDialog.js.map +1 -0
- package/dist/ui/help/useHelpCarousel.js +25 -0
- package/dist/ui/help/useHelpCarousel.js.map +1 -0
- package/dist/ui/labels/IconBadge.js +82 -0
- package/dist/ui/labels/IconBadge.js.map +1 -0
- package/dist/ui/navigation/RouteTabs.js +75 -0
- package/dist/ui/navigation/RouteTabs.js.map +1 -0
- package/dist/ui/navigation/routeTabMatching.js +23 -0
- package/dist/ui/navigation/routeTabMatching.js.map +1 -0
- package/dist/upload/AttachmentsInput.js +127 -0
- package/dist/upload/AttachmentsInput.js.map +1 -0
- package/dist/upload/FileChip.js +27 -0
- package/dist/upload/FileChip.js.map +1 -0
- package/dist/upload/FileDropzone.js +19 -84
- package/dist/upload/FileDropzone.js.map +1 -1
- package/dist/upload/FileInput.js +112 -0
- package/dist/upload/FileInput.js.map +1 -0
- package/dist/upload/FileUploadInput.js +285 -0
- package/dist/upload/FileUploadInput.js.map +1 -0
- package/dist/upload/ImageInput.js +125 -0
- package/dist/upload/ImageInput.js.map +1 -0
- package/dist/upload/TemplateDownloadButton.js +67 -0
- package/dist/upload/TemplateDownloadButton.js.map +1 -0
- package/dist/upload/fileAppearance.js +21 -0
- package/dist/upload/fileAppearance.js.map +1 -0
- package/dist/upload/fileRules.js +27 -1
- package/dist/upload/fileRules.js.map +1 -1
- package/dist/upload/transferProgress.js +23 -0
- package/dist/upload/transferProgress.js.map +1 -0
- package/dist/upload/uploadMachine.js +8 -8
- package/dist/upload/uploadMachine.js.map +1 -1
- package/dist/upload/uploadWithProgress.js +1 -1
- package/dist/upload/uploadWithProgress.js.map +1 -1
- package/dist/upload/useFilePicker.js +101 -0
- package/dist/upload/useFilePicker.js.map +1 -0
- package/package.json +44 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FileDropzone.js","sources":["../../src/upload/FileDropzone.jsx"],"sourcesContent":["// La zona para soltar UN archivo, con puro MUI (sin librerías): se suelta o se elige, se revisa su tipo y su peso, y el\n// elegido se ve con su ícono, su nombre y su peso, con la × para quitarlo sin volver a abrir la ventana de archivos.\n//\n// Hay otra con el mismo uso y FilePond por dentro (@stackcraftmx/calcita/filepond): animaciones, varios archivos y\n// avance por archivo. Cambiar de una a otra es cambiar el import.\n//\n// Solo ELIGE el archivo (controlado: value / onChange). Enviarlo es de la pantalla: arma su FormData con los campos que\n// pida su backend y lo sube (uploadWithProgress + uploadMachine). Para eso están los huecos:\n// actions botones junto a «Elegir archivo» (p. ej. el de enviar)\n// children lo que va abajo (campos del formulario, el avance de la subida)\n//\n// Configuración: accept y maxSize (upload/fileRules.js); busy pone el candado mientras se sube (no se puede quitar ni\n// cambiar el archivo a media subida) y busyModal además bloquea toda la pantalla con ModalWaiting.\n//\n// Lo que se rechaza se avisa con toastError y los textos del paquete; con onReject la pantalla decide qué hacer.\n//\n// Mobile primero: en el teléfono no hay arrastrar; el botón es el camino y los botones se apilan a lo ancho.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaFileDropzone con defaultProps y\n// styleOverrides de sus partes: root, fileArea, fileChip, hint, actions, limits, extra.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useRef, useState} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport Box from '@mui/material/Box';\nimport Button from '@mui/material/Button';\nimport Chip from '@mui/material/Chip';\nimport Paper from '@mui/material/Paper';\nimport Stack from '@mui/material/Stack';\nimport Typography from '@mui/material/Typography';\nimport CancelIcon from '@mui/icons-material/Cancel';\nimport DescriptionOutlinedIcon from '@mui/icons-material/DescriptionOutlined';\nimport ImageOutlinedIcon from '@mui/icons-material/ImageOutlined';\nimport InsertDriveFileOutlinedIcon from '@mui/icons-material/InsertDriveFileOutlined';\nimport PictureAsPdfOutlinedIcon from '@mui/icons-material/PictureAsPdfOutlined';\nimport TableViewOutlinedIcon from '@mui/icons-material/TableViewOutlined';\n\nimport ModalWaiting from '@/feedback/ModalWaiting.jsx';\nimport {toastError} from '@/feedback/toast.jsx';\nimport {useFormatters} from '@/toolkit/useFormatters.js';\nimport {FILE_REJECTION, matchesAccept, parseFileSize, resolveAccept} from '@/upload/fileRules.js';\nimport {piecePart, usePieceProps} from '@/theme/themePieces.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaFileDropzone)\nconst THEME_NAME = 'CalcitaFileDropzone';\nconst DropzoneRoot = piecePart(THEME_NAME, 'Root', Paper);\nconst DropzoneFileArea = piecePart(THEME_NAME, 'FileArea', Box);\nconst DropzoneFileChip = piecePart(THEME_NAME, 'FileChip', Chip);\nconst DropzoneHint = piecePart(THEME_NAME, 'Hint', Typography);\nconst DropzoneActions = piecePart(THEME_NAME, 'Actions', Stack);\nconst DropzoneLimits = piecePart(THEME_NAME, 'Limits', Typography);\nconst DropzoneExtra = piecePart(THEME_NAME, 'Extra', Box);\n\n// el ícono según el tipo del archivo elegido, con el mismo color que el panel de descargas\nconst FILE_APPEARANCES = [\n {pattern: /\\.(xlsx?|csv)$/, Icon: TableViewOutlinedIcon, color: 'success'},\n {pattern: /\\.pdf$/, Icon: PictureAsPdfOutlinedIcon, color: 'error'},\n {pattern: /\\.(png|jpe?g|gif|webp|svg|avif)$/, Icon: ImageOutlinedIcon, color: 'info'},\n {pattern: /\\.(docx?|txt|md)$/, Icon: DescriptionOutlinedIcon, color: 'primary'},\n];\nconst DEFAULT_FILE_APPEARANCE = {Icon: InsertDriveFileOutlinedIcon, color: 'action'};\n\n/**\n * @param {object} props\n * @param {File|null} [props.value] el archivo elegido\n * @param {(file: File|null) => void} props.onChange null al quitarlo\n * @param {string|string[]} [props.accept] 'excel' | ['excel', 'pdf'] | '.xlsx,.xls' | 'image/*'\n * @param {number|string} [props.maxSize] bytes o '10 MB'\n * @param {string} [props.hint] ya traducido; por omisión «Arrastre aquí su archivo»\n * @param {string} [props.buttonLabel] ya traducido; por omisión «Elegir archivo»\n * @param {boolean} [props.disabled]\n * @param {boolean} [props.busy] candado mientras se sube: no se puede soltar, elegir ni quitar\n * @param {boolean|{title?: string, text?: string}} [props.busyModal] además bloquea la pantalla mientras busy\n * @param {(file: File, reason: string) => void} [props.onReject] reason es uno de FILE_REJECTION; sin él, un aviso\n * @param {import('react').ReactNode} [props.actions]\n * @param {import('react').ReactNode} [props.children]\n * @param {object} [props.sx] ajustes del recuadro\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.CalcitaFileDropzone.defaultProps) y encima lo que llega aquí\n const {\n value = null, onChange, accept, maxSize, hint, buttonLabel, disabled = false, busy = false, busyModal = false, onReject, actions,\n children, sx, texts,\n ...unknownProps\n} = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const {t} = useToolkitTranslation('common', texts);\n const {formatFileSize} = useFormatters();\n const inputRef = useRef(null);\n const [isDragging, setIsDragging] = useState(false);\n const isLocked = disabled || busy;\n const resolvedAccept = resolveAccept(accept);\n const maxBytes = parseFileSize(maxSize);\n const hintText = hint ?? t('upload.hint');\n\n const reject = (file, reason) => {\n if (onReject) {\n onReject(file, reason);\n return;\n }\n const message = reason === FILE_REJECTION.size\n ? t('upload.tooLarge', {name: file.name, max: formatFileSize(maxBytes)})\n : t('upload.invalidType', {name: file.name, accept: resolvedAccept});\n toastError(message);\n };\n\n const pick = (file) => {\n // guard clause: se cerró la ventana sin elegir, o el candado está puesto\n if (!file || isLocked) {\n return;\n }\n if (!matchesAccept(file, resolvedAccept)) {\n reject(file, FILE_REJECTION.type);\n return;\n }\n if (maxBytes && file.size > maxBytes) {\n reject(file, FILE_REJECTION.size);\n return;\n }\n onChange(file);\n };\n\n const openPicker = () => {\n // OJO: se limpia el valor del input para poder volver a elegir EL MISMO archivo (corregido en disco); sin esto\n // el navegador no avisa el cambio\n if (inputRef.current) {\n inputRef.current.value = '';\n }\n inputRef.current?.click();\n };\n\n const appearance = value\n ? FILE_APPEARANCES.find(({pattern}) => pattern.test(value.name.toLowerCase())) ?? DEFAULT_FILE_APPEARANCE\n : null;\n\n return (\n <DropzoneRoot\n variant=\"outlined\"\n aria-busy={busy}\n onDragOver={(event) => {\n event.preventDefault();\n setIsDragging(!isLocked);\n }}\n onDragLeave={() => setIsDragging(false)}\n onDrop={(event) => {\n event.preventDefault();\n setIsDragging(false);\n pick(event.dataTransfer.files?.[0]);\n }}\n ownSx={{\n p: {xs: 3, sm: 5},\n textAlign: 'center',\n borderStyle: 'dashed',\n borderWidth: 2,\n borderColor: isDragging ? 'primary.main' : 'divider',\n bgcolor: isDragging ? 'action.hover' : 'background.paper',\n transition: 'border-color .15s ease, background-color .15s ease',\n opacity: disabled ? 0.6 : 1,\n ...sx,\n }}\n >\n <input ref={inputRef} type=\"file\" accept={resolvedAccept} hidden disabled={isLocked} aria-label={hintText}\n onChange={(event) => pick(event.target.files?.[0])} />\n {value ? (\n <DropzoneFileArea ownSx={{mb: 2}}>\n <DropzoneFileChip\n icon={<appearance.Icon fontSize=\"small\" color={appearance.color} />}\n label={`${value.name} · ${formatFileSize(value.size)}`}\n variant=\"outlined\"\n onDelete={isLocked ? undefined : () => onChange(null)}\n deleteIcon={<CancelIcon aria-label={t('upload.remove', {name: value.name})} />}\n ownSx={{maxWidth: '100%', height: 'auto', py: 0.5, '& .MuiChip-label': {whiteSpace: 'normal', overflowWrap: 'anywhere'}}}\n />\n </DropzoneFileArea>\n ) : (\n <DropzoneHint variant=\"body1\" ownSx={{mb: 2, overflowWrap: 'anywhere'}}>{hintText}</DropzoneHint>\n )}\n <DropzoneActions direction={{xs: 'column', sm: 'row'}} spacing={2} ownSx={{justifyContent: 'center', alignItems: {xs: 'stretch', sm: 'center'}}}>\n <Button variant=\"outlined\" onClick={openPicker} disabled={isLocked}>{buttonLabel ?? t('upload.choose')}</Button>\n {actions}\n </DropzoneActions>\n {/* el límite, discreto, solo si hay límite */}\n {maxBytes && (\n <DropzoneLimits variant=\"caption\" color=\"text.secondary\" ownSx={{display: 'block', mt: 1.5}}>\n {t('upload.maxSizeHint', {max: formatFileSize(maxBytes)})}\n </DropzoneLimits>\n )}\n {children && <DropzoneExtra ownSx={{mt: 3, textAlign: 'left'}}>{children}</DropzoneExtra>}\n {busyModal && <ModalWaiting open={busy} title={busyModal.title} text={busyModal.text} />}\n </DropzoneRoot>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;AA6CA,MAAM,aAAa;AACnB,MAAM,eAAe,UAAU,YAAY,QAAQ,KAAK;AACxD,MAAM,mBAAmB,UAAU,YAAY,YAAY,GAAG;AAC9D,MAAM,mBAAmB,UAAU,YAAY,YAAY,IAAI;AAC/D,MAAM,eAAe,UAAU,YAAY,QAAQ,UAAU;AAC7D,MAAM,kBAAkB,UAAU,YAAY,WAAW,KAAK;AAC9D,MAAM,iBAAiB,UAAU,YAAY,UAAU,UAAU;AACjE,MAAM,gBAAgB,UAAU,YAAY,SAAS,GAAG;AAGxD,MAAM,mBAAmB;AAAA,EACrB,EAAC,SAAS,kBAAkB,MAAM,uBAAuB,OAAO,UAAA;AAAA,EAChE,EAAC,SAAS,UAAU,MAAM,0BAA0B,OAAO,QAAA;AAAA,EAC3D,EAAC,SAAS,oCAAoC,MAAM,mBAAmB,OAAO,OAAA;AAAA,EAC9E,EAAC,SAAS,qBAAqB,MAAM,yBAAyB,OAAO,UAAA;AACzE;AACA,MAAM,0BAA0B,EAAC,MAAM,6BAA6B,OAAO,SAAA;AAmB3E,SAAwB,aAAa,OAAO;AAExC,QAAM;AAAA,IACN,QAAQ;AAAA,IAAM;AAAA,IAAU;AAAA,IAAQ;AAAA,IAAS;AAAA,IAAM;AAAA,IAAa,WAAW;AAAA,IAAO,OAAO;AAAA,IAAO,YAAY;AAAA,IAAO;AAAA,IAAU;AAAA,IACzH;AAAA,IAAU;AAAA,IAAI;AAAA,IACd,GAAG;AAAA,EAAA,IACH,cAAc,OAAO,UAAU;AAC/B,mBAAiB,YAAY,YAAY;AACzC,QAAM,EAAC,EAAA,IAAK,sBAAsB,UAAU,KAAK;AACjD,QAAM,EAAC,eAAA,IAAkB,cAAA;AACzB,QAAM,WAAW,OAAO,IAAI;AAC5B,QAAM,CAAC,YAAY,aAAa,IAAI,SAAS,KAAK;AAClD,QAAM,WAAW,YAAY;AAC7B,QAAM,iBAAiB,cAAc,MAAM;AAC3C,QAAM,WAAW,cAAc,OAAO;AACtC,QAAM,WAAW,QAAQ,EAAE,aAAa;AAExC,QAAM,SAAS,CAAC,MAAM,WAAW;AAC7B,QAAI,UAAU;AACV,eAAS,MAAM,MAAM;AACrB;AAAA,IACJ;AACA,UAAM,UAAU,WAAW,eAAe,OACpC,EAAE,mBAAmB,EAAC,MAAM,KAAK,MAAM,KAAK,eAAe,QAAQ,EAAA,CAAE,IACrE,EAAE,sBAAsB,EAAC,MAAM,KAAK,MAAM,QAAQ,gBAAe;AACvE,eAAW,OAAO;AAAA,EACtB;AAEA,QAAM,OAAO,CAAC,SAAS;AAEnB,QAAI,CAAC,QAAQ,UAAU;AACnB;AAAA,IACJ;AACA,QAAI,CAAC,cAAc,MAAM,cAAc,GAAG;AACtC,aAAO,MAAM,eAAe,IAAI;AAChC;AAAA,IACJ;AACA,QAAI,YAAY,KAAK,OAAO,UAAU;AAClC,aAAO,MAAM,eAAe,IAAI;AAChC;AAAA,IACJ;AACA,aAAS,IAAI;AAAA,EACjB;AAEA,QAAM,aAAa,MAAM;AAGrB,QAAI,SAAS,SAAS;AAClB,eAAS,QAAQ,QAAQ;AAAA,IAC7B;AACA,aAAS,SAAS,MAAA;AAAA,EACtB;AAEA,QAAM,aAAa,QACb,iBAAiB,KAAK,CAAC,EAAC,QAAA,MAAa,QAAQ,KAAK,MAAM,KAAK,YAAA,CAAa,CAAC,KAAK,0BAChF;AAEN,SACI;AAAA,IAAC;AAAA,IAAA;AAAA,MACG,SAAQ;AAAA,MACR,aAAW;AAAA,MACX,YAAY,CAAC,UAAU;AACnB,cAAM,eAAA;AACN,sBAAc,CAAC,QAAQ;AAAA,MAC3B;AAAA,MACA,aAAa,MAAM,cAAc,KAAK;AAAA,MACtC,QAAQ,CAAC,UAAU;AACf,cAAM,eAAA;AACN,sBAAc,KAAK;AACnB,aAAK,MAAM,aAAa,QAAQ,CAAC,CAAC;AAAA,MACtC;AAAA,MACA,OAAO;AAAA,QACH,GAAG,EAAC,IAAI,GAAG,IAAI,EAAA;AAAA,QACf,WAAW;AAAA,QACX,aAAa;AAAA,QACb,aAAa;AAAA,QACb,aAAa,aAAa,iBAAiB;AAAA,QAC3C,SAAS,aAAa,iBAAiB;AAAA,QACvC,YAAY;AAAA,QACZ,SAAS,WAAW,MAAM;AAAA,QAC1B,GAAG;AAAA,MAAA;AAAA,MAGP,UAAA;AAAA,QAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YAAM,KAAK;AAAA,YAAU,MAAK;AAAA,YAAO,QAAQ;AAAA,YAAgB,QAAM;AAAA,YAAC,UAAU;AAAA,YAAU,cAAY;AAAA,YAC1F,UAAU,CAAC,UAAU,KAAK,MAAM,OAAO,QAAQ,CAAC,CAAC;AAAA,UAAA;AAAA,QAAA;AAAA,QACvD,QACG,oBAAC,kBAAA,EAAiB,OAAO,EAAC,IAAI,KAC1B,UAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YACG,0BAAO,WAAW,MAAX,EAAgB,UAAS,SAAQ,OAAO,WAAW,OAAO;AAAA,YACjE,OAAO,GAAG,MAAM,IAAI,MAAM,eAAe,MAAM,IAAI,CAAC;AAAA,YACpD,SAAQ;AAAA,YACR,UAAU,WAAW,SAAY,MAAM,SAAS,IAAI;AAAA,YACpD,YAAY,oBAAC,YAAA,EAAW,cAAY,EAAE,iBAAiB,EAAC,MAAM,MAAM,KAAA,CAAK,EAAA,CAAG;AAAA,YAC5E,OAAO,EAAC,UAAU,QAAQ,QAAQ,QAAQ,IAAI,KAAK,oBAAoB,EAAC,YAAY,UAAU,cAAc,aAAU;AAAA,UAAC;AAAA,QAAA,EAC3H,CACJ,IAEA,oBAAC,cAAA,EAAa,SAAQ,SAAQ,OAAO,EAAC,IAAI,GAAG,cAAc,WAAA,GAAc,UAAA,SAAA,CAAS;AAAA,QAEtF,qBAAC,mBAAgB,WAAW,EAAC,IAAI,UAAU,IAAI,SAAQ,SAAS,GAAG,OAAO,EAAC,gBAAgB,UAAU,YAAY,EAAC,IAAI,WAAW,IAAI,WAAQ,GACzI,UAAA;AAAA,UAAA,oBAAC,QAAA,EAAO,SAAQ,YAAW,SAAS,YAAY,UAAU,UAAW,UAAA,eAAe,EAAE,eAAe,EAAA,CAAE;AAAA,UACtG;AAAA,QAAA,GACL;AAAA,QAEC,gCACI,gBAAA,EAAe,SAAQ,WAAU,OAAM,kBAAiB,OAAO,EAAC,SAAS,SAAS,IAAI,IAAA,GAClF,YAAE,sBAAsB,EAAC,KAAK,eAAe,QAAQ,EAAA,CAAE,GAC5D;AAAA,QAEH,YAAY,oBAAC,eAAA,EAAc,OAAO,EAAC,IAAI,GAAG,WAAW,OAAA,GAAU,SAAA,CAAS;AAAA,QACxE,aAAa,oBAAC,cAAA,EAAa,MAAM,MAAM,OAAO,UAAU,OAAO,MAAM,UAAU,KAAA,CAAM;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGlG;"}
|
|
1
|
+
{"version":3,"file":"FileDropzone.js","sources":["../../src/upload/FileDropzone.jsx"],"sourcesContent":["// La zona para soltar UN archivo, con puro MUI (sin librerías): se suelta o se elige, se revisa su tipo y su peso, y el\n// elegido se ve con su ícono, su nombre y su peso, con la × para quitarlo sin volver a abrir la ventana de archivos.\n//\n// Hay otra con el mismo uso y FilePond por dentro (@stackcraftmx/calcita/filepond): animaciones, varios archivos y\n// avance por archivo. Cambiar de una a otra es cambiar el import. Para un formulario, del tamaño de un campo: FileInput;\n// para varios como en un correo: AttachmentsInput; para una foto: ImageInput.\n//\n// Solo ELIGE el archivo (controlado: value / onChange). Enviarlo es de la pantalla: arma su FormData con los campos que\n// pida su backend y lo sube (uploadWithProgress + uploadMachine). Para eso están los huecos:\n// actions botones junto a «Elegir archivo» (p. ej. el de enviar)\n// template «Descargar la plantilla» junto a «Elegir archivo» (las props de TemplateDownloadButton)\n// children lo que va abajo (campos del formulario, el avance de la subida)\n//\n// Configuración: accept y maxSize (upload/fileRules.js); busy pone el candado mientras se sube (no se puede quitar ni\n// cambiar el archivo a media subida) y busyModal además bloquea toda la pantalla con ModalWaiting.\n//\n// Lo que se rechaza se avisa con toastError y los textos del paquete; con onReject la pantalla decide qué hacer\n// (useFilePicker, el mismo de los demás cargadores).\n//\n// Mobile primero: en el teléfono no hay arrastrar; el botón es el camino y los botones se apilan a lo ancho.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaFileDropzone con defaultProps y\n// styleOverrides de sus partes: root, fileArea, fileChip, hint, actions, limits, extra.\n//\n// Genérico del paquete: function (regla de la casa).\nimport Box from '@mui/material/Box';\nimport Button from '@mui/material/Button';\nimport Chip from '@mui/material/Chip';\nimport Paper from '@mui/material/Paper';\nimport Stack from '@mui/material/Stack';\nimport Typography from '@mui/material/Typography';\n\nimport ModalWaiting from '@/feedback/ModalWaiting.jsx';\nimport {piecePart, usePieceProps} from '@/theme/themePieces.js';\nimport {useFormatters} from '@/toolkit/useFormatters.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\nimport FileChip from '@/upload/FileChip.jsx';\nimport TemplateDownloadButton from '@/upload/TemplateDownloadButton.jsx';\nimport {useFilePicker} from '@/upload/useFilePicker.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaFileDropzone)\nconst THEME_NAME = 'CalcitaFileDropzone';\nconst DropzoneRoot = piecePart(THEME_NAME, 'Root', Paper);\nconst DropzoneFileArea = piecePart(THEME_NAME, 'FileArea', Box);\nconst DropzoneFileChip = piecePart(THEME_NAME, 'FileChip', Chip);\nconst DropzoneHint = piecePart(THEME_NAME, 'Hint', Typography);\nconst DropzoneActions = piecePart(THEME_NAME, 'Actions', Stack);\nconst DropzoneLimits = piecePart(THEME_NAME, 'Limits', Typography);\nconst DropzoneExtra = piecePart(THEME_NAME, 'Extra', Box);\n\n/**\n * @param {object} props\n * @param {File|null} [props.value] el archivo elegido\n * @param {(file: File|null) => void} props.onChange null al quitarlo\n * @param {string|string[]} [props.accept] 'excel' | ['excel', 'pdf'] | '.xlsx,.xls' | 'image/*'\n * @param {number|string} [props.maxSize] bytes o '10 MB'\n * @param {string} [props.hint] ya traducido; por omisión «Arrastre aquí su archivo»\n * @param {string} [props.buttonLabel] ya traducido; por omisión «Elegir archivo»\n * @param {boolean} [props.disabled]\n * @param {boolean} [props.busy] candado mientras se sube: no se puede soltar, elegir ni quitar\n * @param {boolean|{title?: string, text?: string}} [props.busyModal] además bloquea la pantalla mientras busy\n * @param {(file: File, reason: string) => void} [props.onReject] reason es uno de FILE_REJECTION; sin él, un aviso\n * @param {Parameters<typeof import('@/upload/TemplateDownloadButton.jsx').default>[0]} [props.template] «Descargar la\n * plantilla» junto a «Elegir archivo»: {href} de un archivo fijo o {request} al API\n * @param {import('react').ReactNode} [props.actions]\n * @param {import('react').ReactNode} [props.children]\n * @param {object} [props.sx] ajustes del recuadro\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.CalcitaFileDropzone.defaultProps) y encima lo que llega aquí\n const {\n value = null, onChange, accept, maxSize, hint, buttonLabel, disabled = false, busy = false, busyModal = false, onReject, template,\n actions, children, sx, texts,\n ...unknownProps\n } = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const {t} = useToolkitTranslation('common', texts);\n const {formatFileSize} = useFormatters();\n const isLocked = disabled || busy;\n const hintText = hint ?? t('upload.hint');\n const picker = useFilePicker({accept, maxSize, disabled: isLocked, onAccept: onChange, onReject, texts});\n\n return (\n <DropzoneRoot\n variant=\"outlined\"\n aria-busy={busy}\n {...picker.dropHandlers}\n ownSx={{\n p: {xs: 3, sm: 5},\n textAlign: 'center',\n borderStyle: 'dashed',\n borderWidth: 2,\n borderColor: picker.isDragging ? 'primary.main' : 'divider',\n bgcolor: picker.isDragging ? 'action.hover' : 'background.paper',\n transition: 'border-color .15s ease, background-color .15s ease',\n opacity: disabled ? 0.6 : 1,\n ...sx,\n }}\n >\n <input {...picker.inputProps} aria-label={hintText} />\n {value ? (\n <DropzoneFileArea ownSx={{mb: 2}}>\n <FileChip file={value} ChipComponent={DropzoneFileChip} removeLabel={t('upload.remove', {name: value.name})}\n onRemove={isLocked ? undefined : () => onChange(null)} />\n </DropzoneFileArea>\n ) : (\n <DropzoneHint variant=\"body1\" ownSx={{mb: 2, overflowWrap: 'anywhere'}}>{hintText}</DropzoneHint>\n )}\n <DropzoneActions direction={{xs: 'column', sm: 'row'}} spacing={2} ownSx={{justifyContent: 'center', alignItems: {xs: 'stretch', sm: 'center'}}}>\n <Button variant=\"outlined\" onClick={picker.open} disabled={isLocked}>{buttonLabel ?? t('upload.choose')}</Button>\n {template && <TemplateDownloadButton texts={texts} {...template} />}\n {actions}\n </DropzoneActions>\n {/* el límite, discreto, solo si hay límite */}\n {picker.maxBytes && (\n <DropzoneLimits variant=\"caption\" color=\"text.secondary\" ownSx={{display: 'block', mt: 1.5}}>\n {t('upload.maxSizeHint', {max: formatFileSize(picker.maxBytes)})}\n </DropzoneLimits>\n )}\n {children && <DropzoneExtra ownSx={{mt: 3, textAlign: 'left'}}>{children}</DropzoneExtra>}\n {busyModal && <ModalWaiting open={busy} title={busyModal.title} text={busyModal.text} />}\n </DropzoneRoot>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useId } from "react";
|
|
4
|
+
import Button from "@mui/material/Button";
|
|
5
|
+
import Chip from "@mui/material/Chip";
|
|
6
|
+
import FormHelperText from "@mui/material/FormHelperText";
|
|
7
|
+
import Stack from "@mui/material/Stack";
|
|
8
|
+
import Typography from "@mui/material/Typography";
|
|
9
|
+
import AttachFileRoundedIcon from "@mui/icons-material/AttachFileRounded";
|
|
10
|
+
import { translateValidationMessage } from "../forms/validationMessages.js";
|
|
11
|
+
import FieldFrame from "../inputs/FieldFrame.js";
|
|
12
|
+
import { piecePart, usePieceProps } from "../theme/themePieces.js";
|
|
13
|
+
import { useToolkitTranslation } from "../toolkit/useToolkitTranslation.js";
|
|
14
|
+
import { warnUnknownProps } from "../toolkit/unknownProps.js";
|
|
15
|
+
import FileChip from "./FileChip.js";
|
|
16
|
+
import TemplateDownloadButton from "./TemplateDownloadButton.js";
|
|
17
|
+
import { useFilePicker } from "./useFilePicker.js";
|
|
18
|
+
const THEME_NAME = "CalcitaFileInput";
|
|
19
|
+
const FileInputRoot = piecePart(THEME_NAME, "Root", Stack);
|
|
20
|
+
const FileInputButton = piecePart(THEME_NAME, "Button", Button);
|
|
21
|
+
const FileInputChip = piecePart(THEME_NAME, "FileChip", Chip);
|
|
22
|
+
const FileInputEmpty = piecePart(THEME_NAME, "Empty", Typography);
|
|
23
|
+
function FileInput(props) {
|
|
24
|
+
const {
|
|
25
|
+
name,
|
|
26
|
+
label,
|
|
27
|
+
value = null,
|
|
28
|
+
onChange,
|
|
29
|
+
onBlur,
|
|
30
|
+
accept,
|
|
31
|
+
maxSize,
|
|
32
|
+
error,
|
|
33
|
+
helperText = " ",
|
|
34
|
+
buttonLabel,
|
|
35
|
+
emptyText,
|
|
36
|
+
disabled = false,
|
|
37
|
+
busy = false,
|
|
38
|
+
onReject,
|
|
39
|
+
template,
|
|
40
|
+
actions,
|
|
41
|
+
sx,
|
|
42
|
+
texts,
|
|
43
|
+
...unknownProps
|
|
44
|
+
} = usePieceProps(props, THEME_NAME);
|
|
45
|
+
warnUnknownProps(THEME_NAME, unknownProps);
|
|
46
|
+
const { t } = useToolkitTranslation("common", texts);
|
|
47
|
+
const generatedId = useId();
|
|
48
|
+
const buttonId = `${name ?? "file"}-${generatedId}`;
|
|
49
|
+
const errorText = translateValidationMessage(t, error);
|
|
50
|
+
const isLocked = disabled || busy;
|
|
51
|
+
const change = (file) => {
|
|
52
|
+
onChange(file);
|
|
53
|
+
onBlur?.();
|
|
54
|
+
};
|
|
55
|
+
const picker = useFilePicker({ accept, maxSize, disabled: isLocked, onAccept: change, onReject, texts });
|
|
56
|
+
return /* @__PURE__ */ jsx(FieldFrame, { inputId: buttonId, label, children: /* @__PURE__ */ jsxs("div", { children: [
|
|
57
|
+
/* @__PURE__ */ jsxs(
|
|
58
|
+
FileInputRoot,
|
|
59
|
+
{
|
|
60
|
+
direction: "row",
|
|
61
|
+
"aria-busy": busy,
|
|
62
|
+
...picker.dropHandlers,
|
|
63
|
+
ownSx: {
|
|
64
|
+
alignItems: "center",
|
|
65
|
+
flexWrap: "wrap",
|
|
66
|
+
gap: 1,
|
|
67
|
+
minHeight: 40,
|
|
68
|
+
borderRadius: 1,
|
|
69
|
+
px: 0.5,
|
|
70
|
+
mx: -0.5,
|
|
71
|
+
outline: picker.isDragging ? 2 : 0,
|
|
72
|
+
outlineStyle: "dashed",
|
|
73
|
+
outlineColor: "primary.main",
|
|
74
|
+
bgcolor: picker.isDragging ? "action.hover" : "transparent",
|
|
75
|
+
...sx
|
|
76
|
+
},
|
|
77
|
+
children: [
|
|
78
|
+
/* @__PURE__ */ jsx("input", { ...picker.inputProps, name, "aria-hidden": "true", tabIndex: -1 }),
|
|
79
|
+
/* @__PURE__ */ jsx(
|
|
80
|
+
FileInputButton,
|
|
81
|
+
{
|
|
82
|
+
id: buttonId,
|
|
83
|
+
variant: "outlined",
|
|
84
|
+
size: "small",
|
|
85
|
+
startIcon: /* @__PURE__ */ jsx(AttachFileRoundedIcon, {}),
|
|
86
|
+
onClick: picker.open,
|
|
87
|
+
disabled: isLocked,
|
|
88
|
+
ownSx: { flexShrink: 0 },
|
|
89
|
+
children: buttonLabel ?? t("upload.choose")
|
|
90
|
+
}
|
|
91
|
+
),
|
|
92
|
+
value ? /* @__PURE__ */ jsx(
|
|
93
|
+
FileChip,
|
|
94
|
+
{
|
|
95
|
+
file: value,
|
|
96
|
+
ChipComponent: FileInputChip,
|
|
97
|
+
removeLabel: t("upload.remove", { name: value.name }),
|
|
98
|
+
onRemove: isLocked ? void 0 : () => change(null)
|
|
99
|
+
}
|
|
100
|
+
) : /* @__PURE__ */ jsx(FileInputEmpty, { variant: "body2", color: "text.secondary", children: emptyText ?? t("upload.empty") }),
|
|
101
|
+
template && /* @__PURE__ */ jsx(TemplateDownloadButton, { size: "small", texts, ...template }),
|
|
102
|
+
actions
|
|
103
|
+
]
|
|
104
|
+
}
|
|
105
|
+
),
|
|
106
|
+
/* @__PURE__ */ jsx(FormHelperText, { error: Boolean(errorText), children: errorText ?? helperText })
|
|
107
|
+
] }) });
|
|
108
|
+
}
|
|
109
|
+
export {
|
|
110
|
+
FileInput as default
|
|
111
|
+
};
|
|
112
|
+
//# sourceMappingURL=FileInput.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FileInput.js","sources":["../../src/upload/FileInput.jsx"],"sourcesContent":["// Un archivo del tamaño de un campo, para un formulario (la constancia fiscal de un cliente, el contrato firmado): la\n// etiqueta como los demás campos, el botón «Elegir archivo», el elegido en su chip con la × y abajo su ayuda o su error.\n// Dentro de una FormSection se vuelve renglón, como los demás campos. También se puede soltar el archivo encima.\n//\n// Solo ELIGE (controlado: value / onChange), con las mismas reglas que la zona grande (accept, maxSize; useFilePicker):\n// enviarlo es de la pantalla. value puede ser un archivo nuevo (File) o uno que ya está guardado ({name, size?}), para\n// editar un registro que ya lo tiene.\n//\n// Huecos: template («Descargar la plantilla» junto al botón), actions (botones suyos en el mismo renglón), emptyText,\n// buttonLabel y texts. Con formik: FormFileField (/forms); con react-hook-form, el del mismo nombre (/react-hook-form).\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaFileInput con defaultProps y\n// styleOverrides de sus partes: root, button, fileChip, empty.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useId} from 'react';\nimport Button from '@mui/material/Button';\nimport Chip from '@mui/material/Chip';\nimport FormHelperText from '@mui/material/FormHelperText';\nimport Stack from '@mui/material/Stack';\nimport Typography from '@mui/material/Typography';\nimport AttachFileRoundedIcon from '@mui/icons-material/AttachFileRounded';\n\nimport {translateValidationMessage} from '@/forms/validationMessages.js';\nimport FieldFrame from '@/inputs/FieldFrame.jsx';\nimport {piecePart, usePieceProps} from '@/theme/themePieces.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\nimport FileChip from '@/upload/FileChip.jsx';\nimport TemplateDownloadButton from '@/upload/TemplateDownloadButton.jsx';\nimport {useFilePicker} from '@/upload/useFilePicker.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaFileInput)\nconst THEME_NAME = 'CalcitaFileInput';\nconst FileInputRoot = piecePart(THEME_NAME, 'Root', Stack);\nconst FileInputButton = piecePart(THEME_NAME, 'Button', Button);\nconst FileInputChip = piecePart(THEME_NAME, 'FileChip', Chip);\nconst FileInputEmpty = piecePart(THEME_NAME, 'Empty', Typography);\n\n/**\n * @param {object} props\n * @param {string} [props.name]\n * @param {import('react').ReactNode} [props.label] ya traducida\n * @param {File|{name: string, size?: number}|null} [props.value] el archivo nuevo o el que ya está guardado\n * @param {(file: File|null) => void} props.onChange null al quitarlo\n * @param {() => void} [props.onBlur] se llama al elegir o quitar (para que el formulario lo marque como tocado)\n * @param {string|string[]} [props.accept] 'excel' | ['pdf', 'image'] | '.xlsx,.xls' | 'image/*'\n * @param {number|string} [props.maxSize] bytes o '10 MB'\n * @param {string|{key: string, values?: object}} [props.error]\n * @param {string} [props.helperText] ya traducida\n * @param {string} [props.buttonLabel] ya traducido; por omisión «Elegir archivo»\n * @param {string} [props.emptyText] ya traducido; por omisión «Ningún archivo elegido»\n * @param {boolean} [props.disabled]\n * @param {boolean} [props.busy] candado mientras se sube: no se puede elegir, soltar ni quitar\n * @param {(file: File, reason: string) => void} [props.onReject] reason es uno de FILE_REJECTION; sin él, un aviso\n * @param {Parameters<typeof import('@/upload/TemplateDownloadButton.jsx').default>[0]} [props.template] «Descargar la\n * plantilla» junto al botón\n * @param {import('react').ReactNode} [props.actions] botones suyos en el mismo renglón\n * @param {object} [props.sx] ajustes del renglón\n * @param {object} [props.texts] los textos que cambian, con la forma de `common` en los textos del paquete\n */\nexport default function FileInput(props) {\n const {\n name, label, value = null, onChange, onBlur, accept, maxSize, error, helperText = ' ', buttonLabel, emptyText, disabled = false,\n busy = false, onReject, template, actions, sx, texts,\n ...unknownProps\n } = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const {t} = useToolkitTranslation('common', texts);\n const generatedId = useId();\n const buttonId = `${name ?? 'file'}-${generatedId}`;\n const errorText = translateValidationMessage(t, error);\n const isLocked = disabled || busy;\n const change = (file) => {\n onChange(file);\n onBlur?.();\n };\n const picker = useFilePicker({accept, maxSize, disabled: isLocked, onAccept: change, onReject, texts});\n\n return (\n <FieldFrame inputId={buttonId} label={label}>\n <div>\n <FileInputRoot\n direction=\"row\"\n aria-busy={busy}\n {...picker.dropHandlers}\n ownSx={{\n alignItems: 'center', flexWrap: 'wrap', gap: 1, minHeight: 40, borderRadius: 1, px: 0.5, mx: -0.5,\n outline: picker.isDragging ? 2 : 0, outlineStyle: 'dashed', outlineColor: 'primary.main',\n bgcolor: picker.isDragging ? 'action.hover' : 'transparent',\n ...sx,\n }}\n >\n <input {...picker.inputProps} name={name} aria-hidden=\"true\" tabIndex={-1} />\n <FileInputButton id={buttonId} variant=\"outlined\" size=\"small\" startIcon={<AttachFileRoundedIcon />} onClick={picker.open}\n disabled={isLocked} ownSx={{flexShrink: 0}}>\n {buttonLabel ?? t('upload.choose')}\n </FileInputButton>\n {value ? (\n <FileChip file={value} ChipComponent={FileInputChip} removeLabel={t('upload.remove', {name: value.name})}\n onRemove={isLocked ? undefined : () => change(null)} />\n ) : (\n <FileInputEmpty variant=\"body2\" color=\"text.secondary\">{emptyText ?? t('upload.empty')}</FileInputEmpty>\n )}\n {template && <TemplateDownloadButton size=\"small\" texts={texts} {...template} />}\n {actions}\n </FileInputRoot>\n <FormHelperText error={Boolean(errorText)}>{errorText ?? helperText}</FormHelperText>\n </div>\n </FieldFrame>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAiCA,MAAM,aAAa;AACnB,MAAM,gBAAgB,UAAU,YAAY,QAAQ,KAAK;AACzD,MAAM,kBAAkB,UAAU,YAAY,UAAU,MAAM;AAC9D,MAAM,gBAAgB,UAAU,YAAY,YAAY,IAAI;AAC5D,MAAM,iBAAiB,UAAU,YAAY,SAAS,UAAU;AAwBhE,SAAwB,UAAU,OAAO;AACrC,QAAM;AAAA,IACF;AAAA,IAAM;AAAA,IAAO,QAAQ;AAAA,IAAM;AAAA,IAAU;AAAA,IAAQ;AAAA,IAAQ;AAAA,IAAS;AAAA,IAAO,aAAa;AAAA,IAAK;AAAA,IAAa;AAAA,IAAW,WAAW;AAAA,IAC1H,OAAO;AAAA,IAAO;AAAA,IAAU;AAAA,IAAU;AAAA,IAAS;AAAA,IAAI;AAAA,IAC/C,GAAG;AAAA,EAAA,IACH,cAAc,OAAO,UAAU;AACnC,mBAAiB,YAAY,YAAY;AACzC,QAAM,EAAC,EAAA,IAAK,sBAAsB,UAAU,KAAK;AACjD,QAAM,cAAc,MAAA;AACpB,QAAM,WAAW,GAAG,QAAQ,MAAM,IAAI,WAAW;AACjD,QAAM,YAAY,2BAA2B,GAAG,KAAK;AACrD,QAAM,WAAW,YAAY;AAC7B,QAAM,SAAS,CAAC,SAAS;AACrB,aAAS,IAAI;AACb,aAAA;AAAA,EACJ;AACA,QAAM,SAAS,cAAc,EAAC,QAAQ,SAAS,UAAU,UAAU,UAAU,QAAQ,UAAU,MAAA,CAAM;AAErG,6BACK,YAAA,EAAW,SAAS,UAAU,OAC3B,+BAAC,OAAA,EACG,UAAA;AAAA,IAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACG,WAAU;AAAA,QACV,aAAW;AAAA,QACV,GAAG,OAAO;AAAA,QACX,OAAO;AAAA,UACH,YAAY;AAAA,UAAU,UAAU;AAAA,UAAQ,KAAK;AAAA,UAAG,WAAW;AAAA,UAAI,cAAc;AAAA,UAAG,IAAI;AAAA,UAAK,IAAI;AAAA,UAC7F,SAAS,OAAO,aAAa,IAAI;AAAA,UAAG,cAAc;AAAA,UAAU,cAAc;AAAA,UAC1E,SAAS,OAAO,aAAa,iBAAiB;AAAA,UAC9C,GAAG;AAAA,QAAA;AAAA,QAGP,UAAA;AAAA,UAAA,oBAAC,SAAA,EAAO,GAAG,OAAO,YAAY,MAAY,eAAY,QAAO,UAAU,IAAI;AAAA,UAC3E;AAAA,YAAC;AAAA,YAAA;AAAA,cAAgB,IAAI;AAAA,cAAU,SAAQ;AAAA,cAAW,MAAK;AAAA,cAAQ,+BAAY,uBAAA,EAAsB;AAAA,cAAI,SAAS,OAAO;AAAA,cACpG,UAAU;AAAA,cAAU,OAAO,EAAC,YAAY,EAAA;AAAA,cACpD,UAAA,eAAe,EAAE,eAAe;AAAA,YAAA;AAAA,UAAA;AAAA,UAEpC,QACG;AAAA,YAAC;AAAA,YAAA;AAAA,cAAS,MAAM;AAAA,cAAO,eAAe;AAAA,cAAe,aAAa,EAAE,iBAAiB,EAAC,MAAM,MAAM,MAAK;AAAA,cAC7F,UAAU,WAAW,SAAY,MAAM,OAAO,IAAI;AAAA,YAAA;AAAA,UAAA,IAE5D,oBAAC,gBAAA,EAAe,SAAQ,SAAQ,OAAM,kBAAkB,UAAA,aAAa,EAAE,cAAc,EAAA,CAAE;AAAA,UAE1F,YAAY,oBAAC,wBAAA,EAAuB,MAAK,SAAQ,OAAe,GAAG,UAAU;AAAA,UAC7E;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,wBAEJ,gBAAA,EAAe,OAAO,QAAQ,SAAS,GAAI,uBAAa,WAAA,CAAW;AAAA,EAAA,EAAA,CACxE,EAAA,CACJ;AAER;"}
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useId, useState, useRef, useEffect } from "react";
|
|
4
|
+
import { useMachine } from "@xstate/react";
|
|
5
|
+
import Button from "@mui/material/Button";
|
|
6
|
+
import Chip from "@mui/material/Chip";
|
|
7
|
+
import CircularProgress from "@mui/material/CircularProgress";
|
|
8
|
+
import FormHelperText from "@mui/material/FormHelperText";
|
|
9
|
+
import IconButton from "@mui/material/IconButton";
|
|
10
|
+
import Stack from "@mui/material/Stack";
|
|
11
|
+
import Tooltip from "@mui/material/Tooltip";
|
|
12
|
+
import Typography from "@mui/material/Typography";
|
|
13
|
+
import CheckCircleRoundedIcon from "@mui/icons-material/CheckCircleRounded";
|
|
14
|
+
import CloseRoundedIcon from "@mui/icons-material/CloseRounded";
|
|
15
|
+
import FileDownloadOutlinedIcon from "@mui/icons-material/FileDownloadOutlined";
|
|
16
|
+
import FileUploadOutlinedIcon from "@mui/icons-material/FileUploadOutlined";
|
|
17
|
+
import { startDownload } from "../downloads/downloadQueue.js";
|
|
18
|
+
import { translateValidationMessage } from "../forms/validationMessages.js";
|
|
19
|
+
import { newIdentifier } from "../identifiers.js";
|
|
20
|
+
import FieldFrame from "../inputs/FieldFrame.js";
|
|
21
|
+
import { fieldErrorsOf } from "../problems.js";
|
|
22
|
+
import { piecePart, usePieceProps } from "../theme/themePieces.js";
|
|
23
|
+
import { useFormatters } from "../toolkit/useFormatters.js";
|
|
24
|
+
import { useToolkitTranslation } from "../toolkit/useToolkitTranslation.js";
|
|
25
|
+
import { warnUnknownProps } from "../toolkit/unknownProps.js";
|
|
26
|
+
import { FILE_REJECTION } from "./fileRules.js";
|
|
27
|
+
import { transferProgressOf } from "./transferProgress.js";
|
|
28
|
+
import { uploadMachine, buildUploadActors } from "./uploadMachine.js";
|
|
29
|
+
import { useFilePicker } from "./useFilePicker.js";
|
|
30
|
+
const FILE_UPLOAD_STATUS = Object.freeze({
|
|
31
|
+
pending: "pending",
|
|
32
|
+
uploading: "uploading",
|
|
33
|
+
processing: "processing",
|
|
34
|
+
uploaded: "uploaded",
|
|
35
|
+
failed: "failed"
|
|
36
|
+
});
|
|
37
|
+
const STATUS_COLORS = Object.freeze({
|
|
38
|
+
[FILE_UPLOAD_STATUS.pending]: "default",
|
|
39
|
+
[FILE_UPLOAD_STATUS.uploading]: "info",
|
|
40
|
+
[FILE_UPLOAD_STATUS.processing]: "info",
|
|
41
|
+
[FILE_UPLOAD_STATUS.uploaded]: "success",
|
|
42
|
+
[FILE_UPLOAD_STATUS.failed]: "error"
|
|
43
|
+
});
|
|
44
|
+
const PROGRESS_SEPARATOR = " · ";
|
|
45
|
+
const THEME_NAME = "CalcitaFileUploadInput";
|
|
46
|
+
const FileUploadRoot = piecePart(THEME_NAME, "Root", Stack);
|
|
47
|
+
const FileUploadName = piecePart(THEME_NAME, "FileName", Typography);
|
|
48
|
+
const FileUploadStatus = piecePart(THEME_NAME, "Status", Chip);
|
|
49
|
+
const FileUploadButton = piecePart(THEME_NAME, "Button", Button);
|
|
50
|
+
const FileUploadHelper = piecePart(THEME_NAME, "Helper", FormHelperText);
|
|
51
|
+
function uploadErrorTextOf(error) {
|
|
52
|
+
const problem = (
|
|
53
|
+
/** @type {{problem?: object, message?: string}} */
|
|
54
|
+
error?.problem
|
|
55
|
+
);
|
|
56
|
+
const firstFieldMessage = Object.values(fieldErrorsOf(problem ?? null)).flat()[0];
|
|
57
|
+
return firstFieldMessage ?? problem?.detail ?? null;
|
|
58
|
+
}
|
|
59
|
+
function FileUploadInput(props) {
|
|
60
|
+
const {
|
|
61
|
+
name,
|
|
62
|
+
label,
|
|
63
|
+
value = "",
|
|
64
|
+
onChange,
|
|
65
|
+
onBlur,
|
|
66
|
+
upload,
|
|
67
|
+
idOf = (result2) => result2?.id,
|
|
68
|
+
uploadedFile = null,
|
|
69
|
+
download,
|
|
70
|
+
accept,
|
|
71
|
+
maxSize,
|
|
72
|
+
limitsText,
|
|
73
|
+
error,
|
|
74
|
+
helperText = " ",
|
|
75
|
+
disabled = false,
|
|
76
|
+
actions,
|
|
77
|
+
onUploaded,
|
|
78
|
+
onUploadFailed,
|
|
79
|
+
onCancel,
|
|
80
|
+
onRemove,
|
|
81
|
+
onStatusChange,
|
|
82
|
+
sx,
|
|
83
|
+
texts,
|
|
84
|
+
...unknownProps
|
|
85
|
+
} = usePieceProps(props, THEME_NAME);
|
|
86
|
+
warnUnknownProps(THEME_NAME, unknownProps);
|
|
87
|
+
const { t } = useToolkitTranslation("common", texts);
|
|
88
|
+
const { formatFileSize } = useFormatters();
|
|
89
|
+
const generatedId = useId();
|
|
90
|
+
const inputId = `${name ?? "file-upload"}-${generatedId}`;
|
|
91
|
+
const labelId = `${inputId}-label`;
|
|
92
|
+
const buttonTextId = `${inputId}-button`;
|
|
93
|
+
const [chosenFile, setChosenFile] = useState(null);
|
|
94
|
+
const [rejectedText, setRejectedText] = useState(null);
|
|
95
|
+
const startedAtRef = useRef(0);
|
|
96
|
+
const [uploadSnapshot, sendToUpload] = useMachine(uploadMachine.provide({
|
|
97
|
+
actors: buildUploadActors(({ file, idempotencyKey }, onProgress, signal) => upload(file, {
|
|
98
|
+
signal,
|
|
99
|
+
idempotencyKey,
|
|
100
|
+
onUploadProgress: (transfer2) => {
|
|
101
|
+
const total = transfer2.total ?? file.size;
|
|
102
|
+
onProgress(total ? Math.round(transfer2.loaded * 100 / total) : 0, { ...transfer2, total });
|
|
103
|
+
}
|
|
104
|
+
}))
|
|
105
|
+
}));
|
|
106
|
+
const { progress, transfer, result, problem } = uploadSnapshot.context;
|
|
107
|
+
const isSending = uploadSnapshot.matches({ uploading: "sending" });
|
|
108
|
+
const isProcessing = uploadSnapshot.matches({ uploading: "processing" });
|
|
109
|
+
const isUploading = isSending || isProcessing;
|
|
110
|
+
const isFailed = uploadSnapshot.matches("failed") || Boolean(rejectedText);
|
|
111
|
+
const isUploaded = Boolean(value) && !isUploading && !isFailed;
|
|
112
|
+
let status = FILE_UPLOAD_STATUS.pending;
|
|
113
|
+
if (isSending) {
|
|
114
|
+
status = FILE_UPLOAD_STATUS.uploading;
|
|
115
|
+
} else if (isProcessing) {
|
|
116
|
+
status = FILE_UPLOAD_STATUS.processing;
|
|
117
|
+
} else if (isFailed) {
|
|
118
|
+
status = FILE_UPLOAD_STATUS.failed;
|
|
119
|
+
} else if (isUploaded) {
|
|
120
|
+
status = FILE_UPLOAD_STATUS.uploaded;
|
|
121
|
+
}
|
|
122
|
+
useEffect(() => {
|
|
123
|
+
if (!result) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
onChange(idOf(result) ?? "");
|
|
127
|
+
onUploaded?.(result);
|
|
128
|
+
}, [result]);
|
|
129
|
+
useEffect(() => {
|
|
130
|
+
if (problem) {
|
|
131
|
+
onUploadFailed?.(problem);
|
|
132
|
+
}
|
|
133
|
+
}, [problem]);
|
|
134
|
+
useEffect(() => {
|
|
135
|
+
onStatusChange?.(status);
|
|
136
|
+
}, [status]);
|
|
137
|
+
useEffect(() => {
|
|
138
|
+
if (!value && uploadSnapshot.matches("done")) {
|
|
139
|
+
setChosenFile(null);
|
|
140
|
+
sendToUpload({ type: "RESET" });
|
|
141
|
+
}
|
|
142
|
+
}, [value]);
|
|
143
|
+
const startUpload = (file) => {
|
|
144
|
+
onBlur?.();
|
|
145
|
+
setRejectedText(null);
|
|
146
|
+
setChosenFile(file);
|
|
147
|
+
onChange("");
|
|
148
|
+
startedAtRef.current = Date.now();
|
|
149
|
+
sendToUpload({ type: "UPLOAD", input: { file, idempotencyKey: newIdentifier() } });
|
|
150
|
+
};
|
|
151
|
+
const picker = useFilePicker({
|
|
152
|
+
accept,
|
|
153
|
+
maxSize,
|
|
154
|
+
disabled: disabled || isUploading,
|
|
155
|
+
onAccept: startUpload,
|
|
156
|
+
texts,
|
|
157
|
+
onReject: (file, reason) => {
|
|
158
|
+
onBlur?.();
|
|
159
|
+
setRejectedText(reason === FILE_REJECTION.size ? t("upload.tooLarge", { name: file.name, max: formatFileSize(picker.maxBytes) }) : t("upload.invalidType", { name: file.name, accept: picker.resolvedAccept }));
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
const remove = () => {
|
|
163
|
+
setChosenFile(null);
|
|
164
|
+
setRejectedText(null);
|
|
165
|
+
onChange("");
|
|
166
|
+
onBlur?.();
|
|
167
|
+
sendToUpload({ type: "RESET" });
|
|
168
|
+
onRemove?.();
|
|
169
|
+
};
|
|
170
|
+
const cancel = () => {
|
|
171
|
+
sendToUpload({ type: "CANCEL" });
|
|
172
|
+
onCancel?.();
|
|
173
|
+
};
|
|
174
|
+
const shownFile = chosenFile ?? (value ? uploadedFile : null);
|
|
175
|
+
const statusLabel = status === FILE_UPLOAD_STATUS.uploading ? t("upload.status.uploading", { percent: progress ?? 0 }) : t(`upload.status.${status}`);
|
|
176
|
+
const validationText = translateValidationMessage(t, error);
|
|
177
|
+
const errorText = rejectedText ?? (uploadSnapshot.matches("failed") ? uploadErrorTextOf(problem) ?? t("upload.failed") : null) ?? (isUploading ? null : validationText);
|
|
178
|
+
let progressText = null;
|
|
179
|
+
if (isProcessing) {
|
|
180
|
+
progressText = t("upload.processing");
|
|
181
|
+
} else if (isSending) {
|
|
182
|
+
const now = transfer ? transferProgressOf(transfer, startedAtRef.current) : null;
|
|
183
|
+
progressText = now ? [
|
|
184
|
+
t("upload.amount", { loaded: formatFileSize(now.loaded), total: formatFileSize(now.total) }),
|
|
185
|
+
now.bytesPerSecond ? t("upload.speed", { size: formatFileSize(now.bytesPerSecond) }) : null,
|
|
186
|
+
now.remainingSeconds != null ? t("upload.remaining", { count: now.remainingSeconds }) : null
|
|
187
|
+
].filter(Boolean).join(PROGRESS_SEPARATOR) : t("upload.starting");
|
|
188
|
+
} else if (uploadSnapshot.matches("cancelled")) {
|
|
189
|
+
progressText = t("upload.cancelled");
|
|
190
|
+
}
|
|
191
|
+
const buttonHint = limitsText ?? (picker.maxBytes ? t("upload.maxSizeHint", { max: formatFileSize(picker.maxBytes) }) : "");
|
|
192
|
+
const downloadUploaded = () => startDownload({
|
|
193
|
+
label: shownFile?.name ?? t("upload.download"),
|
|
194
|
+
request: (transferOptions) => download(value, transferOptions)
|
|
195
|
+
});
|
|
196
|
+
const row = /* @__PURE__ */ jsxs("div", { children: [
|
|
197
|
+
/* @__PURE__ */ jsxs(
|
|
198
|
+
FileUploadRoot,
|
|
199
|
+
{
|
|
200
|
+
direction: "row",
|
|
201
|
+
"aria-busy": isUploading,
|
|
202
|
+
...picker.dropHandlers,
|
|
203
|
+
ownSx: {
|
|
204
|
+
// en el teléfono el nombre baja a su renglón; en escritorio se corta con «…» y el botón no se mueve
|
|
205
|
+
alignItems: "center",
|
|
206
|
+
justifyContent: "flex-end",
|
|
207
|
+
flexWrap: { xs: "wrap", sm: "nowrap" },
|
|
208
|
+
gap: 1,
|
|
209
|
+
minWidth: 0,
|
|
210
|
+
minHeight: 40,
|
|
211
|
+
px: 0.5,
|
|
212
|
+
mx: -0.5,
|
|
213
|
+
borderRadius: 1,
|
|
214
|
+
outline: picker.isDragging ? 2 : 0,
|
|
215
|
+
outlineStyle: "dashed",
|
|
216
|
+
outlineColor: "primary.main",
|
|
217
|
+
bgcolor: picker.isDragging ? "action.hover" : "transparent",
|
|
218
|
+
...sx
|
|
219
|
+
},
|
|
220
|
+
children: [
|
|
221
|
+
/* @__PURE__ */ jsx("input", { ...picker.inputProps, id: inputId, name, "aria-labelledby": label ? labelId : void 0 }),
|
|
222
|
+
shownFile && // el nombre cortado si es largo y completo al pasar el cursor; en el teléfono baja a su renglón
|
|
223
|
+
/* @__PURE__ */ jsx(Tooltip, { title: shownFile.size != null ? `${shownFile.name} · ${formatFileSize(shownFile.size)}` : shownFile.name, children: /* @__PURE__ */ jsx(
|
|
224
|
+
FileUploadName,
|
|
225
|
+
{
|
|
226
|
+
variant: "caption",
|
|
227
|
+
color: "text.secondary",
|
|
228
|
+
noWrap: true,
|
|
229
|
+
ownSx: { minWidth: 0, flexShrink: 1, flexBasis: { xs: "100%", sm: "auto" }, textAlign: { xs: "right", sm: "left" } },
|
|
230
|
+
children: shownFile.name
|
|
231
|
+
}
|
|
232
|
+
) }),
|
|
233
|
+
actions,
|
|
234
|
+
/* @__PURE__ */ jsx(
|
|
235
|
+
FileUploadStatus,
|
|
236
|
+
{
|
|
237
|
+
size: "small",
|
|
238
|
+
color: STATUS_COLORS[status],
|
|
239
|
+
variant: status === FILE_UPLOAD_STATUS.pending ? "filled" : "outlined",
|
|
240
|
+
label: statusLabel,
|
|
241
|
+
icon: isUploading && /* @__PURE__ */ jsx(CircularProgress, { size: 12, color: "inherit", variant: isProcessing ? "indeterminate" : "determinate", value: progress ?? 0 }) || isUploaded && /* @__PURE__ */ jsx(CheckCircleRoundedIcon, {}) || void 0,
|
|
242
|
+
ownSx: { flexShrink: 0 }
|
|
243
|
+
}
|
|
244
|
+
),
|
|
245
|
+
isUploading ? /* @__PURE__ */ jsx(FileUploadButton, { size: "small", color: "inherit", onClick: cancel, ownSx: { flexShrink: 0 }, children: t("upload.cancel") }) : (
|
|
246
|
+
// describeChild: lo que acepta se lee como descripción; el nombre sigue siendo «Cargar» y el documento
|
|
247
|
+
/* @__PURE__ */ jsx(Tooltip, { title: buttonHint, describeChild: true, children: /* @__PURE__ */ jsx(
|
|
248
|
+
FileUploadButton,
|
|
249
|
+
{
|
|
250
|
+
size: "small",
|
|
251
|
+
variant: isUploaded ? "outlined" : "contained",
|
|
252
|
+
startIcon: /* @__PURE__ */ jsx(FileUploadOutlinedIcon, {}),
|
|
253
|
+
disabled,
|
|
254
|
+
onClick: picker.open,
|
|
255
|
+
"aria-labelledby": label ? `${buttonTextId} ${labelId}` : void 0,
|
|
256
|
+
ownSx: { flexShrink: 0 },
|
|
257
|
+
children: /* @__PURE__ */ jsx("span", { id: buttonTextId, children: isUploaded ? t("upload.replace") : t("upload.upload") })
|
|
258
|
+
}
|
|
259
|
+
) })
|
|
260
|
+
),
|
|
261
|
+
isUploaded && download && /* @__PURE__ */ jsx(Tooltip, { title: t("upload.download"), children: /* @__PURE__ */ jsx(IconButton, { size: "small", "aria-label": t("upload.download"), onClick: downloadUploaded, children: /* @__PURE__ */ jsx(FileDownloadOutlinedIcon, { fontSize: "small" }) }) }),
|
|
262
|
+
isUploaded && !disabled && /* @__PURE__ */ jsx(Tooltip, { title: t("upload.remove", { name: shownFile?.name ?? "" }), children: /* @__PURE__ */ jsx(IconButton, { size: "small", "aria-label": t("upload.remove", { name: shownFile?.name ?? "" }), onClick: remove, children: /* @__PURE__ */ jsx(CloseRoundedIcon, { fontSize: "small" }) }) })
|
|
263
|
+
]
|
|
264
|
+
}
|
|
265
|
+
),
|
|
266
|
+
/* @__PURE__ */ jsx(
|
|
267
|
+
FileUploadHelper,
|
|
268
|
+
{
|
|
269
|
+
error: Boolean(errorText),
|
|
270
|
+
role: errorText ? "alert" : progressText ? "status" : void 0,
|
|
271
|
+
ownSx: { textAlign: "right", mx: 0 },
|
|
272
|
+
children: errorText ?? progressText ?? helperText
|
|
273
|
+
}
|
|
274
|
+
)
|
|
275
|
+
] });
|
|
276
|
+
if (!label) {
|
|
277
|
+
return row;
|
|
278
|
+
}
|
|
279
|
+
return /* @__PURE__ */ jsx(FieldFrame, { inputId, labelId, label, children: row });
|
|
280
|
+
}
|
|
281
|
+
export {
|
|
282
|
+
FILE_UPLOAD_STATUS,
|
|
283
|
+
FileUploadInput as default
|
|
284
|
+
};
|
|
285
|
+
//# sourceMappingURL=FileUploadInput.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FileUploadInput.js","sources":["../../src/upload/FileUploadInput.jsx"],"sourcesContent":["// El cargador MINI: un archivo que se SUBE en cuanto se elige, del tamaño de un renglón. Para una lista de documentos (el\n// expediente de un cliente, lo que pide una solicitud): a la izquierda el nombre del documento (la etiqueta del renglón) y a\n// la derecha el nombre del archivo, su estado y el botón.\n//\n// estado Por cargar · Subiendo 40 % · Procesando · Cargado · No se cargó\n// botón Cargar (lleno) → ya cargado, Sustituir (con borde); mientras sube, Cancelar\n// íconos bajar el que ya subió (si la pantalla da download) y quitarlo\n// abajo el avance en palabras («1.2 MB de 3 MB · 350 kB/s · faltan 5 segundos»; el porcentaje ya va en el estado) o el\n// motivo del error\n//\n// Al formulario llega SOLO lo que el servidor da para reconocer el archivo (su id, por omisión result.id): el formulario\n// manda ese id junto con lo demás y el backend lo liga a su registro. Mientras sube, el valor es '' (no se puede guardar\n// con el id del anterior). Subir es de la pantalla: `upload` recibe el archivo con su señal para cancelar, su avance y una\n// llave por archivo elegido (si la red se cae a media subida, el servidor no deja dos copias). Con uploadWithProgress o\n// con axios, igual.\n//\n// Lo que es del negocio (Aprobado, Falta información) no va aquí: la pantalla lo pinta junto, con `actions`, y sabe en\n// qué va cada uno con onStatusChange (para «3 de 5 cargados»). Tipo y peso se revisan ANTES de subir (accept, maxSize): no\n// se gastan minutos subiendo lo que el servidor va a rechazar; el motivo sale abajo, en rojo.\n//\n// Dentro de una FormSection se vuelve renglón, como los demás campos. También se puede soltar el archivo encima.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaFileUploadInput con defaultProps y\n// styleOverrides de sus partes: root, fileName, status, button, helper.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useEffect, useId, useRef, useState} from 'react';\nimport {useMachine} from '@xstate/react';\nimport Button from '@mui/material/Button';\nimport Chip from '@mui/material/Chip';\nimport CircularProgress from '@mui/material/CircularProgress';\nimport FormHelperText from '@mui/material/FormHelperText';\nimport IconButton from '@mui/material/IconButton';\nimport Stack from '@mui/material/Stack';\nimport Tooltip from '@mui/material/Tooltip';\nimport Typography from '@mui/material/Typography';\nimport CheckCircleRoundedIcon from '@mui/icons-material/CheckCircleRounded';\nimport CloseRoundedIcon from '@mui/icons-material/CloseRounded';\nimport FileDownloadOutlinedIcon from '@mui/icons-material/FileDownloadOutlined';\nimport FileUploadOutlinedIcon from '@mui/icons-material/FileUploadOutlined';\n\nimport {startDownload} from '@/downloads/downloadQueue.js';\nimport {translateValidationMessage} from '@/forms/validationMessages.js';\nimport {newIdentifier} from '@/identifiers.js';\nimport FieldFrame from '@/inputs/FieldFrame.jsx';\nimport {fieldErrorsOf} from '@/problems.js';\nimport {piecePart, usePieceProps} from '@/theme/themePieces.js';\nimport {useFormatters} from '@/toolkit/useFormatters.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\nimport {FILE_REJECTION} from '@/upload/fileRules.js';\nimport {transferProgressOf} from '@/upload/transferProgress.js';\nimport {buildUploadActors, uploadMachine} from '@/upload/uploadMachine.js';\nimport {useFilePicker} from '@/upload/useFilePicker.js';\n\n/** En qué va el archivo de un renglón (onStatusChange). */\nexport const FILE_UPLOAD_STATUS = Object.freeze({\n pending: 'pending', uploading: 'uploading', processing: 'processing', uploaded: 'uploaded', failed: 'failed',\n});\n\n// el color de la etiqueta de cada estado (el color es información)\nconst STATUS_COLORS = Object.freeze({\n [FILE_UPLOAD_STATUS.pending]: 'default',\n [FILE_UPLOAD_STATUS.uploading]: 'info',\n [FILE_UPLOAD_STATUS.processing]: 'info',\n [FILE_UPLOAD_STATUS.uploaded]: 'success',\n [FILE_UPLOAD_STATUS.failed]: 'error',\n});\n\n// entre las partes del avance en palabras\nconst PROGRESS_SEPARATOR = ' · ';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaFileUploadInput)\nconst THEME_NAME = 'CalcitaFileUploadInput';\nconst FileUploadRoot = piecePart(THEME_NAME, 'Root', Stack);\nconst FileUploadName = piecePart(THEME_NAME, 'FileName', Typography);\nconst FileUploadStatus = piecePart(THEME_NAME, 'Status', Chip);\nconst FileUploadButton = piecePart(THEME_NAME, 'Button', Button);\nconst FileUploadHelper = piecePart(THEME_NAME, 'Helper', FormHelperText);\n\n/**\n * El texto de un error de la subida: el del campo si el servidor mandó un 422 con su lista, si no su detail, y si no hubo\n * respuesta, el genérico.\n *\n * @param {unknown} error\n * @returns {string|null}\n */\nfunction uploadErrorTextOf(error) {\n const problem = /** @type {{problem?: object, message?: string}} */ (error)?.problem;\n const firstFieldMessage = Object.values(fieldErrorsOf(problem ?? null)).flat()[0];\n return firstFieldMessage ?? problem?.detail ?? null;\n}\n\n/**\n * @param {object} props\n * @param {string} [props.name]\n * @param {import('react').ReactNode} [props.label] ya traducida: el nombre del documento; sin ella, el renglón va solo\n * @param {string|null} [props.value] lo que el servidor dio para reconocer el archivo subido ('' o null = ninguno)\n * @param {(value: string) => void} props.onChange recibe el id al terminar de subir, '' al quitarlo o mientras sube otro\n * @param {() => void} [props.onBlur] se llama al elegir o quitar (para que el formulario lo marque como tocado)\n * @param {(file: File, options: {signal: AbortSignal, idempotencyKey: string, onUploadProgress: (transfer: {loaded: number, total: number, rate?: number, estimated?: number}) => void}) => Promise<any>} props.upload\n * sube el archivo (con uploadWithProgress, axios o el cliente del proyecto) y regresa lo que conteste el servidor\n * @param {(result: any) => string} [props.idOf] de la respuesta, lo que se guarda en el formulario; por omisión result.id\n * @param {{name: string, size?: number}|null} [props.uploadedFile] el que ya estaba subido, para enseñar su nombre al editar\n * @param {(value: string, transferOptions: object) => Promise<{blob: Blob, fileName: string}>} [props.download] baja el\n * que ya subió (pasa por el panel de descargas); sin él no sale el ícono\n * @param {string|string[]} [props.accept] 'pdf' | ['pdf', 'image'] | '.xlsx,.xls' | 'image/*'\n * @param {number|string} [props.maxSize] bytes o '10 MB'\n * @param {string} [props.limitsText] ya traducido: qué acepta, en palabras («PDF o foto, hasta 5 MB»); sale al pasar sobre\n * el botón. Por omisión solo el peso máximo\n * @param {string|{key: string, values?: object}} [props.error]\n * @param {string} [props.helperText] ya traducida\n * @param {boolean} [props.disabled]\n * @param {import('react').ReactNode} [props.actions] lo del negocio junto al estado (Aprobado, Falta información)\n * @param {(result: any) => void} [props.onUploaded] terminó de subir (p. ej. para enseñar el folio de la respuesta)\n * @param {(error: unknown) => void} [props.onUploadFailed] no se pudo subir (el motivo ya sale abajo)\n * @param {() => void} [props.onCancel] la persona canceló a media subida\n * @param {() => void} [props.onRemove] la persona quitó el archivo\n * @param {(status: string) => void} [props.onStatusChange] uno de FILE_UPLOAD_STATUS, cada vez que cambia\n * @param {object} [props.sx] ajustes del renglón\n * @param {object} [props.texts] los textos que cambian, con la forma de `common` en los textos del paquete\n */\nexport default function FileUploadInput(props) {\n const {\n name, label, value = '', onChange, onBlur, upload, idOf = (result) => result?.id, uploadedFile = null, download, accept, maxSize,\n limitsText, error, helperText = ' ', disabled = false, actions, onUploaded, onUploadFailed, onCancel, onRemove, onStatusChange,\n sx, texts,\n ...unknownProps\n } = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const {t} = useToolkitTranslation('common', texts);\n const {formatFileSize} = useFormatters();\n const generatedId = useId();\n const inputId = `${name ?? 'file-upload'}-${generatedId}`;\n const labelId = `${inputId}-label`;\n const buttonTextId = `${inputId}-button`;\n const [chosenFile, setChosenFile] = useState(null);\n const [rejectedText, setRejectedText] = useState(null);\n const startedAtRef = useRef(0);\n const [uploadSnapshot, sendToUpload] = useMachine(uploadMachine.provide({\n actors: buildUploadActors(({file, idempotencyKey}, onProgress, signal) => upload(file, {\n signal,\n idempotencyKey,\n onUploadProgress: (transfer) => {\n const total = transfer.total ?? file.size;\n onProgress(total ? Math.round((transfer.loaded * 100) / total) : 0, {...transfer, total});\n },\n })),\n }));\n const {progress, transfer, result, problem} = uploadSnapshot.context;\n const isSending = uploadSnapshot.matches({uploading: 'sending'});\n const isProcessing = uploadSnapshot.matches({uploading: 'processing'});\n const isUploading = isSending || isProcessing;\n const isFailed = uploadSnapshot.matches('failed') || Boolean(rejectedText);\n const isUploaded = Boolean(value) && !isUploading && !isFailed;\n let status = FILE_UPLOAD_STATUS.pending;\n if (isSending) {\n status = FILE_UPLOAD_STATUS.uploading;\n } else if (isProcessing) {\n status = FILE_UPLOAD_STATUS.processing;\n } else if (isFailed) {\n status = FILE_UPLOAD_STATUS.failed;\n } else if (isUploaded) {\n status = FILE_UPLOAD_STATUS.uploaded;\n }\n\n // al terminar, el formulario se queda con el id; quien usa el campo puede enseñar lo demás de la respuesta\n useEffect(() => {\n if (!result) {\n return;\n }\n onChange(idOf(result) ?? '');\n onUploaded?.(result);\n // OJO: solo cuando llega una respuesta nueva; onChange, idOf y onUploaded cambian en cada render\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [result]);\n useEffect(() => {\n if (problem) {\n onUploadFailed?.(problem);\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [problem]);\n useEffect(() => {\n onStatusChange?.(status);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [status]);\n // si el formulario se limpia (después de guardar), el renglón también regresa a «por cargar»\n useEffect(() => {\n if (!value && uploadSnapshot.matches('done')) {\n setChosenFile(null);\n sendToUpload({type: 'RESET'});\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [value]);\n\n const startUpload = (file) => {\n onBlur?.();\n setRejectedText(null);\n setChosenFile(file);\n // mientras sube, el formulario no tiene archivo: no se puede guardar con el id del anterior\n onChange('');\n startedAtRef.current = Date.now();\n sendToUpload({type: 'UPLOAD', input: {file, idempotencyKey: newIdentifier()}});\n };\n const picker = useFilePicker({\n accept, maxSize, disabled: disabled || isUploading, onAccept: startUpload, texts,\n onReject: (file, reason) => {\n onBlur?.();\n setRejectedText(reason === FILE_REJECTION.size\n ? t('upload.tooLarge', {name: file.name, max: formatFileSize(picker.maxBytes)})\n : t('upload.invalidType', {name: file.name, accept: picker.resolvedAccept}));\n },\n });\n const remove = () => {\n setChosenFile(null);\n setRejectedText(null);\n onChange('');\n onBlur?.();\n sendToUpload({type: 'RESET'});\n onRemove?.();\n };\n const cancel = () => {\n sendToUpload({type: 'CANCEL'});\n onCancel?.();\n };\n\n const shownFile = chosenFile ?? (value ? uploadedFile : null);\n const statusLabel = status === FILE_UPLOAD_STATUS.uploading\n ? t('upload.status.uploading', {percent: progress ?? 0})\n : t(`upload.status.${status}`);\n const validationText = translateValidationMessage(t, error);\n const errorText = rejectedText\n ?? (uploadSnapshot.matches('failed') ? uploadErrorTextOf(problem) ?? t('upload.failed') : null)\n ?? (isUploading ? null : validationText);\n let progressText = null;\n if (isProcessing) {\n progressText = t('upload.processing');\n } else if (isSending) {\n // «1.2 MB de 3 MB · 350 kB/s · faltan 5 segundos»: la velocidad y lo que falta, cuando ya se pueden decir (el\n // porcentaje ya va en la etiqueta del estado)\n const now = transfer ? transferProgressOf(transfer, startedAtRef.current) : null;\n progressText = now ? [\n t('upload.amount', {loaded: formatFileSize(now.loaded), total: formatFileSize(now.total)}),\n now.bytesPerSecond ? t('upload.speed', {size: formatFileSize(now.bytesPerSecond)}) : null,\n now.remainingSeconds != null ? t('upload.remaining', {count: now.remainingSeconds}) : null,\n ].filter(Boolean).join(PROGRESS_SEPARATOR) : t('upload.starting');\n } else if (uploadSnapshot.matches('cancelled')) {\n progressText = t('upload.cancelled');\n }\n const buttonHint = limitsText ?? (picker.maxBytes ? t('upload.maxSizeHint', {max: formatFileSize(picker.maxBytes)}) : '');\n const downloadUploaded = () => startDownload({\n label: shownFile?.name ?? t('upload.download'),\n request: (transferOptions) => download(value, transferOptions),\n });\n\n const row = (\n <div>\n <FileUploadRoot\n direction=\"row\"\n aria-busy={isUploading}\n {...picker.dropHandlers}\n ownSx={{\n // en el teléfono el nombre baja a su renglón; en escritorio se corta con «…» y el botón no se mueve\n alignItems: 'center', justifyContent: 'flex-end', flexWrap: {xs: 'wrap', sm: 'nowrap'}, gap: 1, minWidth: 0, minHeight: 40,\n px: 0.5, mx: -0.5, borderRadius: 1,\n outline: picker.isDragging ? 2 : 0, outlineStyle: 'dashed', outlineColor: 'primary.main',\n bgcolor: picker.isDragging ? 'action.hover' : 'transparent',\n ...sx,\n }}\n >\n <input {...picker.inputProps} id={inputId} name={name} aria-labelledby={label ? labelId : undefined} />\n {shownFile && (\n // el nombre cortado si es largo y completo al pasar el cursor; en el teléfono baja a su renglón\n <Tooltip title={shownFile.size != null ? `${shownFile.name} · ${formatFileSize(shownFile.size)}` : shownFile.name}>\n <FileUploadName variant=\"caption\" color=\"text.secondary\" noWrap\n ownSx={{minWidth: 0, flexShrink: 1, flexBasis: {xs: '100%', sm: 'auto'}, textAlign: {xs: 'right', sm: 'left'}}}>\n {shownFile.name}\n </FileUploadName>\n </Tooltip>\n )}\n {actions}\n <FileUploadStatus\n size=\"small\"\n color={STATUS_COLORS[status]}\n variant={status === FILE_UPLOAD_STATUS.pending ? 'filled' : 'outlined'}\n label={statusLabel}\n icon={(isUploading && (\n <CircularProgress size={12} color=\"inherit\" variant={isProcessing ? 'indeterminate' : 'determinate'} value={progress ?? 0} />\n )) || (isUploaded && <CheckCircleRoundedIcon />) || undefined}\n ownSx={{flexShrink: 0}}\n />\n {isUploading ? (\n <FileUploadButton size=\"small\" color=\"inherit\" onClick={cancel} ownSx={{flexShrink: 0}}>{t('upload.cancel')}</FileUploadButton>\n ) : (\n // describeChild: lo que acepta se lee como descripción; el nombre sigue siendo «Cargar» y el documento\n <Tooltip title={buttonHint} describeChild>\n <FileUploadButton\n size=\"small\"\n variant={isUploaded ? 'outlined' : 'contained'}\n startIcon={<FileUploadOutlinedIcon />}\n disabled={disabled}\n onClick={picker.open}\n aria-labelledby={label ? `${buttonTextId} ${labelId}` : undefined}\n ownSx={{flexShrink: 0}}\n >\n <span id={buttonTextId}>{isUploaded ? t('upload.replace') : t('upload.upload')}</span>\n </FileUploadButton>\n </Tooltip>\n )}\n {isUploaded && download && (\n <Tooltip title={t('upload.download')}>\n <IconButton size=\"small\" aria-label={t('upload.download')} onClick={downloadUploaded}>\n <FileDownloadOutlinedIcon fontSize=\"small\" />\n </IconButton>\n </Tooltip>\n )}\n {isUploaded && !disabled && (\n <Tooltip title={t('upload.remove', {name: shownFile?.name ?? ''})}>\n <IconButton size=\"small\" aria-label={t('upload.remove', {name: shownFile?.name ?? ''})} onClick={remove}>\n <CloseRoundedIcon fontSize=\"small\" />\n </IconButton>\n </Tooltip>\n )}\n </FileUploadRoot>\n {/* abajo: el error manda; si no, el avance; si no, la ayuda de la pantalla */}\n <FileUploadHelper error={Boolean(errorText)} role={errorText ? 'alert' : (progressText ? 'status' : undefined)}\n ownSx={{textAlign: 'right', mx: 0}}>\n {errorText ?? progressText ?? helperText}\n </FileUploadHelper>\n </div>\n );\n\n // sin etiqueta, el renglón va solo (la pantalla pone el título del documento a su manera)\n if (!label) {\n return row;\n }\n return <FieldFrame inputId={inputId} labelId={labelId} label={label}>{row}</FieldFrame>;\n}\n"],"names":["result","transfer"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwDO,MAAM,qBAAqB,OAAO,OAAO;AAAA,EAC5C,SAAS;AAAA,EAAW,WAAW;AAAA,EAAa,YAAY;AAAA,EAAc,UAAU;AAAA,EAAY,QAAQ;AACxG,CAAC;AAGD,MAAM,gBAAgB,OAAO,OAAO;AAAA,EAChC,CAAC,mBAAmB,OAAO,GAAG;AAAA,EAC9B,CAAC,mBAAmB,SAAS,GAAG;AAAA,EAChC,CAAC,mBAAmB,UAAU,GAAG;AAAA,EACjC,CAAC,mBAAmB,QAAQ,GAAG;AAAA,EAC/B,CAAC,mBAAmB,MAAM,GAAG;AACjC,CAAC;AAGD,MAAM,qBAAqB;AAG3B,MAAM,aAAa;AACnB,MAAM,iBAAiB,UAAU,YAAY,QAAQ,KAAK;AAC1D,MAAM,iBAAiB,UAAU,YAAY,YAAY,UAAU;AACnE,MAAM,mBAAmB,UAAU,YAAY,UAAU,IAAI;AAC7D,MAAM,mBAAmB,UAAU,YAAY,UAAU,MAAM;AAC/D,MAAM,mBAAmB,UAAU,YAAY,UAAU,cAAc;AASvE,SAAS,kBAAkB,OAAO;AAC9B,QAAM;AAAA;AAAA,IAA+D,OAAQ;AAAA;AAC7E,QAAM,oBAAoB,OAAO,OAAO,cAAc,WAAW,IAAI,CAAC,EAAE,KAAA,EAAO,CAAC;AAChF,SAAO,qBAAqB,SAAS,UAAU;AACnD;AA+BA,SAAwB,gBAAgB,OAAO;AAC3C,QAAM;AAAA,IACF;AAAA,IAAM;AAAA,IAAO,QAAQ;AAAA,IAAI;AAAA,IAAU;AAAA,IAAQ;AAAA,IAAQ,OAAO,CAACA,YAAWA,SAAQ;AAAA,IAAI,eAAe;AAAA,IAAM;AAAA,IAAU;AAAA,IAAQ;AAAA,IACzH;AAAA,IAAY;AAAA,IAAO,aAAa;AAAA,IAAK,WAAW;AAAA,IAAO;AAAA,IAAS;AAAA,IAAY;AAAA,IAAgB;AAAA,IAAU;AAAA,IAAU;AAAA,IAChH;AAAA,IAAI;AAAA,IACJ,GAAG;AAAA,EAAA,IACH,cAAc,OAAO,UAAU;AACnC,mBAAiB,YAAY,YAAY;AACzC,QAAM,EAAC,EAAA,IAAK,sBAAsB,UAAU,KAAK;AACjD,QAAM,EAAC,eAAA,IAAkB,cAAA;AACzB,QAAM,cAAc,MAAA;AACpB,QAAM,UAAU,GAAG,QAAQ,aAAa,IAAI,WAAW;AACvD,QAAM,UAAU,GAAG,OAAO;AAC1B,QAAM,eAAe,GAAG,OAAO;AAC/B,QAAM,CAAC,YAAY,aAAa,IAAI,SAAS,IAAI;AACjD,QAAM,CAAC,cAAc,eAAe,IAAI,SAAS,IAAI;AACrD,QAAM,eAAe,OAAO,CAAC;AAC7B,QAAM,CAAC,gBAAgB,YAAY,IAAI,WAAW,cAAc,QAAQ;AAAA,IACpE,QAAQ,kBAAkB,CAAC,EAAC,MAAM,kBAAiB,YAAY,WAAW,OAAO,MAAM;AAAA,MACnF;AAAA,MACA;AAAA,MACA,kBAAkB,CAACC,cAAa;AAC5B,cAAM,QAAQA,UAAS,SAAS,KAAK;AACrC,mBAAW,QAAQ,KAAK,MAAOA,UAAS,SAAS,MAAO,KAAK,IAAI,GAAG,EAAC,GAAGA,WAAU,OAAM;AAAA,MAC5F;AAAA,IAAA,CACH,CAAC;AAAA,EAAA,CACL,CAAC;AACF,QAAM,EAAC,UAAU,UAAU,QAAQ,QAAA,IAAW,eAAe;AAC7D,QAAM,YAAY,eAAe,QAAQ,EAAC,WAAW,WAAU;AAC/D,QAAM,eAAe,eAAe,QAAQ,EAAC,WAAW,cAAa;AACrE,QAAM,cAAc,aAAa;AACjC,QAAM,WAAW,eAAe,QAAQ,QAAQ,KAAK,QAAQ,YAAY;AACzE,QAAM,aAAa,QAAQ,KAAK,KAAK,CAAC,eAAe,CAAC;AACtD,MAAI,SAAS,mBAAmB;AAChC,MAAI,WAAW;AACX,aAAS,mBAAmB;AAAA,EAChC,WAAW,cAAc;AACrB,aAAS,mBAAmB;AAAA,EAChC,WAAW,UAAU;AACjB,aAAS,mBAAmB;AAAA,EAChC,WAAW,YAAY;AACnB,aAAS,mBAAmB;AAAA,EAChC;AAGA,YAAU,MAAM;AACZ,QAAI,CAAC,QAAQ;AACT;AAAA,IACJ;AACA,aAAS,KAAK,MAAM,KAAK,EAAE;AAC3B,iBAAa,MAAM;AAAA,EAGvB,GAAG,CAAC,MAAM,CAAC;AACX,YAAU,MAAM;AACZ,QAAI,SAAS;AACT,uBAAiB,OAAO;AAAA,IAC5B;AAAA,EAEJ,GAAG,CAAC,OAAO,CAAC;AACZ,YAAU,MAAM;AACZ,qBAAiB,MAAM;AAAA,EAE3B,GAAG,CAAC,MAAM,CAAC;AAEX,YAAU,MAAM;AACZ,QAAI,CAAC,SAAS,eAAe,QAAQ,MAAM,GAAG;AAC1C,oBAAc,IAAI;AAClB,mBAAa,EAAC,MAAM,SAAQ;AAAA,IAChC;AAAA,EAEJ,GAAG,CAAC,KAAK,CAAC;AAEV,QAAM,cAAc,CAAC,SAAS;AAC1B,aAAA;AACA,oBAAgB,IAAI;AACpB,kBAAc,IAAI;AAElB,aAAS,EAAE;AACX,iBAAa,UAAU,KAAK,IAAA;AAC5B,iBAAa,EAAC,MAAM,UAAU,OAAO,EAAC,MAAM,gBAAgB,cAAA,EAAc,GAAG;AAAA,EACjF;AACA,QAAM,SAAS,cAAc;AAAA,IACzB;AAAA,IAAQ;AAAA,IAAS,UAAU,YAAY;AAAA,IAAa,UAAU;AAAA,IAAa;AAAA,IAC3E,UAAU,CAAC,MAAM,WAAW;AACxB,eAAA;AACA,sBAAgB,WAAW,eAAe,OACpC,EAAE,mBAAmB,EAAC,MAAM,KAAK,MAAM,KAAK,eAAe,OAAO,QAAQ,GAAE,IAC5E,EAAE,sBAAsB,EAAC,MAAM,KAAK,MAAM,QAAQ,OAAO,eAAA,CAAe,CAAC;AAAA,IACnF;AAAA,EAAA,CACH;AACD,QAAM,SAAS,MAAM;AACjB,kBAAc,IAAI;AAClB,oBAAgB,IAAI;AACpB,aAAS,EAAE;AACX,aAAA;AACA,iBAAa,EAAC,MAAM,SAAQ;AAC5B,eAAA;AAAA,EACJ;AACA,QAAM,SAAS,MAAM;AACjB,iBAAa,EAAC,MAAM,UAAS;AAC7B,eAAA;AAAA,EACJ;AAEA,QAAM,YAAY,eAAe,QAAQ,eAAe;AACxD,QAAM,cAAc,WAAW,mBAAmB,YAC5C,EAAE,2BAA2B,EAAC,SAAS,YAAY,GAAE,IACrD,EAAE,iBAAiB,MAAM,EAAE;AACjC,QAAM,iBAAiB,2BAA2B,GAAG,KAAK;AAC1D,QAAM,YAAY,iBACV,eAAe,QAAQ,QAAQ,IAAI,kBAAkB,OAAO,KAAK,EAAE,eAAe,IAAI,UACtF,cAAc,OAAO;AAC7B,MAAI,eAAe;AACnB,MAAI,cAAc;AACd,mBAAe,EAAE,mBAAmB;AAAA,EACxC,WAAW,WAAW;AAGlB,UAAM,MAAM,WAAW,mBAAmB,UAAU,aAAa,OAAO,IAAI;AAC5E,mBAAe,MAAM;AAAA,MACjB,EAAE,iBAAiB,EAAC,QAAQ,eAAe,IAAI,MAAM,GAAG,OAAO,eAAe,IAAI,KAAK,GAAE;AAAA,MACzF,IAAI,iBAAiB,EAAE,gBAAgB,EAAC,MAAM,eAAe,IAAI,cAAc,EAAA,CAAE,IAAI;AAAA,MACrF,IAAI,oBAAoB,OAAO,EAAE,oBAAoB,EAAC,OAAO,IAAI,iBAAA,CAAiB,IAAI;AAAA,IAAA,EACxF,OAAO,OAAO,EAAE,KAAK,kBAAkB,IAAI,EAAE,iBAAiB;AAAA,EACpE,WAAW,eAAe,QAAQ,WAAW,GAAG;AAC5C,mBAAe,EAAE,kBAAkB;AAAA,EACvC;AACA,QAAM,aAAa,eAAe,OAAO,WAAW,EAAE,sBAAsB,EAAC,KAAK,eAAe,OAAO,QAAQ,EAAA,CAAE,IAAI;AACtH,QAAM,mBAAmB,MAAM,cAAc;AAAA,IACzC,OAAO,WAAW,QAAQ,EAAE,iBAAiB;AAAA,IAC7C,SAAS,CAAC,oBAAoB,SAAS,OAAO,eAAe;AAAA,EAAA,CAChE;AAED,QAAM,2BACD,OAAA,EACG,UAAA;AAAA,IAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACG,WAAU;AAAA,QACV,aAAW;AAAA,QACV,GAAG,OAAO;AAAA,QACX,OAAO;AAAA;AAAA,UAEH,YAAY;AAAA,UAAU,gBAAgB;AAAA,UAAY,UAAU,EAAC,IAAI,QAAQ,IAAI,SAAA;AAAA,UAAW,KAAK;AAAA,UAAG,UAAU;AAAA,UAAG,WAAW;AAAA,UACxH,IAAI;AAAA,UAAK,IAAI;AAAA,UAAM,cAAc;AAAA,UACjC,SAAS,OAAO,aAAa,IAAI;AAAA,UAAG,cAAc;AAAA,UAAU,cAAc;AAAA,UAC1E,SAAS,OAAO,aAAa,iBAAiB;AAAA,UAC9C,GAAG;AAAA,QAAA;AAAA,QAGP,UAAA;AAAA,UAAA,oBAAC,SAAA,EAAO,GAAG,OAAO,YAAY,IAAI,SAAS,MAAY,mBAAiB,QAAQ,UAAU,OAAA,CAAW;AAAA,UACpG;AAAA,8BAEI,SAAA,EAAQ,OAAO,UAAU,QAAQ,OAAO,GAAG,UAAU,IAAI,MAAM,eAAe,UAAU,IAAI,CAAC,KAAK,UAAU,MACzG,UAAA;AAAA,YAAC;AAAA,YAAA;AAAA,cAAe,SAAQ;AAAA,cAAU,OAAM;AAAA,cAAiB,QAAM;AAAA,cAC/C,OAAO,EAAC,UAAU,GAAG,YAAY,GAAG,WAAW,EAAC,IAAI,QAAQ,IAAI,OAAA,GAAS,WAAW,EAAC,IAAI,SAAS,IAAI,SAAM;AAAA,cACvH,UAAA,UAAU;AAAA,YAAA;AAAA,UAAA,GAEnB;AAAA,UAEH;AAAA,UACD;AAAA,YAAC;AAAA,YAAA;AAAA,cACG,MAAK;AAAA,cACL,OAAO,cAAc,MAAM;AAAA,cAC3B,SAAS,WAAW,mBAAmB,UAAU,WAAW;AAAA,cAC5D,OAAO;AAAA,cACP,MAAO,eACH,oBAAC,oBAAiB,MAAM,IAAI,OAAM,WAAU,SAAS,eAAe,kBAAkB,eAAe,OAAO,YAAY,EAAA,CAAG,KACxH,cAAc,oBAAC,0BAAuB,KAAO;AAAA,cACpD,OAAO,EAAC,YAAY,EAAA;AAAA,YAAC;AAAA,UAAA;AAAA,UAExB,cACG,oBAAC,kBAAA,EAAiB,MAAK,SAAQ,OAAM,WAAU,SAAS,QAAQ,OAAO,EAAC,YAAY,EAAA,GAAK,UAAA,EAAE,eAAe,GAAE;AAAA;AAAA,YAG5G,oBAAC,SAAA,EAAQ,OAAO,YAAY,eAAa,MACrC,UAAA;AAAA,cAAC;AAAA,cAAA;AAAA,gBACG,MAAK;AAAA,gBACL,SAAS,aAAa,aAAa;AAAA,gBACnC,+BAAY,wBAAA,EAAuB;AAAA,gBACnC;AAAA,gBACA,SAAS,OAAO;AAAA,gBAChB,mBAAiB,QAAQ,GAAG,YAAY,IAAI,OAAO,KAAK;AAAA,gBACxD,OAAO,EAAC,YAAY,EAAA;AAAA,gBAEpB,UAAA,oBAAC,QAAA,EAAK,IAAI,cAAe,UAAA,aAAa,EAAE,gBAAgB,IAAI,EAAE,eAAe,EAAA,CAAE;AAAA,cAAA;AAAA,YAAA,EACnF,CACJ;AAAA;AAAA,UAEH,cAAc,YACX,oBAAC,SAAA,EAAQ,OAAO,EAAE,iBAAiB,GAC/B,UAAA,oBAAC,YAAA,EAAW,MAAK,SAAQ,cAAY,EAAE,iBAAiB,GAAG,SAAS,kBAChE,8BAAC,0BAAA,EAAyB,UAAS,QAAA,CAAQ,EAAA,CAC/C,EAAA,CACJ;AAAA,UAEH,cAAc,CAAC,YACZ,oBAAC,SAAA,EAAQ,OAAO,EAAE,iBAAiB,EAAC,MAAM,WAAW,QAAQ,GAAA,CAAG,GAC5D,8BAAC,YAAA,EAAW,MAAK,SAAQ,cAAY,EAAE,iBAAiB,EAAC,MAAM,WAAW,QAAQ,IAAG,GAAG,SAAS,QAC7F,UAAA,oBAAC,kBAAA,EAAiB,UAAS,QAAA,CAAQ,GACvC,EAAA,CACJ;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,IAIR;AAAA,MAAC;AAAA,MAAA;AAAA,QAAiB,OAAO,QAAQ,SAAS;AAAA,QAAG,MAAM,YAAY,UAAW,eAAe,WAAW;AAAA,QAClF,OAAO,EAAC,WAAW,SAAS,IAAI,EAAA;AAAA,QAC7C,uBAAa,gBAAgB;AAAA,MAAA;AAAA,IAAA;AAAA,EAClC,GACJ;AAIJ,MAAI,CAAC,OAAO;AACR,WAAO;AAAA,EACX;AACA,SAAO,oBAAC,YAAA,EAAW,SAAkB,SAAkB,OAAe,UAAA,KAAI;AAC9E;"}
|