@stackcraftmx/calcita 0.54.0 → 0.55.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -2
- package/dist/api/createApiClient.js +11 -3
- package/dist/api/createApiClient.js.map +1 -1
- package/dist/forms/FormWizard.js +22 -17
- package/dist/forms/FormWizard.js.map +1 -1
- package/dist/hookForm/FormWizard.js +22 -17
- package/dist/hookForm/FormWizard.js.map +1 -1
- package/dist/index.js +2 -1
- package/dist/inputs/FormSection.js +40 -13
- package/dist/inputs/FormSection.js.map +1 -1
- package/dist/inputs/WizardLayout.js +25 -4
- package/dist/inputs/WizardLayout.js.map +1 -1
- package/dist/inputs/formLayoutContext.js +1 -1
- package/dist/inputs/formLayoutContext.js.map +1 -1
- package/dist/inputs/useWizardSteps.js +3 -0
- package/dist/inputs/useWizardSteps.js.map +1 -1
- package/dist/types/api/createApiClient.d.ts +10 -0
- package/dist/types/forms/FormWizard.d.ts +4 -0
- package/dist/types/hookForm/FormWizard.d.ts +4 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/inputs/FormSection.d.ts +14 -0
- package/dist/types/inputs/WizardLayout.d.ts +1 -0
- package/dist/types/inputs/formLayoutContext.d.ts +8 -7
- package/dist/types/inputs/useWizardSteps.d.ts +2 -1
- package/dist/upload/FileUploadInput.js +1 -1
- package/dist/upload/FileUploadInput.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +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 {useValidationText} from '@/forms/useValidationText.js';\nimport {newIdentifier} from '@/identifiers.js';\nimport FieldFrame from '@/inputs/FieldFrame.jsx';\nimport {fieldErrorsOf} from '@/problems.js';\nimport {piecePart, usePieceProps, withPieceSlots} 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 {boolean} [props.required] obligatorio: el asterisco en la etiqueta (lo revisa el esquema del formulario)\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 {Record<string, string|object>} [props.messages] el texto del error de este campo, por regla de validación\n * ({required: 'Escriba el RFC', matches: 'El RFC lleva 12 o 13 caracteres'}): gana al de Calcita y al de los textos\n * @param {object} [props.texts] los textos que cambian, con la forma de `common` en los textos del paquete\n */\nfunction FileUploadInputContent(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, required = false, actions, onUploaded, onUploadFailed, onCancel, onRemove, onStatusChange,\n sx, messages, texts,\n ...unknownProps\n } = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const {t} = useToolkitTranslation('common', texts);\n // el texto del error: el de la pantalla (messages), el del proyecto para este campo o el de Calcita\n const validationTextOf = useValidationText({name, label, messages});\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 = validationTextOf(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 }}\n sx={sx}\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} required={required}>{row}</FieldFrame>;\n}\n\n// lo que se exporta: la pieza con slots y slotProps para cambiar sus partes (themePieces.js)\nconst FileUploadInput = withPieceSlots(FileUploadInputContent, THEME_NAME);\nexport default FileUploadInput;\n"],"names":["result","transfer"],"mappings":"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"}
|
|
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 {useValidationText} from '@/forms/useValidationText.js';\nimport {newIdentifier} from '@/identifiers.js';\nimport FieldFrame from '@/inputs/FieldFrame.jsx';\nimport {fieldErrorsOf} from '@/problems.js';\nimport {piecePart, usePieceProps, withPieceSlots} 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 {boolean} [props.required] obligatorio: el asterisco en la etiqueta (lo revisa el esquema del formulario)\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 {Record<string, string|object>} [props.messages] el texto del error de este campo, por regla de validación\n * ({required: 'Escriba el RFC', matches: 'El RFC lleva 12 o 13 caracteres'}): gana al de Calcita y al de los textos\n * @param {object} [props.texts] los textos que cambian, con la forma de `common` en los textos del paquete\n */\nfunction FileUploadInputContent(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, required = false, actions, onUploaded, onUploadFailed, onCancel, onRemove, onStatusChange,\n sx, messages, texts,\n ...unknownProps\n } = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const {t} = useToolkitTranslation('common', texts);\n // el texto del error: el de la pantalla (messages), el del proyecto para este campo o el de Calcita\n const validationTextOf = useValidationText({name, label, messages});\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 = validationTextOf(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 }}\n sx={sx}\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. width 0 con minWidth 100 %: el texto\n usa el ancho de los botones de arriba y nunca lo ensancha. OJO: en un renglón que mide lo que ocupa (FormSection\n con labelWidth auto) el avance, que cambia cada momento, empujaba el nombre del documento a cada rato */}\n <FileUploadHelper error={Boolean(errorText)} role={errorText ? 'alert' : (progressText ? 'status' : undefined)}\n ownSx={{textAlign: 'right', mx: 0, width: 0, minWidth: '100%'}}>\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} required={required}>{row}</FieldFrame>;\n}\n\n// lo que se exporta: la pieza con slots y slotProps para cambiar sus partes (themePieces.js)\nconst FileUploadInput = withPieceSlots(FileUploadInputContent, THEME_NAME);\nexport default FileUploadInput;\n"],"names":["result","transfer"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stackcraftmx/calcita",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.55.0",
|
|
4
4
|
"description": "Componentes y utilidades de React con MUI para arrancar proyectos rápido: tabla de servidor, diálogos, avisos, descargas, formularios, formatos y reportes en Excel.",
|
|
5
5
|
"license": "FSL-1.1-MIT",
|
|
6
6
|
"author": "calcifux",
|