@stackcraftmx/calcita 0.53.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 +87 -2
- package/dist/api/createApiClient.js +11 -3
- package/dist/api/createApiClient.js.map +1 -1
- package/dist/filepond/FileDropzone.js +60 -19
- package/dist/filepond/FileDropzone.js.map +1 -1
- package/dist/filepond/pondServer.js +86 -27
- package/dist/filepond/pondServer.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 +4 -1
- package/dist/index.js.map +1 -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/locales/en.js +1 -0
- package/dist/locales/en.js.map +1 -1
- package/dist/locales/es.js +1 -0
- package/dist/locales/es.js.map +1 -1
- package/dist/types/api/createApiClient.d.ts +10 -0
- package/dist/types/filepond/FileDropzone.d.ts +5 -0
- package/dist/types/filepond/chunkedUpload.d.ts +122 -0
- package/dist/types/filepond/pondServer.d.ts +25 -3
- package/dist/types/forms/FormWizard.d.ts +4 -0
- package/dist/types/hookForm/FormWizard.d.ts +4 -0
- package/dist/types/index.d.ts +2 -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/types/locales/en.d.ts +1 -0
- package/dist/types/locales/es.d.ts +1 -0
- package/dist/types/upload/chunkedUpload.d.ts +189 -0
- package/dist/types/upload/uploadWithProgress.d.ts +4 -2
- package/dist/upload/FileUploadInput.js +1 -1
- package/dist/upload/FileUploadInput.js.map +1 -1
- package/dist/upload/chunkedUpload.js +261 -0
- package/dist/upload/chunkedUpload.js.map +1 -0
- package/dist/upload/uploadWithProgress.js +8 -3
- package/dist/upload/uploadWithProgress.js.map +1 -1
- package/package.json +1 -1
|
@@ -11,10 +11,12 @@ import { fieldVariablesOf, accentTextStylesOf, PANEL_RADIUS, fieldWellFromVariab
|
|
|
11
11
|
import { warnUnknownProps } from "../toolkit/unknownProps.js";
|
|
12
12
|
import { useToolkitTranslation } from "../toolkit/useToolkitTranslation.js";
|
|
13
13
|
const FORM_LABEL_PLACEMENT = Object.freeze({ side: "side", topOnPhone: "topOnPhone" });
|
|
14
|
+
const FORM_LABEL_WIDTH = Object.freeze({ auto: "auto" });
|
|
14
15
|
const FORM_ROW_CLASS = "calcita-form-row";
|
|
15
16
|
const ROW_MIN_HEIGHT_PIXELS = 52;
|
|
16
17
|
const ROW_PADDING_BLOCK_PIXELS = 8;
|
|
17
18
|
const LABEL_COLUMN = Object.freeze({ share: "38%", maxPixels: 200 });
|
|
19
|
+
const AUTO_CONTROL_MAX_SHARE = "50%";
|
|
18
20
|
const LABEL_TOP_PIXELS = 8;
|
|
19
21
|
const RING = Object.freeze({ focusPercent: 45, errorPercent: 60, pixels: 2 });
|
|
20
22
|
function rowControlSxOf(theme) {
|
|
@@ -61,25 +63,40 @@ const FormRowRoot = styled(Box, { name: ROW_THEME_NAME, slot: "Root", shouldForw
|
|
|
61
63
|
...isTopOnPhone && { [theme.breakpoints.down("sm")]: { flexDirection: "column", alignItems: "stretch", gap: 0.5 } }
|
|
62
64
|
});
|
|
63
65
|
});
|
|
66
|
+
function labelColumnStylesOf(labelWidth) {
|
|
67
|
+
if (labelWidth === FORM_LABEL_WIDTH.auto) {
|
|
68
|
+
return { flex: "1 1 auto", maxWidth: "none" };
|
|
69
|
+
}
|
|
70
|
+
if (labelWidth !== void 0 && labelWidth !== null) {
|
|
71
|
+
const width = typeof labelWidth === "number" ? `${labelWidth}px` : labelWidth;
|
|
72
|
+
return { flex: `0 0 ${width}`, maxWidth: "none" };
|
|
73
|
+
}
|
|
74
|
+
return { flex: `0 0 ${LABEL_COLUMN.share}`, maxWidth: LABEL_COLUMN.maxPixels };
|
|
75
|
+
}
|
|
64
76
|
const FormRowLabel = styled(Typography, { name: ROW_THEME_NAME, slot: "Label", shouldForwardProp: isPartProp })(({ theme, ownerState }) => theme.unstable_sx({
|
|
65
|
-
|
|
66
|
-
maxWidth: LABEL_COLUMN.maxPixels,
|
|
77
|
+
...labelColumnStylesOf(ownerState?.labelWidth),
|
|
67
78
|
pt: `${LABEL_TOP_PIXELS}px`,
|
|
68
79
|
lineHeight: 1.5,
|
|
69
80
|
color: "text.primary",
|
|
81
|
+
// va después del ancho: en el teléfono, con la etiqueta arriba, la columna ya no cuenta
|
|
70
82
|
...ownerState?.labelPlacement === FORM_LABEL_PLACEMENT.topOnPhone && {
|
|
71
83
|
[theme.breakpoints.down("sm")]: { flex: "none", maxWidth: "none", pt: 0 }
|
|
72
84
|
},
|
|
73
85
|
// el asterisco de lo obligatorio: aquí, debajo de lo que el proyecto ponga en su tema
|
|
74
86
|
...ownerState?.requiredMark && requiredMarkSxOf(ownerState.requiredMark)
|
|
75
87
|
}));
|
|
76
|
-
const FormRowControl = styled(Box, { name: ROW_THEME_NAME, slot: "Control" })({
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
88
|
+
const FormRowControl = styled(Box, { name: ROW_THEME_NAME, slot: "Control", shouldForwardProp: isPartProp })(({ theme, ownerState }) => {
|
|
89
|
+
const isAuto = ownerState?.labelWidth === FORM_LABEL_WIDTH.auto;
|
|
90
|
+
return {
|
|
91
|
+
flex: isAuto ? "0 0 auto" : 1,
|
|
92
|
+
...isAuto && { maxWidth: AUTO_CONTROL_MAX_SHARE },
|
|
93
|
+
...isAuto && ownerState?.labelPlacement === FORM_LABEL_PLACEMENT.topOnPhone && { [theme.breakpoints.down("sm")]: { maxWidth: "none" } },
|
|
94
|
+
minWidth: 0,
|
|
95
|
+
display: "flex",
|
|
96
|
+
flexDirection: "column",
|
|
97
|
+
justifyContent: "center",
|
|
98
|
+
minHeight: 40
|
|
99
|
+
};
|
|
83
100
|
});
|
|
84
101
|
const FormSectionRoot = styled(Box, { name: SECTION_THEME_NAME, slot: "Root" })({});
|
|
85
102
|
const FormSectionTitle = styled(Typography, { name: SECTION_THEME_NAME, slot: "Title" })(({ theme }) => theme.unstable_sx({ px: 2, mb: 0.75, fontWeight: 600 }));
|
|
@@ -104,6 +121,7 @@ function FormRow(props) {
|
|
|
104
121
|
const sectionLayout = useFormSectionLayout();
|
|
105
122
|
const ownerState = {
|
|
106
123
|
labelPlacement: sectionLayout ? sectionLayout.labelPlacement : FORM_LABEL_PLACEMENT.side,
|
|
124
|
+
labelWidth: sectionLayout ? sectionLayout.labelWidth : void 0,
|
|
107
125
|
requiredMark: required ? t("form.requiredMark") : null
|
|
108
126
|
};
|
|
109
127
|
return /* @__PURE__ */ jsxs(FormRowRoot, { className: FORM_ROW_CLASS, ownerState, children: [
|
|
@@ -119,15 +137,23 @@ function FormRow(props) {
|
|
|
119
137
|
children: label
|
|
120
138
|
}
|
|
121
139
|
),
|
|
122
|
-
/* @__PURE__ */ jsx(FormRowControl, { children })
|
|
140
|
+
/* @__PURE__ */ jsx(FormRowControl, { ownerState, children })
|
|
123
141
|
] });
|
|
124
142
|
}
|
|
125
143
|
function FormSection(props) {
|
|
126
|
-
const {
|
|
144
|
+
const {
|
|
145
|
+
title,
|
|
146
|
+
footer,
|
|
147
|
+
labelPlacement = FORM_LABEL_PLACEMENT.side,
|
|
148
|
+
labelWidth,
|
|
149
|
+
sx,
|
|
150
|
+
children,
|
|
151
|
+
...unknownProps
|
|
152
|
+
} = useThemeProps({ props, name: SECTION_THEME_NAME });
|
|
127
153
|
warnUnknownProps(SECTION_THEME_NAME, unknownProps);
|
|
128
154
|
const titleId = useId();
|
|
129
|
-
const layout = useMemo(() => ({ labelPlacement }), [labelPlacement]);
|
|
130
|
-
return /* @__PURE__ */ jsx(FormSectionContext, { value: layout, children: /* @__PURE__ */ jsxs(FormSectionRoot, { component: "section", "aria-labelledby": title ? titleId : void 0, children: [
|
|
155
|
+
const layout = useMemo(() => ({ labelPlacement, labelWidth }), [labelPlacement, labelWidth]);
|
|
156
|
+
return /* @__PURE__ */ jsx(FormSectionContext, { value: layout, children: /* @__PURE__ */ jsxs(FormSectionRoot, { component: "section", "aria-labelledby": title ? titleId : void 0, sx, children: [
|
|
131
157
|
title && /* @__PURE__ */ jsx(FormSectionTitle, { id: titleId, variant: "subtitle2", component: "h2", color: "text.secondary", children: title }),
|
|
132
158
|
/* @__PURE__ */ jsx(FormSectionCard, { variant: "outlined", children: /* @__PURE__ */ jsx(Box, { sx: { mt: "-1px" }, children }) }),
|
|
133
159
|
footer && /* @__PURE__ */ jsx(FormSectionFooter, { variant: "caption", component: "p", color: "text.secondary", children: footer })
|
|
@@ -135,6 +161,7 @@ function FormSection(props) {
|
|
|
135
161
|
}
|
|
136
162
|
export {
|
|
137
163
|
FORM_LABEL_PLACEMENT,
|
|
164
|
+
FORM_LABEL_WIDTH,
|
|
138
165
|
FORM_ROW_CLASS,
|
|
139
166
|
FormRow,
|
|
140
167
|
FormSection as default
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FormSection.js","sources":["../../src/inputs/FormSection.jsx"],"sourcesContent":["// El formulario agrupado de Apple (el de Ajustes y el de la hoja «Nuevo evento» de Calendario): una tarjeta redonda con\n// renglones, el título de la sección arriba de la tarjeta y la explicación abajo. Dentro de una FormSection cada campo del\n// framework (TextInput, NumberInput, SelectInput, CatalogInput, ColorInput, DateInput y sus versiones de formik y de\n// react-hook-form) se pinta solo como renglón: la etiqueta a la izquierda y el campo a la derecha.\n//\n// <FormSection title=\"Datos del cliente\" footer=\"Le enviaremos la factura al correo.\">\n// <FormTextField name=\"name\" label=\"Nombre\" />\n// <FormTextField name=\"email\" label=\"Correo\" />\n// <FormRow label=\"Recibir avisos\"><Switch /></FormRow>\n// </FormSection>\n//\n// Las reglas, de la guía de Apple (HIG «Text fields», «Pickers», «Combo boxes» y sus imágenes):\n// - la etiqueta siempre a la vista, a la izquierda (la pista se va al escribir; la etiqueta no);\n// - el campo es un «pozo» parejo con esquinas de 12 px (fieldWellStylesOf): gris tenue en claro y más claro que la\n// tarjeta en oscuro, con la letra clara, como el campo de la imagen de Apple. Antes iba transparente y el relleno solo\n// salía con el cursor encima, en cuadro: no se entendía dónde escribir (lo reportaron);\n// - la fecha es el mismo campo que los demás, a todo lo ancho (en cápsula al final del renglón quedaba descuadrada), con\n// la letra del texto (no del color del tema: en oscuro el naranja ahí «se veía horrible», lo reportaron);\n// - al escribir, un aro tenue del color principal y la etiqueta del color del tema; con error, aro y etiqueta en rojo y\n// el mensaje debajo del campo;\n// - una línea fina entre renglones, que empieza donde empieza el texto.\n//\n// Los campos pueden ir directo o dentro de lo que el proyecto necesite (un Grid de dos columnas, un Stack, un componente\n// suyo): cada renglón dibuja su propia línea ARRIBA y la tarjeta sube su contenido un píxel y lo recorta, así la línea del\n// primero queda escondida. OJO: antes la línea se dibujaba entre dos hermanos seguidos y con un Grid en medio desaparecían\n// todas (lo señalaron en una revisión: obligaba a «la forma Calcita»).\n//\n// En el teléfono la etiqueta se lleva el 38 % del renglón y un nombre largo («Correo adicional (notificaciones)») se parte en\n// tres renglones: con labelPlacement={FORM_LABEL_PLACEMENT.topOnPhone} la etiqueta sube arriba del campo por debajo de `sm`\n// (como en los formularios de iOS y de Android); en pantallas más anchas, igual que siempre. Por omisión `side`, como antes.\n//\n// Un renglón con `required` lleva un asterisco en la etiqueta (requiredMark.js: en el estilo, así la etiqueta sigue diciendo lo\n// mismo y el lector de pantalla no lo lee; el campo ya dice que es obligatorio).\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaFormSection (sus partes root, title, card y\n// footer) y components.CalcitaFormRow (root, label y control; el asterisco es '&[data-required]::after' de label), con\n// styleOverrides y defaultProps. Los colores de los campos,\n// más fácil con createToolkitTheme({fields}).\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useId, useMemo} from 'react';\nimport Box from '@mui/material/Box';\nimport Paper from '@mui/material/Paper';\nimport Typography from '@mui/material/Typography';\nimport {styled, useThemeProps} from '@mui/material/styles';\n\nimport {FormSectionContext, useFormSectionLayout} from '@/inputs/formLayoutContext.js';\nimport {REQUIRED_LABEL_ATTRIBUTE, requiredMarkSxOf} from '@/inputs/requiredMark.js';\nimport {PANEL_RADIUS, accentTextStylesOf, fieldVariablesOf, fieldWellFromVariablesOf} from '@/theme/glassSurface.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\n\n/**\n * Dónde va la etiqueta de cada renglón: a la izquierda siempre (side, lo de siempre) o arriba del campo en el teléfono\n * (topOnPhone: por debajo de `sm` en el tema; más ancho, a la izquierda).\n */\nexport const FORM_LABEL_PLACEMENT = Object.freeze({side: 'side', topOnPhone: 'topOnPhone'});\n\n/** La clase de cada renglón: la sección le dibuja su línea de arriba, esté donde esté adentro de la tarjeta. */\nexport const FORM_ROW_CLASS = 'calcita-form-row';\n\n// el renglón: alto mínimo para el dedo, su aire arriba y abajo, y lo ancho de la columna de las etiquetas\nconst ROW_MIN_HEIGHT_PIXELS = 52;\nconst ROW_PADDING_BLOCK_PIXELS = 8;\nconst LABEL_COLUMN = Object.freeze({share: '38%', maxPixels: 200});\n// lo que la etiqueta baja para quedar en la misma línea que el texto del campo (el campo mide 40 px y su letra 24 px)\nconst LABEL_TOP_PIXELS = 8;\n// el aro al escribir y con error (por ciento del color) y su grosor\nconst RING = Object.freeze({focusPercent: 45, errorPercent: 60, pixels: 2});\n\n/**\n * Los campos de MUI dentro de un renglón: el pozo de Apple (fieldWellStylesOf), sin el borde de MUI ni el vidrio del tema,\n * con su aro al escribir. Los selectores van con «html body &&&» para ganarle a los ajustes del tema dentro de una\n * tarjeta, que con el lente llevan una etiqueta y hasta cuatro clases (con menos, el relleno del tema salía al pasar el\n * cursor, en cuadro: los «hovers espantosos» que reportaron).\n *\n * @param {object} theme\n * @returns {object}\n */\nfunction rowControlSxOf(theme) {\n const palette = (theme.vars ?? theme).palette;\n const strong = (selectors) => selectors.map((selector) => `html body &&& ${selector}`).join(', ');\n const roots = ['.MuiOutlinedInput-root', '.MuiFilledInput-root', '.MuiPickersOutlinedInput-root'];\n const ringOf = (color, percent) => ({outline: `${RING.pixels}px solid color-mix(in srgb, ${color} ${percent}%, transparent)`, outlineOffset: 0});\n return {\n // los tonos van en variables del renglón (fieldVariablesOf, abajo en FormRow): aquí abajo una regla de oscuro no aplica\n [strong(roots)]: {...fieldWellFromVariablesOf(theme), outline: 'none'},\n [strong(roots.map((root) => `${root}.Mui-focused`))]: ringOf(palette.primary.main, RING.focusPercent),\n [strong(roots.map((root) => `${root}.Mui-error`))]: ringOf(palette.error.main, RING.errorPercent),\n [strong(['.MuiOutlinedInput-notchedOutline', '.MuiPickersOutlinedInput-notchedOutline'])]: {display: 'none'},\n [strong(['.MuiFilledInput-root::before', '.MuiFilledInput-root::after'])]: {display: 'none'},\n // la fecha: el mismo campo que los demás, a todo lo ancho de su columna (en cápsula al final del renglón quedaba\n // descuadrada, con su ayuda a la izquierda debajo de nada; lo reportaron), con el ícono del calendario discreto\n [strong(['.MuiPickersOutlinedInput-root'])]: {color: palette.text.primary},\n [strong(['.MuiPickersOutlinedInput-root .MuiInputAdornment-root .MuiSvgIcon-root'])]: {fontSize: '1.125rem', color: palette.text.secondary},\n // la ayuda y el error, debajo del campo; el renglón no guarda lugar para una ayuda vacía\n '& .MuiFormHelperText-root': {marginInline: theme.spacing(0.5), marginTop: '4px', marginBottom: '2px'},\n '& .MuiFormHelperText-root:has(> .notranslate:only-child)': {display: 'none'},\n };\n}\n\n// los nombres de las piezas en el tema (theme.components.CalcitaFormSection y CalcitaFormRow)\nconst SECTION_THEME_NAME = 'CalcitaFormSection';\nconst ROW_THEME_NAME = 'CalcitaFormRow';\n\n// el renglón: la etiqueta a la izquierda y el campo a la derecha, con la línea de arriba y los tonos de los campos\n// ownerState: dónde va la etiqueta (también lo recibe un styleOverrides del tema que sea función); no llega al DOM\nconst isPartProp = (propName) => propName !== 'ownerState';\n\nconst FormRowRoot = styled(Box, {name: ROW_THEME_NAME, slot: 'Root', shouldForwardProp: isPartProp})(({theme, ownerState}) => {\n const palette = (theme.vars ?? theme).palette;\n const isTopOnPhone = ownerState?.labelPlacement === FORM_LABEL_PLACEMENT.topOnPhone;\n return theme.unstable_sx({\n position: 'relative',\n display: 'flex',\n alignItems: 'flex-start',\n gap: 2,\n minHeight: ROW_MIN_HEIGHT_PIXELS,\n px: 2,\n py: `${ROW_PADDING_BLOCK_PIXELS}px`,\n '&:focus-within > label': accentTextStylesOf(theme),\n '&:has(.Mui-error) > label': {color: palette.error.main},\n // los tonos de los campos (los del proyecto con createToolkitTheme fields, o los de Apple) para sus campos\n ...fieldVariablesOf(theme),\n ...rowControlSxOf(theme),\n // en el teléfono, la etiqueta arriba y el campo a todo lo ancho debajo\n ...(isTopOnPhone && {[theme.breakpoints.down('sm')]: {flexDirection: 'column', alignItems: 'stretch', gap: 0.5}}),\n });\n});\n\nconst FormRowLabel = styled(Typography, {name: ROW_THEME_NAME, slot: 'Label', shouldForwardProp: isPartProp})(({theme, ownerState}) => theme.unstable_sx({\n flex: `0 0 ${LABEL_COLUMN.share}`, maxWidth: LABEL_COLUMN.maxPixels, pt: `${LABEL_TOP_PIXELS}px`, lineHeight: 1.5, color: 'text.primary',\n ...(ownerState?.labelPlacement === FORM_LABEL_PLACEMENT.topOnPhone && {\n [theme.breakpoints.down('sm')]: {flex: 'none', maxWidth: 'none', pt: 0},\n }),\n // el asterisco de lo obligatorio: aquí, debajo de lo que el proyecto ponga en su tema\n ...(ownerState?.requiredMark && requiredMarkSxOf(ownerState.requiredMark)),\n}));\n\n\nconst FormRowControl = styled(Box, {name: ROW_THEME_NAME, slot: 'Control'})({\n flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column', justifyContent: 'center', minHeight: 40,\n});\n\nconst FormSectionRoot = styled(Box, {name: SECTION_THEME_NAME, slot: 'Root'})({});\n\nconst FormSectionTitle = styled(Typography, {name: SECTION_THEME_NAME, slot: 'Title'})(({theme}) => theme.unstable_sx({px: 2, mb: 0.75, fontWeight: 600}));\n\n// la tarjeta: la línea de arriba de cada renglón, desde donde empieza el texto (la del primero la recorta la tarjeta)\nconst FormSectionCard = styled(Paper, {name: SECTION_THEME_NAME, slot: 'Card'})(({theme}) => ({\n borderRadius: PANEL_RADIUS,\n overflow: 'hidden',\n [`& .${FORM_ROW_CLASS}::before`]: {\n content: '\"\"', position: 'absolute', top: 0, right: 0, left: theme.spacing(2), height: '1px',\n backgroundColor: (theme.vars ?? theme).palette.divider,\n },\n}));\n\nconst FormSectionFooter = styled(Typography, {name: SECTION_THEME_NAME, slot: 'Footer'})(({theme}) => theme.unstable_sx({px: 2, mt: 0.75}));\n\n/**\n * Un renglón del formulario agrupado: la etiqueta a la izquierda y lo que sea a la derecha (un campo, un interruptor, un\n * texto). Los campos del framework lo usan solos dentro de una FormSection; FormRow suelto es para lo demás.\n *\n * @param {object} props\n * @param {import('react').ReactNode} props.label ya traducida\n * @param {string} [props.htmlFor] el id del campo que nombra la etiqueta\n * @param {string} [props.labelId] el id de la etiqueta (para un campo que la nombra con aria-labelledby)\n * @param {boolean} [props.required] el asterisco en la etiqueta (el lector de pantalla no lo lee: el campo ya lo dice)\n * @param {import('react').ReactNode} props.children\n */\nexport function FormRow(props) {\n // lo que el proyecto puso en su tema (components.CalcitaFormRow.defaultProps) y encima lo que llega aquí\n const {label, htmlFor, labelId, required = false, children, ...unknownProps} = useThemeProps({props, name: ROW_THEME_NAME});\n warnUnknownProps(ROW_THEME_NAME, unknownProps);\n const {t} = useToolkitTranslation('common');\n // dónde va la etiqueta lo pide la sección; un FormRow fuera de una sección, a la izquierda\n const sectionLayout = useFormSectionLayout();\n const ownerState = {\n labelPlacement: sectionLayout ? sectionLayout.labelPlacement : FORM_LABEL_PLACEMENT.side,\n requiredMark: required ? t('form.requiredMark') : null,\n };\n return (\n <FormRowRoot className={FORM_ROW_CLASS} ownerState={ownerState}>\n <FormRowLabel component=\"label\" id={labelId} htmlFor={htmlFor} variant=\"body1\" ownerState={ownerState}\n {...(required && {[REQUIRED_LABEL_ATTRIBUTE]: 'true'})}>\n {label}\n </FormRowLabel>\n <FormRowControl>\n {children}\n </FormRowControl>\n </FormRowRoot>\n );\n}\n\n/**\n * @param {object} props\n * @param {string} [props.title] ya traducido; va arriba de la tarjeta\n * @param {import('react').ReactNode} [props.footer] la explicación, abajo de la tarjeta\n * @param {string} [props.labelPlacement] uno de FORM_LABEL_PLACEMENT: side (por omisión, la etiqueta a la izquierda) o\n * topOnPhone (arriba del campo por debajo de `sm`)\n * @param {import('react').ReactNode} props.children los campos, directo o dentro de un Grid, un Stack o un componente propio\n */\nexport default function FormSection(props) {\n // lo que el proyecto puso en su tema (components.CalcitaFormSection.defaultProps) y encima lo que llega aquí\n const {title, footer, labelPlacement = FORM_LABEL_PLACEMENT.side, children, ...unknownProps} = useThemeProps({props, name: SECTION_THEME_NAME});\n warnUnknownProps(SECTION_THEME_NAME, unknownProps);\n const titleId = useId();\n const layout = useMemo(() => ({labelPlacement}), [labelPlacement]);\n return (\n <FormSectionContext value={layout}>\n <FormSectionRoot component=\"section\" aria-labelledby={title ? titleId : undefined}>\n {title && (\n <FormSectionTitle id={titleId} variant=\"subtitle2\" component=\"h2\" color=\"text.secondary\">\n {title}\n </FormSectionTitle>\n )}\n <FormSectionCard variant=\"outlined\">\n {/* un píxel arriba: la línea de los renglones de hasta arriba queda fuera de la tarjeta (overflow hidden) */}\n <Box sx={{mt: '-1px'}}>{children}</Box>\n </FormSectionCard>\n {footer && (\n <FormSectionFooter variant=\"caption\" component=\"p\" color=\"text.secondary\">{footer}</FormSectionFooter>\n )}\n </FormSectionRoot>\n </FormSectionContext>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"FormSection.js","sources":["../../src/inputs/FormSection.jsx"],"sourcesContent":["// El formulario agrupado de Apple (el de Ajustes y el de la hoja «Nuevo evento» de Calendario): una tarjeta redonda con\n// renglones, el título de la sección arriba de la tarjeta y la explicación abajo. Dentro de una FormSection cada campo del\n// framework (TextInput, NumberInput, SelectInput, CatalogInput, ColorInput, DateInput y sus versiones de formik y de\n// react-hook-form) se pinta solo como renglón: la etiqueta a la izquierda y el campo a la derecha.\n//\n// <FormSection title=\"Datos del cliente\" footer=\"Le enviaremos la factura al correo.\">\n// <FormTextField name=\"name\" label=\"Nombre\" />\n// <FormTextField name=\"email\" label=\"Correo\" />\n// <FormRow label=\"Recibir avisos\"><Switch /></FormRow>\n// </FormSection>\n//\n// Las reglas, de la guía de Apple (HIG «Text fields», «Pickers», «Combo boxes» y sus imágenes):\n// - la etiqueta siempre a la vista, a la izquierda (la pista se va al escribir; la etiqueta no);\n// - el campo es un «pozo» parejo con esquinas de 12 px (fieldWellStylesOf): gris tenue en claro y más claro que la\n// tarjeta en oscuro, con la letra clara, como el campo de la imagen de Apple. Antes iba transparente y el relleno solo\n// salía con el cursor encima, en cuadro: no se entendía dónde escribir (lo reportaron);\n// - la fecha es el mismo campo que los demás, a todo lo ancho (en cápsula al final del renglón quedaba descuadrada), con\n// la letra del texto (no del color del tema: en oscuro el naranja ahí «se veía horrible», lo reportaron);\n// - al escribir, un aro tenue del color principal y la etiqueta del color del tema; con error, aro y etiqueta en rojo y\n// el mensaje debajo del campo;\n// - una línea fina entre renglones, que empieza donde empieza el texto.\n//\n// Los campos pueden ir directo o dentro de lo que el proyecto necesite (un Grid de dos columnas, un Stack, un componente\n// suyo): cada renglón dibuja su propia línea ARRIBA y la tarjeta sube su contenido un píxel y lo recorta, así la línea del\n// primero queda escondida. OJO: antes la línea se dibujaba entre dos hermanos seguidos y con un Grid en medio desaparecían\n// todas (lo señalaron en una revisión: obligaba a «la forma Calcita»).\n//\n// En el teléfono la etiqueta se lleva el 38 % del renglón y un nombre largo («Correo adicional (notificaciones)») se parte en\n// tres renglones: con labelPlacement={FORM_LABEL_PLACEMENT.topOnPhone} la etiqueta sube arriba del campo por debajo de `sm`\n// (como en los formularios de iOS y de Android); en pantallas más anchas, igual que siempre. Por omisión `side`, como antes.\n//\n// Lo ancho de la columna de las etiquetas, por sección (labelWidth): por omisión el 38 % del renglón hasta 200 px, lo de\n// siempre. Una lista de documentos lleva nombres de cien caracteres y a la derecha solo sus botones: con 200 px el nombre se\n// partía en ocho renglones. Con labelWidth={FORM_LABEL_WIDTH.auto} el nombre se lleva lo que sobre y el campo solo lo que\n// ocupa; con un ancho ('320px', '50%' o un número en px) la columna mide eso. Las demás secciones de la pantalla no cambian.\n// En el teléfono lo que sobra es poco: con auto, mejor junto con labelPlacement topOnPhone (el nombre arriba, el campo abajo).\n//\n// Un renglón con `required` lleva un asterisco en la etiqueta (requiredMark.js: en el estilo, así la etiqueta sigue diciendo lo\n// mismo y el lector de pantalla no lo lee; el campo ya dice que es obligatorio).\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaFormSection (sus partes root, title, card y\n// footer) y components.CalcitaFormRow (root, label y control; el asterisco es '&[data-required]::after' de label), con\n// styleOverrides y defaultProps. Los colores de los campos,\n// más fácil con createToolkitTheme({fields}).\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useId, useMemo} from 'react';\nimport Box from '@mui/material/Box';\nimport Paper from '@mui/material/Paper';\nimport Typography from '@mui/material/Typography';\nimport {styled, useThemeProps} from '@mui/material/styles';\n\nimport {FormSectionContext, useFormSectionLayout} from '@/inputs/formLayoutContext.js';\nimport {REQUIRED_LABEL_ATTRIBUTE, requiredMarkSxOf} from '@/inputs/requiredMark.js';\nimport {PANEL_RADIUS, accentTextStylesOf, fieldVariablesOf, fieldWellFromVariablesOf} from '@/theme/glassSurface.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\n\n/**\n * Dónde va la etiqueta de cada renglón: a la izquierda siempre (side, lo de siempre) o arriba del campo en el teléfono\n * (topOnPhone: por debajo de `sm` en el tema; más ancho, a la izquierda).\n */\nexport const FORM_LABEL_PLACEMENT = Object.freeze({side: 'side', topOnPhone: 'topOnPhone'});\n\n/**\n * Lo ancho de la columna de las etiquetas que no es un número: auto = la etiqueta se lleva lo que sobre y el campo solo lo\n * que ocupa (una lista de documentos con su cargador).\n */\nexport const FORM_LABEL_WIDTH = Object.freeze({auto: 'auto'});\n\n/** La clase de cada renglón: la sección le dibuja su línea de arriba, esté donde esté adentro de la tarjeta. */\nexport const FORM_ROW_CLASS = 'calcita-form-row';\n\n// el renglón: alto mínimo para el dedo, su aire arriba y abajo, y lo ancho de la columna de las etiquetas\nconst ROW_MIN_HEIGHT_PIXELS = 52;\nconst ROW_PADDING_BLOCK_PIXELS = 8;\nconst LABEL_COLUMN = Object.freeze({share: '38%', maxPixels: 200});\n// con la etiqueta en auto, lo más que se lleva el campo: el nombre del documento conserva al menos la otra mitad\nconst AUTO_CONTROL_MAX_SHARE = '50%';\n// lo que la etiqueta baja para quedar en la misma línea que el texto del campo (el campo mide 40 px y su letra 24 px)\nconst LABEL_TOP_PIXELS = 8;\n// el aro al escribir y con error (por ciento del color) y su grosor\nconst RING = Object.freeze({focusPercent: 45, errorPercent: 60, pixels: 2});\n\n/**\n * Los campos de MUI dentro de un renglón: el pozo de Apple (fieldWellStylesOf), sin el borde de MUI ni el vidrio del tema,\n * con su aro al escribir. Los selectores van con «html body &&&» para ganarle a los ajustes del tema dentro de una\n * tarjeta, que con el lente llevan una etiqueta y hasta cuatro clases (con menos, el relleno del tema salía al pasar el\n * cursor, en cuadro: los «hovers espantosos» que reportaron).\n *\n * @param {object} theme\n * @returns {object}\n */\nfunction rowControlSxOf(theme) {\n const palette = (theme.vars ?? theme).palette;\n const strong = (selectors) => selectors.map((selector) => `html body &&& ${selector}`).join(', ');\n const roots = ['.MuiOutlinedInput-root', '.MuiFilledInput-root', '.MuiPickersOutlinedInput-root'];\n const ringOf = (color, percent) => ({outline: `${RING.pixels}px solid color-mix(in srgb, ${color} ${percent}%, transparent)`, outlineOffset: 0});\n return {\n // los tonos van en variables del renglón (fieldVariablesOf, abajo en FormRow): aquí abajo una regla de oscuro no aplica\n [strong(roots)]: {...fieldWellFromVariablesOf(theme), outline: 'none'},\n [strong(roots.map((root) => `${root}.Mui-focused`))]: ringOf(palette.primary.main, RING.focusPercent),\n [strong(roots.map((root) => `${root}.Mui-error`))]: ringOf(palette.error.main, RING.errorPercent),\n [strong(['.MuiOutlinedInput-notchedOutline', '.MuiPickersOutlinedInput-notchedOutline'])]: {display: 'none'},\n [strong(['.MuiFilledInput-root::before', '.MuiFilledInput-root::after'])]: {display: 'none'},\n // la fecha: el mismo campo que los demás, a todo lo ancho de su columna (en cápsula al final del renglón quedaba\n // descuadrada, con su ayuda a la izquierda debajo de nada; lo reportaron), con el ícono del calendario discreto\n [strong(['.MuiPickersOutlinedInput-root'])]: {color: palette.text.primary},\n [strong(['.MuiPickersOutlinedInput-root .MuiInputAdornment-root .MuiSvgIcon-root'])]: {fontSize: '1.125rem', color: palette.text.secondary},\n // la ayuda y el error, debajo del campo; el renglón no guarda lugar para una ayuda vacía\n '& .MuiFormHelperText-root': {marginInline: theme.spacing(0.5), marginTop: '4px', marginBottom: '2px'},\n '& .MuiFormHelperText-root:has(> .notranslate:only-child)': {display: 'none'},\n };\n}\n\n// los nombres de las piezas en el tema (theme.components.CalcitaFormSection y CalcitaFormRow)\nconst SECTION_THEME_NAME = 'CalcitaFormSection';\nconst ROW_THEME_NAME = 'CalcitaFormRow';\n\n// el renglón: la etiqueta a la izquierda y el campo a la derecha, con la línea de arriba y los tonos de los campos\n// ownerState: dónde va la etiqueta (también lo recibe un styleOverrides del tema que sea función); no llega al DOM\nconst isPartProp = (propName) => propName !== 'ownerState';\n\nconst FormRowRoot = styled(Box, {name: ROW_THEME_NAME, slot: 'Root', shouldForwardProp: isPartProp})(({theme, ownerState}) => {\n const palette = (theme.vars ?? theme).palette;\n const isTopOnPhone = ownerState?.labelPlacement === FORM_LABEL_PLACEMENT.topOnPhone;\n return theme.unstable_sx({\n position: 'relative',\n display: 'flex',\n alignItems: 'flex-start',\n gap: 2,\n minHeight: ROW_MIN_HEIGHT_PIXELS,\n px: 2,\n py: `${ROW_PADDING_BLOCK_PIXELS}px`,\n '&:focus-within > label': accentTextStylesOf(theme),\n '&:has(.Mui-error) > label': {color: palette.error.main},\n // los tonos de los campos (los del proyecto con createToolkitTheme fields, o los de Apple) para sus campos\n ...fieldVariablesOf(theme),\n ...rowControlSxOf(theme),\n // en el teléfono, la etiqueta arriba y el campo a todo lo ancho debajo\n ...(isTopOnPhone && {[theme.breakpoints.down('sm')]: {flexDirection: 'column', alignItems: 'stretch', gap: 0.5}}),\n });\n});\n\n/**\n * Lo ancho de la columna de la etiqueta según la sección: lo de siempre (38 % hasta 200 px), auto (lo que sobre) o el ancho\n * que dijo (un número son px, como en sx).\n *\n * @param {string|number|undefined} labelWidth\n * @returns {object}\n */\nfunction labelColumnStylesOf(labelWidth) {\n if (labelWidth === FORM_LABEL_WIDTH.auto) {\n return {flex: '1 1 auto', maxWidth: 'none'};\n }\n if (labelWidth !== undefined && labelWidth !== null) {\n const width = typeof labelWidth === 'number' ? `${labelWidth}px` : labelWidth;\n return {flex: `0 0 ${width}`, maxWidth: 'none'};\n }\n return {flex: `0 0 ${LABEL_COLUMN.share}`, maxWidth: LABEL_COLUMN.maxPixels};\n}\n\nconst FormRowLabel = styled(Typography, {name: ROW_THEME_NAME, slot: 'Label', shouldForwardProp: isPartProp})(({theme, ownerState}) => theme.unstable_sx({\n ...labelColumnStylesOf(ownerState?.labelWidth), pt: `${LABEL_TOP_PIXELS}px`, lineHeight: 1.5, color: 'text.primary',\n // va después del ancho: en el teléfono, con la etiqueta arriba, la columna ya no cuenta\n ...(ownerState?.labelPlacement === FORM_LABEL_PLACEMENT.topOnPhone && {\n [theme.breakpoints.down('sm')]: {flex: 'none', maxWidth: 'none', pt: 0},\n }),\n // el asterisco de lo obligatorio: aquí, debajo de lo que el proyecto ponga en su tema\n ...(ownerState?.requiredMark && requiredMarkSxOf(ownerState.requiredMark)),\n}));\n\n\n// el campo: lo que queda del renglón; con la etiqueta en auto, solo lo que ocupa (los botones de un cargador), sin encogerse\n// y hasta la mitad del renglón (un archivo de nombre largo se corta con «…» en lugar de aplastar el nombre del documento).\n// OJO: con '0 1 auto' el renglón angosto lo apretaba y, como el cargador alinea a la derecha, sus botones se salían hacia la\n// izquierda, encima del nombre (pasó en el teléfono). Con la etiqueta arriba en el teléfono, el campo va a todo lo ancho\nconst FormRowControl = styled(Box, {name: ROW_THEME_NAME, slot: 'Control', shouldForwardProp: isPartProp})(({theme, ownerState}) => {\n const isAuto = ownerState?.labelWidth === FORM_LABEL_WIDTH.auto;\n return {\n flex: isAuto ? '0 0 auto' : 1,\n ...(isAuto && {maxWidth: AUTO_CONTROL_MAX_SHARE}),\n ...(isAuto && ownerState?.labelPlacement === FORM_LABEL_PLACEMENT.topOnPhone && {[theme.breakpoints.down('sm')]: {maxWidth: 'none'}}),\n minWidth: 0, display: 'flex', flexDirection: 'column', justifyContent: 'center', minHeight: 40,\n };\n});\n\nconst FormSectionRoot = styled(Box, {name: SECTION_THEME_NAME, slot: 'Root'})({});\n\nconst FormSectionTitle = styled(Typography, {name: SECTION_THEME_NAME, slot: 'Title'})(({theme}) => theme.unstable_sx({px: 2, mb: 0.75, fontWeight: 600}));\n\n// la tarjeta: la línea de arriba de cada renglón, desde donde empieza el texto (la del primero la recorta la tarjeta)\nconst FormSectionCard = styled(Paper, {name: SECTION_THEME_NAME, slot: 'Card'})(({theme}) => ({\n borderRadius: PANEL_RADIUS,\n overflow: 'hidden',\n [`& .${FORM_ROW_CLASS}::before`]: {\n content: '\"\"', position: 'absolute', top: 0, right: 0, left: theme.spacing(2), height: '1px',\n backgroundColor: (theme.vars ?? theme).palette.divider,\n },\n}));\n\nconst FormSectionFooter = styled(Typography, {name: SECTION_THEME_NAME, slot: 'Footer'})(({theme}) => theme.unstable_sx({px: 2, mt: 0.75}));\n\n/**\n * Un renglón del formulario agrupado: la etiqueta a la izquierda y lo que sea a la derecha (un campo, un interruptor, un\n * texto). Los campos del framework lo usan solos dentro de una FormSection; FormRow suelto es para lo demás.\n *\n * @param {object} props\n * @param {import('react').ReactNode} props.label ya traducida\n * @param {string} [props.htmlFor] el id del campo que nombra la etiqueta\n * @param {string} [props.labelId] el id de la etiqueta (para un campo que la nombra con aria-labelledby)\n * @param {boolean} [props.required] el asterisco en la etiqueta (el lector de pantalla no lo lee: el campo ya lo dice)\n * @param {import('react').ReactNode} props.children\n */\nexport function FormRow(props) {\n // lo que el proyecto puso en su tema (components.CalcitaFormRow.defaultProps) y encima lo que llega aquí\n const {label, htmlFor, labelId, required = false, children, ...unknownProps} = useThemeProps({props, name: ROW_THEME_NAME});\n warnUnknownProps(ROW_THEME_NAME, unknownProps);\n const {t} = useToolkitTranslation('common');\n // dónde va la etiqueta lo pide la sección; un FormRow fuera de una sección, a la izquierda\n const sectionLayout = useFormSectionLayout();\n const ownerState = {\n labelPlacement: sectionLayout ? sectionLayout.labelPlacement : FORM_LABEL_PLACEMENT.side,\n labelWidth: sectionLayout ? sectionLayout.labelWidth : undefined,\n requiredMark: required ? t('form.requiredMark') : null,\n };\n return (\n <FormRowRoot className={FORM_ROW_CLASS} ownerState={ownerState}>\n <FormRowLabel component=\"label\" id={labelId} htmlFor={htmlFor} variant=\"body1\" ownerState={ownerState}\n {...(required && {[REQUIRED_LABEL_ATTRIBUTE]: 'true'})}>\n {label}\n </FormRowLabel>\n <FormRowControl ownerState={ownerState}>\n {children}\n </FormRowControl>\n </FormRowRoot>\n );\n}\n\n/**\n * @param {object} props\n * @param {string} [props.title] ya traducido; va arriba de la tarjeta\n * @param {import('react').ReactNode} [props.footer] la explicación, abajo de la tarjeta\n * @param {string} [props.labelPlacement] uno de FORM_LABEL_PLACEMENT: side (por omisión, la etiqueta a la izquierda) o\n * topOnPhone (arriba del campo por debajo de `sm`)\n * @param {'auto'|string|number} [props.labelWidth] lo ancho de la columna de las etiquetas: FORM_LABEL_WIDTH.auto (la etiqueta\n * se lleva lo que sobre y el campo lo que ocupa), un ancho de CSS ('320px', '50%') o un número en px; por omisión el 38 %\n * del renglón hasta 200 px, como siempre\n * @param {import('@mui/material/styles').SxProps<import('@mui/material/styles').Theme>} [props.sx] los estilos de la sección\n * en esta pantalla, encima de los del tema (como en las demás piezas)\n * @param {import('react').ReactNode} props.children los campos, directo o dentro de un Grid, un Stack o un componente propio\n */\nexport default function FormSection(props) {\n // lo que el proyecto puso en su tema (components.CalcitaFormSection.defaultProps) y encima lo que llega aquí\n const {\n title, footer, labelPlacement = FORM_LABEL_PLACEMENT.side, labelWidth, sx, children, ...unknownProps\n } = useThemeProps({props, name: SECTION_THEME_NAME});\n warnUnknownProps(SECTION_THEME_NAME, unknownProps);\n const titleId = useId();\n const layout = useMemo(() => ({labelPlacement, labelWidth}), [labelPlacement, labelWidth]);\n return (\n <FormSectionContext value={layout}>\n <FormSectionRoot component=\"section\" aria-labelledby={title ? titleId : undefined} sx={sx}>\n {title && (\n <FormSectionTitle id={titleId} variant=\"subtitle2\" component=\"h2\" color=\"text.secondary\">\n {title}\n </FormSectionTitle>\n )}\n <FormSectionCard variant=\"outlined\">\n {/* un píxel arriba: la línea de los renglones de hasta arriba queda fuera de la tarjeta (overflow hidden) */}\n <Box sx={{mt: '-1px'}}>{children}</Box>\n </FormSectionCard>\n {footer && (\n <FormSectionFooter variant=\"caption\" component=\"p\" color=\"text.secondary\">{footer}</FormSectionFooter>\n )}\n </FormSectionRoot>\n </FormSectionContext>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;;AA8DO,MAAM,uBAAuB,OAAO,OAAO,EAAC,MAAM,QAAQ,YAAY,cAAa;AAMnF,MAAM,mBAAmB,OAAO,OAAO,EAAC,MAAM,QAAO;AAGrD,MAAM,iBAAiB;AAG9B,MAAM,wBAAwB;AAC9B,MAAM,2BAA2B;AACjC,MAAM,eAAe,OAAO,OAAO,EAAC,OAAO,OAAO,WAAW,KAAI;AAEjE,MAAM,yBAAyB;AAE/B,MAAM,mBAAmB;AAEzB,MAAM,OAAO,OAAO,OAAO,EAAC,cAAc,IAAI,cAAc,IAAI,QAAQ,GAAE;AAW1E,SAAS,eAAe,OAAO;AAC3B,QAAM,WAAW,MAAM,QAAQ,OAAO;AACtC,QAAM,SAAS,CAAC,cAAc,UAAU,IAAI,CAAC,aAAa,iBAAiB,QAAQ,EAAE,EAAE,KAAK,IAAI;AAChG,QAAM,QAAQ,CAAC,0BAA0B,wBAAwB,+BAA+B;AAChG,QAAM,SAAS,CAAC,OAAO,aAAa,EAAC,SAAS,GAAG,KAAK,MAAM,+BAA+B,KAAK,IAAI,OAAO,mBAAmB,eAAe;AAC7I,SAAO;AAAA;AAAA,IAEH,CAAC,OAAO,KAAK,CAAC,GAAG,EAAC,GAAG,yBAAyB,KAAK,GAAG,SAAS,OAAA;AAAA,IAC/D,CAAC,OAAO,MAAM,IAAI,CAAC,SAAS,GAAG,IAAI,cAAc,CAAC,CAAC,GAAG,OAAO,QAAQ,QAAQ,MAAM,KAAK,YAAY;AAAA,IACpG,CAAC,OAAO,MAAM,IAAI,CAAC,SAAS,GAAG,IAAI,YAAY,CAAC,CAAC,GAAG,OAAO,QAAQ,MAAM,MAAM,KAAK,YAAY;AAAA,IAChG,CAAC,OAAO,CAAC,oCAAoC,yCAAyC,CAAC,CAAC,GAAG,EAAC,SAAS,OAAA;AAAA,IACrG,CAAC,OAAO,CAAC,gCAAgC,6BAA6B,CAAC,CAAC,GAAG,EAAC,SAAS,OAAA;AAAA;AAAA;AAAA,IAGrF,CAAC,OAAO,CAAC,+BAA+B,CAAC,CAAC,GAAG,EAAC,OAAO,QAAQ,KAAK,QAAA;AAAA,IAClE,CAAC,OAAO,CAAC,wEAAwE,CAAC,CAAC,GAAG,EAAC,UAAU,YAAY,OAAO,QAAQ,KAAK,UAAA;AAAA;AAAA,IAEjI,6BAA6B,EAAC,cAAc,MAAM,QAAQ,GAAG,GAAG,WAAW,OAAO,cAAc,MAAA;AAAA,IAChG,4DAA4D,EAAC,SAAS,OAAA;AAAA,EAAM;AAEpF;AAGA,MAAM,qBAAqB;AAC3B,MAAM,iBAAiB;AAIvB,MAAM,aAAa,CAAC,aAAa,aAAa;AAE9C,MAAM,cAAc,OAAO,KAAK,EAAC,MAAM,gBAAgB,MAAM,QAAQ,mBAAmB,WAAA,CAAW,EAAE,CAAC,EAAC,OAAO,iBAAgB;AAC1H,QAAM,WAAW,MAAM,QAAQ,OAAO;AACtC,QAAM,eAAe,YAAY,mBAAmB,qBAAqB;AACzE,SAAO,MAAM,YAAY;AAAA,IACrB,UAAU;AAAA,IACV,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,KAAK;AAAA,IACL,WAAW;AAAA,IACX,IAAI;AAAA,IACJ,IAAI,GAAG,wBAAwB;AAAA,IAC/B,0BAA0B,mBAAmB,KAAK;AAAA,IAClD,6BAA6B,EAAC,OAAO,QAAQ,MAAM,KAAA;AAAA;AAAA,IAEnD,GAAG,iBAAiB,KAAK;AAAA,IACzB,GAAG,eAAe,KAAK;AAAA;AAAA,IAEvB,GAAI,gBAAgB,EAAC,CAAC,MAAM,YAAY,KAAK,IAAI,CAAC,GAAG,EAAC,eAAe,UAAU,YAAY,WAAW,KAAK,MAAG;AAAA,EAAC,CAClH;AACL,CAAC;AASD,SAAS,oBAAoB,YAAY;AACrC,MAAI,eAAe,iBAAiB,MAAM;AACtC,WAAO,EAAC,MAAM,YAAY,UAAU,OAAA;AAAA,EACxC;AACA,MAAI,eAAe,UAAa,eAAe,MAAM;AACjD,UAAM,QAAQ,OAAO,eAAe,WAAW,GAAG,UAAU,OAAO;AACnE,WAAO,EAAC,MAAM,OAAO,KAAK,IAAI,UAAU,OAAA;AAAA,EAC5C;AACA,SAAO,EAAC,MAAM,OAAO,aAAa,KAAK,IAAI,UAAU,aAAa,UAAA;AACtE;AAEA,MAAM,eAAe,OAAO,YAAY,EAAC,MAAM,gBAAgB,MAAM,SAAS,mBAAmB,WAAA,CAAW,EAAE,CAAC,EAAC,OAAO,WAAA,MAAgB,MAAM,YAAY;AAAA,EACrJ,GAAG,oBAAoB,YAAY,UAAU;AAAA,EAAG,IAAI,GAAG,gBAAgB;AAAA,EAAM,YAAY;AAAA,EAAK,OAAO;AAAA;AAAA,EAErG,GAAI,YAAY,mBAAmB,qBAAqB,cAAc;AAAA,IAClE,CAAC,MAAM,YAAY,KAAK,IAAI,CAAC,GAAG,EAAC,MAAM,QAAQ,UAAU,QAAQ,IAAI,EAAA;AAAA,EAAC;AAAA;AAAA,EAG1E,GAAI,YAAY,gBAAgB,iBAAiB,WAAW,YAAY;AAC5E,CAAC,CAAC;AAOF,MAAM,iBAAiB,OAAO,KAAK,EAAC,MAAM,gBAAgB,MAAM,WAAW,mBAAmB,WAAA,CAAW,EAAE,CAAC,EAAC,OAAO,iBAAgB;AAChI,QAAM,SAAS,YAAY,eAAe,iBAAiB;AAC3D,SAAO;AAAA,IACH,MAAM,SAAS,aAAa;AAAA,IAC5B,GAAI,UAAU,EAAC,UAAU,uBAAA;AAAA,IACzB,GAAI,UAAU,YAAY,mBAAmB,qBAAqB,cAAc,EAAC,CAAC,MAAM,YAAY,KAAK,IAAI,CAAC,GAAG,EAAC,UAAU,SAAM;AAAA,IAClI,UAAU;AAAA,IAAG,SAAS;AAAA,IAAQ,eAAe;AAAA,IAAU,gBAAgB;AAAA,IAAU,WAAW;AAAA,EAAA;AAEpG,CAAC;AAED,MAAM,kBAAkB,OAAO,KAAK,EAAC,MAAM,oBAAoB,MAAM,QAAO,EAAE,EAAE;AAEhF,MAAM,mBAAmB,OAAO,YAAY,EAAC,MAAM,oBAAoB,MAAM,SAAQ,EAAE,CAAC,EAAC,YAAW,MAAM,YAAY,EAAC,IAAI,GAAG,IAAI,MAAM,YAAY,IAAA,CAAI,CAAC;AAGzJ,MAAM,kBAAkB,OAAO,OAAO,EAAC,MAAM,oBAAoB,MAAM,OAAA,CAAO,EAAE,CAAC,EAAC,aAAY;AAAA,EAC1F,cAAc;AAAA,EACd,UAAU;AAAA,EACV,CAAC,MAAM,cAAc,UAAU,GAAG;AAAA,IAC9B,SAAS;AAAA,IAAM,UAAU;AAAA,IAAY,KAAK;AAAA,IAAG,OAAO;AAAA,IAAG,MAAM,MAAM,QAAQ,CAAC;AAAA,IAAG,QAAQ;AAAA,IACvF,kBAAkB,MAAM,QAAQ,OAAO,QAAQ;AAAA,EAAA;AAEvD,EAAE;AAEF,MAAM,oBAAoB,OAAO,YAAY,EAAC,MAAM,oBAAoB,MAAM,SAAA,CAAS,EAAE,CAAC,EAAC,YAAW,MAAM,YAAY,EAAC,IAAI,GAAG,IAAI,KAAA,CAAK,CAAC;AAanI,SAAS,QAAQ,OAAO;AAE3B,QAAM,EAAC,OAAO,SAAS,SAAS,WAAW,OAAO,UAAU,GAAG,aAAA,IAAgB,cAAc,EAAC,OAAO,MAAM,gBAAe;AAC1H,mBAAiB,gBAAgB,YAAY;AAC7C,QAAM,EAAC,EAAA,IAAK,sBAAsB,QAAQ;AAE1C,QAAM,gBAAgB,qBAAA;AACtB,QAAM,aAAa;AAAA,IACf,gBAAgB,gBAAgB,cAAc,iBAAiB,qBAAqB;AAAA,IACpF,YAAY,gBAAgB,cAAc,aAAa;AAAA,IACvD,cAAc,WAAW,EAAE,mBAAmB,IAAI;AAAA,EAAA;AAEtD,SACI,qBAAC,aAAA,EAAY,WAAW,gBAAgB,YACpC,UAAA;AAAA,IAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QAAa,WAAU;AAAA,QAAQ,IAAI;AAAA,QAAS;AAAA,QAAkB,SAAQ;AAAA,QAAQ;AAAA,QAChE,GAAI,YAAY,EAAC,CAAC,wBAAwB,GAAG,OAAA;AAAA,QACvD,UAAA;AAAA,MAAA;AAAA,IAAA;AAAA,IAEL,oBAAC,gBAAA,EAAe,YACX,SAAA,CACL;AAAA,EAAA,GACJ;AAER;AAeA,SAAwB,YAAY,OAAO;AAEvC,QAAM;AAAA,IACF;AAAA,IAAO;AAAA,IAAQ,iBAAiB,qBAAqB;AAAA,IAAM;AAAA,IAAY;AAAA,IAAI;AAAA,IAAU,GAAG;AAAA,EAAA,IACxF,cAAc,EAAC,OAAO,MAAM,oBAAmB;AACnD,mBAAiB,oBAAoB,YAAY;AACjD,QAAM,UAAU,MAAA;AAChB,QAAM,SAAS,QAAQ,OAAO,EAAC,gBAAgB,eAAc,CAAC,gBAAgB,UAAU,CAAC;AACzF,SACI,oBAAC,oBAAA,EAAmB,OAAO,QACvB,UAAA,qBAAC,iBAAA,EAAgB,WAAU,WAAU,mBAAiB,QAAQ,UAAU,QAAW,IAC9E,UAAA;AAAA,IAAA,SACG,oBAAC,kBAAA,EAAiB,IAAI,SAAS,SAAQ,aAAY,WAAU,MAAK,OAAM,kBACnE,UAAA,MAAA,CACL;AAAA,IAEJ,oBAAC,iBAAA,EAAgB,SAAQ,YAErB,UAAA,oBAAC,KAAA,EAAI,IAAI,EAAC,IAAI,OAAA,GAAU,SAAA,CAAS,EAAA,CACrC;AAAA,IACC,8BACI,mBAAA,EAAkB,SAAQ,WAAU,WAAU,KAAI,OAAM,kBAAkB,UAAA,OAAA,CAAO;AAAA,EAAA,EAAA,CAE1F,EAAA,CACJ;AAER;"}
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useId } from "react";
|
|
3
4
|
import { useToolkitTranslation } from "../toolkit/useToolkitTranslation.js";
|
|
4
5
|
import Box from "@mui/material/Box";
|
|
5
|
-
import Button from "@mui/material/Button";
|
|
6
|
+
import Button, { buttonClasses } from "@mui/material/Button";
|
|
6
7
|
import Stack from "@mui/material/Stack";
|
|
7
8
|
import Step from "@mui/material/Step";
|
|
8
9
|
import StepButton from "@mui/material/StepButton";
|
|
9
10
|
import StepContent from "@mui/material/StepContent";
|
|
10
11
|
import StepLabel from "@mui/material/StepLabel";
|
|
11
12
|
import Stepper from "@mui/material/Stepper";
|
|
13
|
+
import Tooltip from "@mui/material/Tooltip";
|
|
12
14
|
import Typography from "@mui/material/Typography";
|
|
13
15
|
import { WIZARD_ORIENTATION, isStepCompleted } from "./wizardSteps.js";
|
|
14
16
|
import { piecePart, withPieceSlots, usePieceProps } from "../theme/themePieces.js";
|
|
@@ -18,12 +20,14 @@ const THEME_NAME = "CalcitaWizardLayout";
|
|
|
18
20
|
const WizardStepper = piecePart(THEME_NAME, "Stepper", Stepper);
|
|
19
21
|
const WizardPhoneSteps = piecePart(THEME_NAME, "PhoneSteps", Box);
|
|
20
22
|
const WizardActions = piecePart(THEME_NAME, "Actions", Stack);
|
|
23
|
+
const WizardNextReason = piecePart(THEME_NAME, "NextReason", Typography);
|
|
21
24
|
function WizardLayoutContent(props) {
|
|
22
25
|
const {
|
|
23
26
|
steps,
|
|
24
27
|
activeIndex,
|
|
25
28
|
skippedKeys = NO_SKIPPED_KEYS,
|
|
26
29
|
isSubmitting = false,
|
|
30
|
+
nextDisabledReason = null,
|
|
27
31
|
orientation = WIZARD_ORIENTATION.horizontal,
|
|
28
32
|
hideBackOnFirstStep = false,
|
|
29
33
|
onBack,
|
|
@@ -37,11 +41,13 @@ function WizardLayoutContent(props) {
|
|
|
37
41
|
} = usePieceProps(props, THEME_NAME);
|
|
38
42
|
warnUnknownProps(THEME_NAME, unknownProps);
|
|
39
43
|
const { t } = useToolkitTranslation("common", texts);
|
|
44
|
+
const reasonId = useId();
|
|
40
45
|
const activeStep = steps[activeIndex];
|
|
41
46
|
const isLastStep = activeIndex === steps.length - 1;
|
|
42
47
|
const isVertical = orientation === WIZARD_ORIENTATION.vertical;
|
|
43
48
|
const isSelectable = typeof onStepSelect === "function";
|
|
44
49
|
const isBackHidden = hideBackOnFirstStep && activeIndex === 0;
|
|
50
|
+
const isNextBlocked = Boolean(nextDisabledReason);
|
|
45
51
|
const stepTitleOf = (step, index) => {
|
|
46
52
|
let caption;
|
|
47
53
|
if (skippedKeys.has(step.key)) {
|
|
@@ -55,17 +61,32 @@ function WizardLayoutContent(props) {
|
|
|
55
61
|
}
|
|
56
62
|
return /* @__PURE__ */ jsx(StepLabel, { optional, children: step.label });
|
|
57
63
|
};
|
|
58
|
-
const
|
|
64
|
+
const nextButton = isLastStep ? submitButton : /* @__PURE__ */ jsx(
|
|
65
|
+
Button,
|
|
66
|
+
{
|
|
67
|
+
variant: "contained",
|
|
68
|
+
size: "large",
|
|
69
|
+
onClick: isNextBlocked ? void 0 : onNext,
|
|
70
|
+
...isNextBlocked && { "aria-disabled": true, "aria-describedby": reasonId, className: buttonClasses.disabled },
|
|
71
|
+
children: t("wizard.next")
|
|
72
|
+
}
|
|
73
|
+
);
|
|
74
|
+
const forwardButton = isNextBlocked ? /* @__PURE__ */ jsx(Tooltip, { title: nextDisabledReason, describeChild: true, disableTouchListener: true, children: /* @__PURE__ */ jsx(Box, { component: "span", sx: { display: "flex", flexDirection: "column" }, children: nextButton }) }) : nextButton;
|
|
75
|
+
const buttons = (
|
|
59
76
|
// sin «Anterior», los de seguir se quedan a la derecha (solos, space-between los mandaba a la izquierda; un margen
|
|
60
77
|
// automático no sirve: Stack les pone margin 0 a sus hijos con más fuerza)
|
|
61
78
|
/* @__PURE__ */ jsxs(WizardActions, { direction: { xs: "column-reverse", sm: "row" }, spacing: 1.5, ownSx: { justifyContent: isBackHidden ? "flex-end" : "space-between" }, children: [
|
|
62
79
|
!isBackHidden && /* @__PURE__ */ jsx(Button, { variant: "text", color: "inherit", disabled: activeIndex === 0 || isSubmitting, onClick: onBack, children: t("wizard.back") }),
|
|
63
80
|
/* @__PURE__ */ jsxs(Stack, { direction: { xs: "column-reverse", sm: "row" }, spacing: 1.5, children: [
|
|
64
81
|
activeStep.optional && !isLastStep && onSkip && /* @__PURE__ */ jsx(Button, { variant: "text", onClick: onSkip, children: t("wizard.skip") }),
|
|
65
|
-
|
|
82
|
+
forwardButton
|
|
66
83
|
] })
|
|
67
84
|
] })
|
|
68
85
|
);
|
|
86
|
+
const actions = isNextBlocked ? /* @__PURE__ */ jsxs(Stack, { spacing: 1, useFlexGap: true, children: [
|
|
87
|
+
/* @__PURE__ */ jsx(WizardNextReason, { id: reasonId, variant: "body2", color: "text.secondary", ownSx: { display: { xs: "block", sm: "none" } }, children: nextDisabledReason }),
|
|
88
|
+
buttons
|
|
89
|
+
] }) : buttons;
|
|
69
90
|
const completedOf = (step, index) => isStepCompleted(step, { index, activeIndex, skippedKeys, isSelectable });
|
|
70
91
|
if (isVertical) {
|
|
71
92
|
return (
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WizardLayout.js","sources":["../../src/inputs/WizardLayout.jsx"],"sourcesContent":["// Lo que se ve de un formulario por pasos: los pasos, los campos del paso abierto y los botones (Anterior · Omitir este paso ·\n// Siguiente o Enviar). No sabe de formik ni de react-hook-form: el FormWizard de cada uno (/forms y /react-hook-form) pone la\n// lógica (qué revisa «Siguiente», qué hace «Omitir», si se puede saltar a otro paso) y le pasa su botón de enviar y su aviso de\n// errores. Suelto sirve para armar un asistente propio con otra librería.\n//\n// Dos acomodos (WIZARD_ORIENTATION):\n// horizontal los pasos en fila arriba y los campos abajo; en el teléfono no caben los nombres: «Paso 2 de 4» y su nombre;\n// vertical uno debajo del otro, como un acordeón: los campos y los botones van dentro del paso abierto (también en el\n// teléfono). Es el de editar un expediente: se ve de un vistazo qué pasos hay y se entra al que hace falta.\n//\n// Con onStepSelect los títulos de los pasos se pulsan (con teclado también: son botones) y avisan qué paso se pidió; quien lo\n// usa decide si salta. Sin él, los títulos solo informan (lo de siempre). Omitir es opcional: sin onSkip no sale el botón.\n//\n// Los textos se cambian con `texts`, con la forma de `common` en los textos del paquete: {wizard: {next: 'Continuar'}}.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaWizardLayout con defaultProps y\n// styleOverrides de sus partes: stepper, phoneSteps, actions.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport Box from '@mui/material/Box';\nimport Button from '@mui/material/Button';\nimport Stack from '@mui/material/Stack';\nimport Step from '@mui/material/Step';\nimport StepButton from '@mui/material/StepButton';\nimport StepContent from '@mui/material/StepContent';\nimport StepLabel from '@mui/material/StepLabel';\nimport Stepper from '@mui/material/Stepper';\nimport Typography from '@mui/material/Typography';\nimport {WIZARD_ORIENTATION, isStepCompleted} from '@/inputs/wizardSteps.js';\nimport {piecePart, usePieceProps, withPieceSlots} from '@/theme/themePieces.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\n// sin pasos omitidos (quien no usa «Omitir» no tiene que mandar nada)\nconst NO_SKIPPED_KEYS = new Set();\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaWizardLayout)\nconst THEME_NAME = 'CalcitaWizardLayout';\nconst WizardStepper = piecePart(THEME_NAME, 'Stepper', Stepper);\nconst WizardPhoneSteps = piecePart(THEME_NAME, 'PhoneSteps', Box);\nconst WizardActions = piecePart(THEME_NAME, 'Actions', Stack);\n\n/**\n * @param {object} props\n * @param {Object[]} props.steps en orden\n * @param {string} props.steps[].key\n * @param {string} props.steps[].label ya traducido\n * @param {boolean} [props.steps[].optional] se puede omitir\n * @param {boolean} [props.steps[].completed] lleva la palomita; por omisión, en fila y sin pulsar, los de antes del abierto\n * @param {import('react').ReactNode} props.steps[].content los campos del paso\n * @param {number} props.activeIndex el paso que se ve\n * @param {Set<string>} [props.skippedKeys] los pasos omitidos\n * @param {boolean} [props.isSubmitting] mientras se envía, «Anterior» se apaga\n * @param {'horizontal'|'vertical'} [props.orientation] uno de WIZARD_ORIENTATION; por omisión horizontal\n * @param {boolean} [props.hideBackOnFirstStep] en el primer paso no se pinta «Anterior» (no hay a dónde volver); por omisión\n * sale apagado, como antes\n * @param {() => void} props.onBack\n * @param {() => void} props.onNext\n * @param {() => void} [props.onSkip] sin él no sale «Omitir este paso»\n * @param {(index: number) => void} [props.onStepSelect] con él, los títulos de los pasos se pulsan y avisan cuál se pidió\n * @param {import('react').ReactNode} props.submitButton el botón de enviar del último paso\n * @param {import('react').ReactNode} [props.problemAlert] el aviso de errores del servidor, arriba de los botones del último paso\n * @param {object} [props.texts] los textos que cambian, con la forma de `common`: {wizard: {next, back, skip, optional, skipped, stepOf}}\n */\nfunction WizardLayoutContent(props) {\n // lo que el proyecto puso en su tema (components.CalcitaWizardLayout.defaultProps) y encima lo que llega aquí\n const {\n steps, activeIndex, skippedKeys = NO_SKIPPED_KEYS, isSubmitting = false, orientation = WIZARD_ORIENTATION.horizontal,\n hideBackOnFirstStep = false, onBack, onNext, onSkip, onStepSelect, submitButton, problemAlert, texts,\n ...unknownProps\n } = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const {t} = useToolkitTranslation('common', texts);\n const activeStep = steps[activeIndex];\n const isLastStep = activeIndex === steps.length - 1;\n const isVertical = orientation === WIZARD_ORIENTATION.vertical;\n const isSelectable = typeof onStepSelect === 'function';\n const isBackHidden = hideBackOnFirstStep && activeIndex === 0;\n\n // el título de un paso: con onStepSelect es un botón (se pulsa y se llega con Tab); sin él, solo informa\n const stepTitleOf = (step, index) => {\n let caption;\n if (skippedKeys.has(step.key)) {\n caption = t('wizard.skipped');\n } else if (step.optional) {\n caption = t('wizard.optional');\n }\n const optional = caption && <Typography variant=\"caption\" color=\"text.secondary\">{caption}</Typography>;\n if (isSelectable) {\n return <StepButton optional={optional} onClick={() => onStepSelect(index)}>{step.label}</StepButton>;\n }\n return <StepLabel optional={optional}>{step.label}</StepLabel>;\n };\n\n // los botones del paso abierto: en fila van abajo de los campos; en vertical, dentro del paso\n const actions = (\n // sin «Anterior», los de seguir se quedan a la derecha (solos, space-between los mandaba a la izquierda; un margen\n // automático no sirve: Stack les pone margin 0 a sus hijos con más fuerza)\n <WizardActions direction={{xs: 'column-reverse', sm: 'row'}} spacing={1.5} ownSx={{justifyContent: isBackHidden ? 'flex-end' : 'space-between'}}>\n {!isBackHidden && (\n <Button variant=\"text\" color=\"inherit\" disabled={activeIndex === 0 || isSubmitting} onClick={onBack}>\n {t('wizard.back')}\n </Button>\n )}\n <Stack direction={{xs: 'column-reverse', sm: 'row'}} spacing={1.5}>\n {activeStep.optional && !isLastStep && onSkip && (\n <Button variant=\"text\" onClick={onSkip}>{t('wizard.skip')}</Button>\n )}\n {isLastStep\n ? submitButton\n : <Button variant=\"contained\" size=\"large\" onClick={onNext}>{t('wizard.next')}</Button>}\n </Stack>\n </WizardActions>\n );\n const completedOf = (step, index) => isStepCompleted(step, {index, activeIndex, skippedKeys, isSelectable});\n\n if (isVertical) {\n return (\n // nonLinear: con los títulos que se pulsan, MUI no debe apagar los pasos de adelante\n <WizardStepper activeStep={activeIndex} orientation=\"vertical\" nonLinear={isSelectable}>\n {steps.map((step, index) => (\n <Step key={step.key} completed={completedOf(step, index)}>\n {stepTitleOf(step, index)}\n {/* solo el paso abierto tiene sus campos montados: al cerrarse se desmontan (unmountOnExit). Los botones\n van solo en el abierto: el que se cierra (la animación dura un momento) ya no es el paso de esos\n botones, y un «Guardar» repetido confunde */}\n <StepContent slotProps={{transition: {unmountOnExit: true}}}>\n <Stack spacing={3} sx={{pt: 1}}>\n <Box>{step.content}</Box>\n {index === activeIndex && isLastStep && problemAlert}\n {index === activeIndex && actions}\n </Stack>\n </StepContent>\n </Step>\n ))}\n </WizardStepper>\n );\n }\n\n return (\n <Stack spacing={3}>\n {/* en el teléfono los nombres de los pasos no caben: se dice en qué paso va */}\n <WizardStepper activeStep={activeIndex} alternativeLabel nonLinear={isSelectable} ownSx={{display: {xs: 'none', sm: 'flex'}}}>\n {steps.map((step, index) => (\n <Step key={step.key} completed={completedOf(step, index)}>\n {stepTitleOf(step, index)}\n </Step>\n ))}\n </WizardStepper>\n <WizardPhoneSteps ownSx={{display: {xs: 'block', sm: 'none'}}}>\n <Typography variant=\"overline\" color=\"text.secondary\">{t('wizard.stepOf', {current: activeIndex + 1, total: steps.length})}</Typography>\n <Typography variant=\"h6\">{activeStep.label}</Typography>\n </WizardPhoneSteps>\n\n <Box>{activeStep.content}</Box>\n\n {isLastStep && problemAlert}\n\n {actions}\n </Stack>\n );\n}\n\n// lo que se exporta: la pieza con slots y slotProps para cambiar sus partes (themePieces.js)\nconst WizardLayout = withPieceSlots(WizardLayoutContent, THEME_NAME);\nexport default WizardLayout;\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"WizardLayout.js","sources":["../../src/inputs/WizardLayout.jsx"],"sourcesContent":["// Lo que se ve de un formulario por pasos: los pasos, los campos del paso abierto y los botones (Anterior · Omitir este paso ·\n// Siguiente o Enviar). No sabe de formik ni de react-hook-form: el FormWizard de cada uno (/forms y /react-hook-form) pone la\n// lógica (qué revisa «Siguiente», qué hace «Omitir», si se puede saltar a otro paso) y le pasa su botón de enviar y su aviso de\n// errores. Suelto sirve para armar un asistente propio con otra librería.\n//\n// Dos acomodos (WIZARD_ORIENTATION):\n// horizontal los pasos en fila arriba y los campos abajo; en el teléfono no caben los nombres: «Paso 2 de 4» y su nombre;\n// vertical uno debajo del otro, como un acordeón: los campos y los botones van dentro del paso abierto (también en el\n// teléfono). Es el de editar un expediente: se ve de un vistazo qué pasos hay y se entra al que hace falta.\n//\n// Con onStepSelect los títulos de los pasos se pulsan (con teclado también: son botones) y avisan qué paso se pidió; quien lo\n// usa decide si salta. Sin él, los títulos solo informan (lo de siempre). Omitir es opcional: sin onSkip no sale el botón.\n//\n// Un paso que todavía no se puede dejar (faltan documentos obligatorios) lo dice con nextDisabledReason, ya traducido: «Siguiente»\n// se ve apagado y no avanza, y el motivo sale en un aviso al pasar el cursor o al llegar con Tab. OJO: el botón NO lleva\n// `disabled`: un botón apagado así no recibe el foco y con el teclado no habría cómo saber por qué; va con aria-disabled y la\n// clase de apagado de MUI (se ve igual que uno apagado, también con el tema). En el teléfono un aviso no sale con el dedo: el\n// motivo va escrito arriba de los botones. En el último paso el botón de enviar es de quien lo pasa en submitButton: él lo\n// apaga (el FormWizard de formik y el de react-hook-form ya lo hacen); el motivo sale igual.\n//\n// Los textos se cambian con `texts`, con la forma de `common` en los textos del paquete: {wizard: {next: 'Continuar'}}.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaWizardLayout con defaultProps y\n// styleOverrides de sus partes: stepper, phoneSteps, actions, nextReason (el motivo escrito en el teléfono).\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useId} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport Box from '@mui/material/Box';\nimport Button, {buttonClasses} from '@mui/material/Button';\nimport Stack from '@mui/material/Stack';\nimport Step from '@mui/material/Step';\nimport StepButton from '@mui/material/StepButton';\nimport StepContent from '@mui/material/StepContent';\nimport StepLabel from '@mui/material/StepLabel';\nimport Stepper from '@mui/material/Stepper';\nimport Tooltip from '@mui/material/Tooltip';\nimport Typography from '@mui/material/Typography';\nimport {WIZARD_ORIENTATION, isStepCompleted} from '@/inputs/wizardSteps.js';\nimport {piecePart, usePieceProps, withPieceSlots} from '@/theme/themePieces.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\n// sin pasos omitidos (quien no usa «Omitir» no tiene que mandar nada)\nconst NO_SKIPPED_KEYS = new Set();\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaWizardLayout)\nconst THEME_NAME = 'CalcitaWizardLayout';\nconst WizardStepper = piecePart(THEME_NAME, 'Stepper', Stepper);\nconst WizardPhoneSteps = piecePart(THEME_NAME, 'PhoneSteps', Box);\nconst WizardActions = piecePart(THEME_NAME, 'Actions', Stack);\nconst WizardNextReason = piecePart(THEME_NAME, 'NextReason', Typography);\n\n/**\n * @param {object} props\n * @param {Object[]} props.steps en orden\n * @param {string} props.steps[].key\n * @param {string} props.steps[].label ya traducido\n * @param {boolean} [props.steps[].optional] se puede omitir\n * @param {boolean} [props.steps[].completed] lleva la palomita; por omisión, en fila y sin pulsar, los de antes del abierto\n * @param {import('react').ReactNode} props.steps[].content los campos del paso\n * @param {number} props.activeIndex el paso que se ve\n * @param {Set<string>} [props.skippedKeys] los pasos omitidos\n * @param {boolean} [props.isSubmitting] mientras se envía, «Anterior» se apaga\n * @param {import('react').ReactNode} [props.nextDisabledReason] ya traducido: el paso abierto todavía no se puede dejar\n * («Faltan 2 documentos»). «Siguiente» se ve apagado y no avanza, y el motivo sale en un aviso (escrito, en el teléfono).\n * En el último paso, apague usted su submitButton. Sin él, como siempre\n * @param {'horizontal'|'vertical'} [props.orientation] uno de WIZARD_ORIENTATION; por omisión horizontal\n * @param {boolean} [props.hideBackOnFirstStep] en el primer paso no se pinta «Anterior» (no hay a dónde volver); por omisión\n * sale apagado, como antes\n * @param {() => void} props.onBack\n * @param {() => void} props.onNext\n * @param {() => void} [props.onSkip] sin él no sale «Omitir este paso»\n * @param {(index: number) => void} [props.onStepSelect] con él, los títulos de los pasos se pulsan y avisan cuál se pidió\n * @param {import('react').ReactNode} props.submitButton el botón de enviar del último paso\n * @param {import('react').ReactNode} [props.problemAlert] el aviso de errores del servidor, arriba de los botones del último paso\n * @param {object} [props.texts] los textos que cambian, con la forma de `common`: {wizard: {next, back, skip, optional, skipped, stepOf}}\n */\nfunction WizardLayoutContent(props) {\n // lo que el proyecto puso en su tema (components.CalcitaWizardLayout.defaultProps) y encima lo que llega aquí\n const {\n steps, activeIndex, skippedKeys = NO_SKIPPED_KEYS, isSubmitting = false, nextDisabledReason = null,\n orientation = WIZARD_ORIENTATION.horizontal, hideBackOnFirstStep = false, onBack, onNext, onSkip, onStepSelect, submitButton,\n problemAlert, texts, ...unknownProps\n } = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const {t} = useToolkitTranslation('common', texts);\n const reasonId = useId();\n const activeStep = steps[activeIndex];\n const isLastStep = activeIndex === steps.length - 1;\n const isVertical = orientation === WIZARD_ORIENTATION.vertical;\n const isSelectable = typeof onStepSelect === 'function';\n const isBackHidden = hideBackOnFirstStep && activeIndex === 0;\n const isNextBlocked = Boolean(nextDisabledReason);\n\n // el título de un paso: con onStepSelect es un botón (se pulsa y se llega con Tab); sin él, solo informa\n const stepTitleOf = (step, index) => {\n let caption;\n if (skippedKeys.has(step.key)) {\n caption = t('wizard.skipped');\n } else if (step.optional) {\n caption = t('wizard.optional');\n }\n const optional = caption && <Typography variant=\"caption\" color=\"text.secondary\">{caption}</Typography>;\n if (isSelectable) {\n return <StepButton optional={optional} onClick={() => onStepSelect(index)}>{step.label}</StepButton>;\n }\n return <StepLabel optional={optional}>{step.label}</StepLabel>;\n };\n\n // el botón de seguir: «Siguiente», o el de enviar en el último paso. Con un motivo para no avanzar, «Siguiente» se ve\n // apagado pero se sigue alcanzando con Tab (aria-disabled, no disabled: ver el encabezado) y no hace nada al pulsarlo\n const nextButton = isLastStep ? submitButton : (\n <Button variant=\"contained\" size=\"large\" onClick={isNextBlocked ? undefined : onNext}\n {...(isNextBlocked && {'aria-disabled': true, 'aria-describedby': reasonId, className: buttonClasses.disabled})}>\n {t('wizard.next')}\n </Button>\n );\n // el aviso va sobre una caja alrededor del botón: un botón que se ve apagado no recibe el cursor (MUI le quita los eventos)\n // y la caja sí. En el teléfono no se abre con el dedo: ahí el motivo va escrito\n const forwardButton = isNextBlocked ? (\n <Tooltip title={nextDisabledReason} describeChild disableTouchListener>\n <Box component=\"span\" sx={{display: 'flex', flexDirection: 'column'}}>{nextButton}</Box>\n </Tooltip>\n ) : nextButton;\n\n // los botones del paso abierto: en fila van abajo de los campos; en vertical, dentro del paso\n const buttons = (\n // sin «Anterior», los de seguir se quedan a la derecha (solos, space-between los mandaba a la izquierda; un margen\n // automático no sirve: Stack les pone margin 0 a sus hijos con más fuerza)\n <WizardActions direction={{xs: 'column-reverse', sm: 'row'}} spacing={1.5} ownSx={{justifyContent: isBackHidden ? 'flex-end' : 'space-between'}}>\n {!isBackHidden && (\n <Button variant=\"text\" color=\"inherit\" disabled={activeIndex === 0 || isSubmitting} onClick={onBack}>\n {t('wizard.back')}\n </Button>\n )}\n <Stack direction={{xs: 'column-reverse', sm: 'row'}} spacing={1.5}>\n {activeStep.optional && !isLastStep && onSkip && (\n <Button variant=\"text\" onClick={onSkip}>{t('wizard.skip')}</Button>\n )}\n {forwardButton}\n </Stack>\n </WizardActions>\n );\n // con un motivo, escrito arriba de los botones en el teléfono (en pantallas anchas lo dice el aviso). Sin motivo, los\n // botones solos, como siempre. useFlexGap: el motivo escondido no deja hueco arriba de los botones\n const actions = isNextBlocked ? (\n <Stack spacing={1} useFlexGap>\n <WizardNextReason id={reasonId} variant=\"body2\" color=\"text.secondary\" ownSx={{display: {xs: 'block', sm: 'none'}}}>\n {nextDisabledReason}\n </WizardNextReason>\n {buttons}\n </Stack>\n ) : buttons;\n const completedOf = (step, index) => isStepCompleted(step, {index, activeIndex, skippedKeys, isSelectable});\n\n if (isVertical) {\n return (\n // nonLinear: con los títulos que se pulsan, MUI no debe apagar los pasos de adelante\n <WizardStepper activeStep={activeIndex} orientation=\"vertical\" nonLinear={isSelectable}>\n {steps.map((step, index) => (\n <Step key={step.key} completed={completedOf(step, index)}>\n {stepTitleOf(step, index)}\n {/* solo el paso abierto tiene sus campos montados: al cerrarse se desmontan (unmountOnExit). Los botones\n van solo en el abierto: el que se cierra (la animación dura un momento) ya no es el paso de esos\n botones, y un «Guardar» repetido confunde */}\n <StepContent slotProps={{transition: {unmountOnExit: true}}}>\n <Stack spacing={3} sx={{pt: 1}}>\n <Box>{step.content}</Box>\n {index === activeIndex && isLastStep && problemAlert}\n {index === activeIndex && actions}\n </Stack>\n </StepContent>\n </Step>\n ))}\n </WizardStepper>\n );\n }\n\n return (\n <Stack spacing={3}>\n {/* en el teléfono los nombres de los pasos no caben: se dice en qué paso va */}\n <WizardStepper activeStep={activeIndex} alternativeLabel nonLinear={isSelectable} ownSx={{display: {xs: 'none', sm: 'flex'}}}>\n {steps.map((step, index) => (\n <Step key={step.key} completed={completedOf(step, index)}>\n {stepTitleOf(step, index)}\n </Step>\n ))}\n </WizardStepper>\n <WizardPhoneSteps ownSx={{display: {xs: 'block', sm: 'none'}}}>\n <Typography variant=\"overline\" color=\"text.secondary\">{t('wizard.stepOf', {current: activeIndex + 1, total: steps.length})}</Typography>\n <Typography variant=\"h6\">{activeStep.label}</Typography>\n </WizardPhoneSteps>\n\n <Box>{activeStep.content}</Box>\n\n {isLastStep && problemAlert}\n\n {actions}\n </Stack>\n );\n}\n\n// lo que se exporta: la pieza con slots y slotProps para cambiar sus partes (themePieces.js)\nconst WizardLayout = withPieceSlots(WizardLayoutContent, THEME_NAME);\nexport default WizardLayout;\n"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { createContext, useContext } from "react";
|
|
3
3
|
const FormSectionContext = createContext(
|
|
4
|
-
/** @type {false|
|
|
4
|
+
/** @type {false|FormSectionLayout} */
|
|
5
5
|
false
|
|
6
6
|
);
|
|
7
7
|
const useIsInsideFormSection = () => Boolean(useContext(FormSectionContext));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"formLayoutContext.js","sources":["../../src/inputs/formLayoutContext.js"],"sourcesContent":["// Si un campo está dentro de una FormSection (el formulario agrupado de Apple): ahí cada campo se pinta como renglón, con\n// la etiqueta a la izquierda y el valor a la derecha. Fuera de ella, la etiqueta va arriba del campo.\n//\n// El valor es false fuera de una sección (RepeatingFields lo vuelve a poner en false para sus renglones) y, dentro, cómo\n// pide la sección sus renglones: {labelPlacement}.\n//\n// Va en su propio archivo y no junto a FormSection: FieldFrame lo lee, y así FieldFrame no importa a FormSection (que a su\n// vez usa FormRow).\nimport {createContext, useContext} from 'react';\n\n/** @type {import('react').Context<false|
|
|
1
|
+
{"version":3,"file":"formLayoutContext.js","sources":["../../src/inputs/formLayoutContext.js"],"sourcesContent":["// Si un campo está dentro de una FormSection (el formulario agrupado de Apple): ahí cada campo se pinta como renglón, con\n// la etiqueta a la izquierda y el valor a la derecha. Fuera de ella, la etiqueta va arriba del campo.\n//\n// El valor es false fuera de una sección (RepeatingFields lo vuelve a poner en false para sus renglones) y, dentro, cómo\n// pide la sección sus renglones: {labelPlacement, labelWidth}.\n//\n// Va en su propio archivo y no junto a FormSection: FieldFrame lo lee, y así FieldFrame no importa a FormSection (que a su\n// vez usa FormRow).\nimport {createContext, useContext} from 'react';\n\n/** @typedef {{labelPlacement: string, labelWidth?: string|number}} FormSectionLayout */\n\n/** @type {import('react').Context<false|FormSectionLayout>} */\nexport const FormSectionContext = createContext(/** @type {false|FormSectionLayout} */ (false));\n\n/** true si el campo va dentro de una FormSection. */\nexport const useIsInsideFormSection = () => Boolean(useContext(FormSectionContext));\n\n/** Cómo pide la sección sus renglones ({labelPlacement, labelWidth}); false fuera de una sección. */\nexport const useFormSectionLayout = () => useContext(FormSectionContext);\n"],"names":[],"mappings":";;AAaY,MAAC,qBAAqB;AAAA;AAAA,EAAsD;AAAK;AAGjF,MAAC,yBAAyB,MAAM,QAAQ,WAAW,kBAAkB,CAAC;AAGtE,MAAC,uBAAuB,MAAM,WAAW,kBAAkB;"}
|
|
@@ -26,6 +26,9 @@ function useWizardSteps({ steps, initialStepKey, stepSelection = WIZARD_STEP_SEL
|
|
|
26
26
|
if (action === "checkFirst" && !await isStepValid(activeIndex)) {
|
|
27
27
|
return;
|
|
28
28
|
}
|
|
29
|
+
if (action === "checkFirst" && index > activeIndex && steps[activeIndex].nextDisabledReason) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
29
32
|
setActiveIndex(index);
|
|
30
33
|
};
|
|
31
34
|
return { activeIndex, setActiveIndex, selectStep: stepSelection === WIZARD_STEP_SELECTION.none ? void 0 : selectStep };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useWizardSteps.js","sources":["../../src/inputs/useWizardSteps.js"],"sourcesContent":["// Lo que los dos FormWizard (formik y react-hook-form) hacen igual con el paso abierto: en qué paso empieza, avisar hacia\n// afuera cada vez que cambia y qué pasa al pulsar el título de otro paso. Lo de cada librería (cómo se revisa un paso) llega\n// en isStepValid.\n//\n// Hook genérico: function (regla de la casa).\nimport {useEffect, useRef, useState} from 'react';\n\nimport {WIZARD_STEP_SELECTION, openingStepOf, stepSelectionActionOf} from '@/inputs/wizardSteps.js';\nimport {createLogger} from '@/observability/logger.js';\n\nconst logger = createLogger('FormWizard');\n\n/**\n * @param {object} options\n * @param {Array<{key: string}>} options.steps\n * @param {string} [options.initialStepKey] el paso en el que se abre (el que diga el servidor); por omisión el primero\n * @param {string} [options.stepSelection] uno de WIZARD_STEP_SELECTION; por omisión none\n * @param {(stepKey: string, index: number) => void} [options.onStepChange] cada vez que cambia el paso abierto\n * @param {(index: number) => Promise<boolean>} options.isStepValid revisa los campos de ese paso y marca sus errores\n * @returns {{activeIndex: number, setActiveIndex: import('react').Dispatch<import('react').SetStateAction<number>>, selectStep: ((index: number) => Promise<void>)|undefined}}\n * selectStep es undefined con none: así WizardLayout sabe que los títulos no se pulsan\n */\nexport function useWizardSteps({steps, initialStepKey, stepSelection = WIZARD_STEP_SELECTION.none, onStepChange, isStepValid}) {\n const [opening] = useState(() => openingStepOf(steps, initialStepKey));\n const [activeIndex, setActiveIndex] = useState(opening.index);\n const announcedIndex = useRef(opening.index);\n\n // una llave que no es de ningún paso no tumba la pantalla: se abre el primero y se dice\n useEffect(() => {\n if (opening.isUnknownKey) {\n logger.warn(`initialStepKey «${initialStepKey}» no es de ningún paso: se abrió el primero`, {stepKeys: steps.map((step) => step.key)});\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n // hacia afuera, solo cuando de veras cambia (no al abrir): la pantalla puede guardar el paso en la dirección\n useEffect(() => {\n if (announcedIndex.current !== activeIndex) {\n announcedIndex.current = activeIndex;\n onStepChange?.(steps[activeIndex].key, activeIndex);\n }\n }, [activeIndex, onStepChange, steps]);\n\n const selectStep = async (index) => {\n const action = stepSelectionActionOf(stepSelection, activeIndex, index);\n if (action === 'ignore') {\n return;\n }\n // con checked, un paso con errores no se deja: se queda ahí para que se corrijan\n if (action === 'checkFirst' && !(await isStepValid(activeIndex))) {\n return;\n }\n setActiveIndex(index);\n };\n\n return {activeIndex, setActiveIndex, selectStep: stepSelection === WIZARD_STEP_SELECTION.none ? undefined : selectStep};\n}\n"],"names":[],"mappings":";;;;AAUA,MAAM,SAAS,aAAa,YAAY;AAYjC,SAAS,eAAe,EAAC,OAAO,gBAAgB,gBAAgB,sBAAsB,MAAM,cAAc,YAAW,GAAG;AAC3H,QAAM,CAAC,OAAO,IAAI,SAAS,MAAM,cAAc,OAAO,cAAc,CAAC;AACrE,QAAM,CAAC,aAAa,cAAc,IAAI,SAAS,QAAQ,KAAK;AAC5D,QAAM,iBAAiB,OAAO,QAAQ,KAAK;AAG3C,YAAU,MAAM;AACZ,QAAI,QAAQ,cAAc;AACtB,aAAO,KAAK,mBAAmB,cAAc,+CAA+C,EAAC,UAAU,MAAM,IAAI,CAAC,SAAS,KAAK,GAAG,EAAC,CAAC;AAAA,IACzI;AAAA,EAEJ,GAAG,CAAA,CAAE;AAGL,YAAU,MAAM;AACZ,QAAI,eAAe,YAAY,aAAa;AACxC,qBAAe,UAAU;AACzB,qBAAe,MAAM,WAAW,EAAE,KAAK,WAAW;AAAA,IACtD;AAAA,EACJ,GAAG,CAAC,aAAa,cAAc,KAAK,CAAC;AAErC,QAAM,aAAa,OAAO,UAAU;AAChC,UAAM,SAAS,sBAAsB,eAAe,aAAa,KAAK;AACtE,QAAI,WAAW,UAAU;AACrB;AAAA,IACJ;AAEA,QAAI,WAAW,gBAAgB,CAAE,MAAM,YAAY,WAAW,GAAI;AAC9D;AAAA,IACJ;AACA,mBAAe,KAAK;AAAA,EACxB;AAEA,SAAO,EAAC,aAAa,gBAAgB,YAAY,kBAAkB,sBAAsB,OAAO,SAAY,WAAU;AAC1H;"}
|
|
1
|
+
{"version":3,"file":"useWizardSteps.js","sources":["../../src/inputs/useWizardSteps.js"],"sourcesContent":["// Lo que los dos FormWizard (formik y react-hook-form) hacen igual con el paso abierto: en qué paso empieza, avisar hacia\n// afuera cada vez que cambia y qué pasa al pulsar el título de otro paso. Lo de cada librería (cómo se revisa un paso) llega\n// en isStepValid.\n//\n// Hook genérico: function (regla de la casa).\nimport {useEffect, useRef, useState} from 'react';\n\nimport {WIZARD_STEP_SELECTION, openingStepOf, stepSelectionActionOf} from '@/inputs/wizardSteps.js';\nimport {createLogger} from '@/observability/logger.js';\n\nconst logger = createLogger('FormWizard');\n\n/**\n * @param {object} options\n * @param {Array<{key: string, nextDisabledReason?: unknown}>} options.steps\n * @param {string} [options.initialStepKey] el paso en el que se abre (el que diga el servidor); por omisión el primero\n * @param {string} [options.stepSelection] uno de WIZARD_STEP_SELECTION; por omisión none\n * @param {(stepKey: string, index: number) => void} [options.onStepChange] cada vez que cambia el paso abierto\n * @param {(index: number) => Promise<boolean>} options.isStepValid revisa los campos de ese paso y marca sus errores\n * @returns {{activeIndex: number, setActiveIndex: import('react').Dispatch<import('react').SetStateAction<number>>, selectStep: ((index: number) => Promise<void>)|undefined}}\n * selectStep es undefined con none: así WizardLayout sabe que los títulos no se pulsan\n */\nexport function useWizardSteps({steps, initialStepKey, stepSelection = WIZARD_STEP_SELECTION.none, onStepChange, isStepValid}) {\n const [opening] = useState(() => openingStepOf(steps, initialStepKey));\n const [activeIndex, setActiveIndex] = useState(opening.index);\n const announcedIndex = useRef(opening.index);\n\n // una llave que no es de ningún paso no tumba la pantalla: se abre el primero y se dice\n useEffect(() => {\n if (opening.isUnknownKey) {\n logger.warn(`initialStepKey «${initialStepKey}» no es de ningún paso: se abrió el primero`, {stepKeys: steps.map((step) => step.key)});\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n // hacia afuera, solo cuando de veras cambia (no al abrir): la pantalla puede guardar el paso en la dirección\n useEffect(() => {\n if (announcedIndex.current !== activeIndex) {\n announcedIndex.current = activeIndex;\n onStepChange?.(steps[activeIndex].key, activeIndex);\n }\n }, [activeIndex, onStepChange, steps]);\n\n const selectStep = async (index) => {\n const action = stepSelectionActionOf(stepSelection, activeIndex, index);\n if (action === 'ignore') {\n return;\n }\n // con checked, un paso con errores no se deja: se queda ahí para que se corrijan\n if (action === 'checkFirst' && !(await isStepValid(activeIndex))) {\n return;\n }\n // con checked, un paso que todavía no se puede dejar (su nextDisabledReason) tampoco deja saltar hacia adelante; hacia\n // atrás sí: no le falta nada a lo de antes\n if (action === 'checkFirst' && index > activeIndex && steps[activeIndex].nextDisabledReason) {\n return;\n }\n setActiveIndex(index);\n };\n\n return {activeIndex, setActiveIndex, selectStep: stepSelection === WIZARD_STEP_SELECTION.none ? undefined : selectStep};\n}\n"],"names":[],"mappings":";;;;AAUA,MAAM,SAAS,aAAa,YAAY;AAYjC,SAAS,eAAe,EAAC,OAAO,gBAAgB,gBAAgB,sBAAsB,MAAM,cAAc,YAAW,GAAG;AAC3H,QAAM,CAAC,OAAO,IAAI,SAAS,MAAM,cAAc,OAAO,cAAc,CAAC;AACrE,QAAM,CAAC,aAAa,cAAc,IAAI,SAAS,QAAQ,KAAK;AAC5D,QAAM,iBAAiB,OAAO,QAAQ,KAAK;AAG3C,YAAU,MAAM;AACZ,QAAI,QAAQ,cAAc;AACtB,aAAO,KAAK,mBAAmB,cAAc,+CAA+C,EAAC,UAAU,MAAM,IAAI,CAAC,SAAS,KAAK,GAAG,EAAC,CAAC;AAAA,IACzI;AAAA,EAEJ,GAAG,CAAA,CAAE;AAGL,YAAU,MAAM;AACZ,QAAI,eAAe,YAAY,aAAa;AACxC,qBAAe,UAAU;AACzB,qBAAe,MAAM,WAAW,EAAE,KAAK,WAAW;AAAA,IACtD;AAAA,EACJ,GAAG,CAAC,aAAa,cAAc,KAAK,CAAC;AAErC,QAAM,aAAa,OAAO,UAAU;AAChC,UAAM,SAAS,sBAAsB,eAAe,aAAa,KAAK;AACtE,QAAI,WAAW,UAAU;AACrB;AAAA,IACJ;AAEA,QAAI,WAAW,gBAAgB,CAAE,MAAM,YAAY,WAAW,GAAI;AAC9D;AAAA,IACJ;AAGA,QAAI,WAAW,gBAAgB,QAAQ,eAAe,MAAM,WAAW,EAAE,oBAAoB;AACzF;AAAA,IACJ;AACA,mBAAe,KAAK;AAAA,EACxB;AAEA,SAAO,EAAC,aAAa,gBAAgB,YAAY,kBAAkB,sBAAsB,OAAO,SAAY,WAAU;AAC1H;"}
|
package/dist/locales/en.js
CHANGED
|
@@ -375,6 +375,7 @@ const en = Object.freeze({
|
|
|
375
375
|
"processingComplete": "Uploaded",
|
|
376
376
|
"processingAborted": "Upload canceled",
|
|
377
377
|
"processingError": "Could not upload",
|
|
378
|
+
"noTransferId": "The server did not return an upload id",
|
|
378
379
|
"revertError": "Could not undo",
|
|
379
380
|
"removeError": "Could not remove",
|
|
380
381
|
"tapToCancel": "tap to cancel",
|