@stackcraftmx/calcita 0.52.0 → 0.53.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 +53 -4
- package/dist/datePickers/DateInput.js +5 -3
- package/dist/datePickers/DateInput.js.map +1 -1
- package/dist/datePickers/DateTimeInput.js +6 -4
- package/dist/datePickers/DateTimeInput.js.map +1 -1
- package/dist/datePickers/FormDateField.js +1 -1
- package/dist/datePickers/FormDateField.js.map +1 -1
- package/dist/datePickers/FormDateTimeField.js +1 -1
- package/dist/datePickers/FormDateTimeField.js.map +1 -1
- package/dist/datePickers/FormMonthField.js +1 -1
- package/dist/datePickers/FormMonthField.js.map +1 -1
- package/dist/datePickers/FormTimeField.js +1 -1
- package/dist/datePickers/FormTimeField.js.map +1 -1
- package/dist/datePickers/MonthInput.js +5 -3
- package/dist/datePickers/MonthInput.js.map +1 -1
- package/dist/datePickers/PickerFrame.js +2 -2
- package/dist/datePickers/PickerFrame.js.map +1 -1
- package/dist/datePickers/TimeInput.js +6 -4
- package/dist/datePickers/TimeInput.js.map +1 -1
- package/dist/feedback/Toaster.js +11 -2
- package/dist/feedback/Toaster.js.map +1 -1
- package/dist/forms/CalcitaFormik.js +11 -0
- package/dist/forms/CalcitaFormik.js.map +1 -0
- package/dist/forms/FormAttachmentsField.js +1 -1
- package/dist/forms/FormAttachmentsField.js.map +1 -1
- package/dist/forms/FormCatalogField.js +1 -1
- package/dist/forms/FormCatalogField.js.map +1 -1
- package/dist/forms/FormCheckboxGroupField.js +1 -1
- package/dist/forms/FormCheckboxGroupField.js.map +1 -1
- package/dist/forms/FormColorField.js +1 -1
- package/dist/forms/FormColorField.js.map +1 -1
- package/dist/forms/FormFileField.js +1 -1
- package/dist/forms/FormFileField.js.map +1 -1
- package/dist/forms/FormFileUploadField.js +1 -1
- package/dist/forms/FormFileUploadField.js.map +1 -1
- package/dist/forms/FormImageField.js +1 -1
- package/dist/forms/FormImageField.js.map +1 -1
- package/dist/forms/FormMaskedField.js +1 -1
- package/dist/forms/FormMaskedField.js.map +1 -1
- package/dist/forms/FormNumberField.js +1 -1
- package/dist/forms/FormNumberField.js.map +1 -1
- package/dist/forms/FormSchemaProvider.js +17 -0
- package/dist/forms/FormSchemaProvider.js.map +1 -0
- package/dist/forms/FormSelectField.js +1 -1
- package/dist/forms/FormSelectField.js.map +1 -1
- package/dist/forms/FormTextField.js +1 -1
- package/dist/forms/FormTextField.js.map +1 -1
- package/dist/forms/FormTransferListField.js +1 -1
- package/dist/forms/FormTransferListField.js.map +1 -1
- package/dist/forms/FormWizard.js +2 -0
- package/dist/forms/FormWizard.js.map +1 -1
- package/dist/forms/schemaRequired.js +33 -0
- package/dist/forms/schemaRequired.js.map +1 -0
- package/dist/forms/useFormikField.js +18 -5
- package/dist/forms/useFormikField.js.map +1 -1
- package/dist/forms.js +52 -46
- package/dist/forms.js.map +1 -1
- package/dist/hookForm/FormAttachmentsField.js +1 -1
- package/dist/hookForm/FormAttachmentsField.js.map +1 -1
- package/dist/hookForm/FormCatalogField.js +1 -1
- package/dist/hookForm/FormCatalogField.js.map +1 -1
- package/dist/hookForm/FormCheckboxGroupField.js +1 -1
- package/dist/hookForm/FormCheckboxGroupField.js.map +1 -1
- package/dist/hookForm/FormColorField.js +1 -1
- package/dist/hookForm/FormColorField.js.map +1 -1
- package/dist/hookForm/FormFileField.js +1 -1
- package/dist/hookForm/FormFileField.js.map +1 -1
- package/dist/hookForm/FormFileUploadField.js +1 -1
- package/dist/hookForm/FormFileUploadField.js.map +1 -1
- package/dist/hookForm/FormImageField.js +1 -1
- package/dist/hookForm/FormImageField.js.map +1 -1
- package/dist/hookForm/FormMaskedField.js +1 -1
- package/dist/hookForm/FormMaskedField.js.map +1 -1
- package/dist/hookForm/FormNumberField.js +1 -1
- package/dist/hookForm/FormNumberField.js.map +1 -1
- package/dist/hookForm/FormSelectField.js +1 -1
- package/dist/hookForm/FormSelectField.js.map +1 -1
- package/dist/hookForm/FormTextField.js +1 -1
- package/dist/hookForm/FormTextField.js.map +1 -1
- package/dist/hookForm/FormTransferListField.js +1 -1
- package/dist/hookForm/FormTransferListField.js.map +1 -1
- package/dist/hookForm/FormWizard.js +2 -0
- package/dist/hookForm/FormWizard.js.map +1 -1
- package/dist/hookForm/useHookFormField.js +9 -3
- package/dist/hookForm/useHookFormField.js.map +1 -1
- package/dist/index.js +4 -2
- package/dist/inputs/CatalogInput.js +3 -2
- package/dist/inputs/CatalogInput.js.map +1 -1
- package/dist/inputs/CheckboxGroupInput.js +2 -1
- package/dist/inputs/CheckboxGroupInput.js.map +1 -1
- package/dist/inputs/ColorInput.js +3 -1
- package/dist/inputs/ColorInput.js.map +1 -1
- package/dist/inputs/FieldFrame.js +18 -3
- package/dist/inputs/FieldFrame.js.map +1 -1
- package/dist/inputs/FormSection.js +42 -11
- package/dist/inputs/FormSection.js.map +1 -1
- package/dist/inputs/NumberInput.js +3 -1
- package/dist/inputs/NumberInput.js.map +1 -1
- package/dist/inputs/SelectInput.js +3 -1
- package/dist/inputs/SelectInput.js.map +1 -1
- package/dist/inputs/TextInput.js +1 -1
- package/dist/inputs/TextInput.js.map +1 -1
- package/dist/inputs/TransferListInput.js +2 -1
- package/dist/inputs/TransferListInput.js.map +1 -1
- package/dist/inputs/WizardLayout.js +12 -6
- package/dist/inputs/WizardLayout.js.map +1 -1
- package/dist/inputs/formLayoutContext.js +7 -2
- package/dist/inputs/formLayoutContext.js.map +1 -1
- package/dist/inputs/requiredMark.js +17 -0
- package/dist/inputs/requiredMark.js.map +1 -0
- 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/richText/FormRichTextField.js +1 -1
- package/dist/richText/FormRichTextField.js.map +1 -1
- package/dist/richText/RichTextEditor.js +4 -3
- package/dist/richText/RichTextEditor.js.map +1 -1
- package/dist/toolkit/ToolkitProvider.js +3 -0
- package/dist/toolkit/ToolkitProvider.js.map +1 -1
- package/dist/toolkit/toolkitContext.js.map +1 -1
- package/dist/types/datePickers/DateInput.d.ts +2 -0
- package/dist/types/datePickers/DateTimeInput.d.ts +2 -0
- package/dist/types/datePickers/FormDateField.d.ts +3 -3
- package/dist/types/datePickers/FormDateTimeField.d.ts +3 -3
- package/dist/types/datePickers/FormMonthField.d.ts +3 -3
- package/dist/types/datePickers/FormTimeField.d.ts +3 -3
- package/dist/types/datePickers/MonthInput.d.ts +2 -0
- package/dist/types/datePickers/PickerFrame.d.ts +3 -1
- package/dist/types/datePickers/TimeInput.d.ts +2 -0
- package/dist/types/feedback/Toaster.d.ts +15 -1
- package/dist/types/feedback/index.d.ts +1 -1
- package/dist/types/forms/CalcitaFormik.d.ts +6 -0
- package/dist/types/forms/FormAttachmentsField.d.ts +3 -3
- package/dist/types/forms/FormCatalogField.d.ts +2 -2
- package/dist/types/forms/FormCheckboxGroupField.d.ts +3 -3
- package/dist/types/forms/FormColorField.d.ts +2 -2
- package/dist/types/forms/FormFileField.d.ts +3 -3
- package/dist/types/forms/FormFileUploadField.d.ts +3 -3
- package/dist/types/forms/FormImageField.d.ts +3 -3
- package/dist/types/forms/FormMaskedField.d.ts +2 -2
- package/dist/types/forms/FormNumberField.d.ts +2 -2
- package/dist/types/forms/FormSchemaProvider.d.ts +13 -0
- package/dist/types/forms/FormSelectField.d.ts +2 -2
- package/dist/types/forms/FormTextField.d.ts +2 -2
- package/dist/types/forms/FormTransferListField.d.ts +3 -3
- package/dist/types/forms/FormWizard.d.ts +3 -1
- package/dist/types/forms/index.d.ts +1 -0
- package/dist/types/forms/schemaRequired.d.ts +14 -0
- package/dist/types/forms/useFormikField.d.ts +10 -4
- package/dist/types/forms.d.ts +4 -2
- package/dist/types/hookForm/FormAttachmentsField.d.ts +3 -3
- package/dist/types/hookForm/FormCatalogField.d.ts +3 -3
- package/dist/types/hookForm/FormCheckboxGroupField.d.ts +3 -3
- package/dist/types/hookForm/FormColorField.d.ts +3 -3
- package/dist/types/hookForm/FormFileField.d.ts +3 -3
- package/dist/types/hookForm/FormFileUploadField.d.ts +3 -3
- package/dist/types/hookForm/FormImageField.d.ts +3 -3
- package/dist/types/hookForm/FormMaskedField.d.ts +3 -3
- package/dist/types/hookForm/FormNumberField.d.ts +3 -3
- package/dist/types/hookForm/FormSelectField.d.ts +3 -3
- package/dist/types/hookForm/FormTextField.d.ts +3 -3
- package/dist/types/hookForm/FormTransferListField.d.ts +3 -3
- package/dist/types/hookForm/FormWizard.d.ts +3 -1
- package/dist/types/hookForm/useHookFormField.d.ts +8 -4
- package/dist/types/index.d.ts +2 -2
- package/dist/types/inputs/CatalogInput.d.ts +1 -0
- package/dist/types/inputs/CheckboxGroupInput.d.ts +1 -0
- package/dist/types/inputs/ColorInput.d.ts +1 -0
- package/dist/types/inputs/FieldFrame.d.ts +1 -0
- package/dist/types/inputs/FormSection.d.ts +13 -0
- package/dist/types/inputs/NumberInput.d.ts +1 -0
- package/dist/types/inputs/SelectInput.d.ts +1 -0
- package/dist/types/inputs/TextInput.d.ts +1 -1
- package/dist/types/inputs/TransferListInput.d.ts +1 -0
- package/dist/types/inputs/WizardLayout.d.ts +1 -0
- package/dist/types/inputs/formLayoutContext.d.ts +8 -1
- package/dist/types/inputs/requiredMark.d.ts +9 -0
- package/dist/types/locales/en.d.ts +1 -0
- package/dist/types/locales/es.d.ts +1 -0
- package/dist/types/richText/FormRichTextField.d.ts +3 -3
- package/dist/types/richText/RichTextEditor.d.ts +1 -0
- package/dist/types/toolkit/ToolkitProvider.d.ts +5 -1
- package/dist/types/toolkit/toolkitContext.d.ts +2 -1
- package/dist/types/upload/AttachmentsInput.d.ts +2 -0
- package/dist/types/upload/FileInput.d.ts +1 -0
- package/dist/types/upload/FileUploadInput.d.ts +1 -0
- package/dist/types/upload/ImageInput.d.ts +1 -0
- package/dist/upload/AttachmentsInput.js +2 -1
- package/dist/upload/AttachmentsInput.js.map +1 -1
- package/dist/upload/FileInput.js +2 -1
- package/dist/upload/FileInput.js.map +1 -1
- package/dist/upload/FileUploadInput.js +2 -1
- package/dist/upload/FileUploadInput.js.map +1 -1
- package/dist/upload/ImageInput.js +2 -1
- package/dist/upload/ImageInput.js.map +1 -1
- package/package.json +1 -1
|
@@ -4,20 +4,35 @@ import Stack from "@mui/material/Stack";
|
|
|
4
4
|
import Typography from "@mui/material/Typography";
|
|
5
5
|
import { FormRow } from "./FormSection.js";
|
|
6
6
|
import { useIsInsideFormSection } from "./formLayoutContext.js";
|
|
7
|
+
import { requiredMarkSxOf, REQUIRED_LABEL_ATTRIBUTE } from "./requiredMark.js";
|
|
7
8
|
import { piecePart, withPieceSlots, usePieceProps } from "../theme/themePieces.js";
|
|
8
9
|
import { warnUnknownProps } from "../toolkit/unknownProps.js";
|
|
10
|
+
import { useToolkitTranslation } from "../toolkit/useToolkitTranslation.js";
|
|
9
11
|
const THEME_NAME = "CalcitaFieldFrame";
|
|
10
12
|
const FieldFrameRoot = piecePart(THEME_NAME, "Root", Stack);
|
|
11
13
|
const FieldFrameLabel = piecePart(THEME_NAME, "Label", Typography);
|
|
12
14
|
function FieldFrameContent(props) {
|
|
13
|
-
const { inputId, labelId, label, children, ...unknownProps } = usePieceProps(props, THEME_NAME);
|
|
15
|
+
const { inputId, labelId, label, required = false, children, ...unknownProps } = usePieceProps(props, THEME_NAME);
|
|
14
16
|
warnUnknownProps(THEME_NAME, unknownProps);
|
|
15
17
|
const isRow = useIsInsideFormSection();
|
|
18
|
+
const { t } = useToolkitTranslation("common");
|
|
16
19
|
if (isRow) {
|
|
17
|
-
return /* @__PURE__ */ jsx(FormRow, { label, htmlFor: inputId, labelId, children });
|
|
20
|
+
return /* @__PURE__ */ jsx(FormRow, { label, htmlFor: inputId, labelId, required, children });
|
|
18
21
|
}
|
|
19
22
|
return /* @__PURE__ */ jsxs(FieldFrameRoot, { spacing: 0.75, children: [
|
|
20
|
-
/* @__PURE__ */ jsx(
|
|
23
|
+
/* @__PURE__ */ jsx(
|
|
24
|
+
FieldFrameLabel,
|
|
25
|
+
{
|
|
26
|
+
component: "label",
|
|
27
|
+
id: labelId,
|
|
28
|
+
htmlFor: inputId,
|
|
29
|
+
variant: "overline",
|
|
30
|
+
color: "text.secondary",
|
|
31
|
+
...required && { [REQUIRED_LABEL_ATTRIBUTE]: "true" },
|
|
32
|
+
ownSx: required ? requiredMarkSxOf(t("form.requiredMark")) : void 0,
|
|
33
|
+
children: label
|
|
34
|
+
}
|
|
35
|
+
),
|
|
21
36
|
children
|
|
22
37
|
] });
|
|
23
38
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FieldFrame.js","sources":["../../src/inputs/FieldFrame.jsx"],"sourcesContent":["// El marco de todos los campos del paquete. Fuera de una FormSection, la etiqueta va ARRIBA del campo, ligada a él por su\n// id; dentro de una FormSection (el formulario agrupado de Apple), el campo se vuelve un renglón: la etiqueta a la\n// izquierda y el valor a la derecha (FormRow).\n//\n// La etiqueta va afuera y no flotando adentro del campo: se lee igual con el campo vacío o lleno, y no brinca al escribir.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaFieldFrame con styleOverrides de sus\n// partes root (la columna) y label (la etiqueta de arriba), y slots/slotProps para cambiarlas. Dentro de una FormSection el\n// renglón es CalcitaFormRow.\n//\n// Genérico del paquete: function (regla de la casa).\nimport Stack from '@mui/material/Stack';\nimport Typography from '@mui/material/Typography';\n\nimport {FormRow} from '@/inputs/FormSection.jsx';\nimport {useIsInsideFormSection} from '@/inputs/formLayoutContext.js';\nimport {piecePart, usePieceProps, withPieceSlots} from '@/theme/themePieces.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaFieldFrame)\nconst THEME_NAME = 'CalcitaFieldFrame';\nconst FieldFrameRoot = piecePart(THEME_NAME, 'Root', Stack);\nconst FieldFrameLabel = piecePart(THEME_NAME, 'Label', Typography);\n\n/**\n * @param {object} props\n * @param {string} props.inputId el id del campo (la etiqueta lo nombra para el lector de pantalla)\n * @param {string} [props.labelId] el id de la etiqueta, para un campo que no es un <input> (la lista desplegable de MUI es un\n * div: `for` no la une con su etiqueta y el campo la nombra con aria-labelledby)\n * @param {import('react').ReactNode} props.label ya traducida\n * @param {import('react').ReactNode} props.children el campo\n */\nfunction FieldFrameContent(props) {\n const {inputId, labelId, label, children, ...unknownProps} = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const isRow = useIsInsideFormSection();\n\n if (isRow) {\n return <FormRow label={label} htmlFor={inputId} labelId={labelId}>{children}</FormRow>;\n }\n\n return (\n <FieldFrameRoot spacing={0.75}>\n <FieldFrameLabel component=\"label\" id={labelId} htmlFor={inputId} variant=\"overline\" color=\"text.secondary\"
|
|
1
|
+
{"version":3,"file":"FieldFrame.js","sources":["../../src/inputs/FieldFrame.jsx"],"sourcesContent":["// El marco de todos los campos del paquete. Fuera de una FormSection, la etiqueta va ARRIBA del campo, ligada a él por su\n// id; dentro de una FormSection (el formulario agrupado de Apple), el campo se vuelve un renglón: la etiqueta a la\n// izquierda y el valor a la derecha (FormRow).\n//\n// La etiqueta va afuera y no flotando adentro del campo: se lee igual con el campo vacío o lleno, y no brinca al escribir.\n//\n// Un campo obligatorio (required) lleva un asterisco en la etiqueta, como en MUI (requiredMark.js: va en el estilo y no como\n// texto, para que la etiqueta siga diciendo lo mismo). Antes no se veía por ningún lado: la etiqueta la pinta este marco y no\n// la de MUI, que es la que ponía el asterisco.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaFieldFrame con styleOverrides de sus\n// partes root (la columna) y label (la etiqueta de arriba; el asterisco es su '&[data-required]::after'), y slots/slotProps\n// para cambiarlas. Dentro de una FormSection el\n// renglón es CalcitaFormRow.\n//\n// Genérico del paquete: function (regla de la casa).\nimport Stack from '@mui/material/Stack';\nimport Typography from '@mui/material/Typography';\n\nimport {FormRow} from '@/inputs/FormSection.jsx';\nimport {useIsInsideFormSection} from '@/inputs/formLayoutContext.js';\nimport {REQUIRED_LABEL_ATTRIBUTE, requiredMarkSxOf} from '@/inputs/requiredMark.js';\nimport {piecePart, usePieceProps, withPieceSlots} from '@/theme/themePieces.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaFieldFrame)\nconst THEME_NAME = 'CalcitaFieldFrame';\nconst FieldFrameRoot = piecePart(THEME_NAME, 'Root', Stack);\nconst FieldFrameLabel = piecePart(THEME_NAME, 'Label', Typography);\n\n/**\n * @param {object} props\n * @param {string} props.inputId el id del campo (la etiqueta lo nombra para el lector de pantalla)\n * @param {string} [props.labelId] el id de la etiqueta, para un campo que no es un <input> (la lista desplegable de MUI es un\n * div: `for` no la une con su etiqueta y el campo la nombra con aria-labelledby)\n * @param {import('react').ReactNode} props.label ya traducida\n * @param {boolean} [props.required] el asterisco en la etiqueta\n * @param {import('react').ReactNode} props.children el campo\n */\nfunction FieldFrameContent(props) {\n const {inputId, labelId, label, required = false, children, ...unknownProps} = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const isRow = useIsInsideFormSection();\n const {t} = useToolkitTranslation('common');\n\n if (isRow) {\n return <FormRow label={label} htmlFor={inputId} labelId={labelId} required={required}>{children}</FormRow>;\n }\n\n return (\n <FieldFrameRoot spacing={0.75}>\n <FieldFrameLabel component=\"label\" id={labelId} htmlFor={inputId} variant=\"overline\" color=\"text.secondary\"\n {...(required && {[REQUIRED_LABEL_ATTRIBUTE]: 'true'})} ownSx={required ? requiredMarkSxOf(t('form.requiredMark')) : undefined}>\n {label}\n </FieldFrameLabel>\n {children}\n </FieldFrameRoot>\n );\n}\n\n// lo que se exporta: la pieza con slots y slotProps para cambiar sus partes (themePieces.js)\nconst FieldFrame = withPieceSlots(FieldFrameContent, THEME_NAME);\nexport default FieldFrame;\n"],"names":[],"mappings":";;;;;;;;;;AA2BA,MAAM,aAAa;AACnB,MAAM,iBAAiB,UAAU,YAAY,QAAQ,KAAK;AAC1D,MAAM,kBAAkB,UAAU,YAAY,SAAS,UAAU;AAWjE,SAAS,kBAAkB,OAAO;AAC9B,QAAM,EAAC,SAAS,SAAS,OAAO,WAAW,OAAO,UAAU,GAAG,aAAA,IAAgB,cAAc,OAAO,UAAU;AAC9G,mBAAiB,YAAY,YAAY;AACzC,QAAM,QAAQ,uBAAA;AACd,QAAM,EAAC,EAAA,IAAK,sBAAsB,QAAQ;AAE1C,MAAI,OAAO;AACP,+BAAQ,SAAA,EAAQ,OAAc,SAAS,SAAS,SAAkB,UAAqB,UAAS;AAAA,EACpG;AAEA,SACI,qBAAC,gBAAA,EAAe,SAAS,MACrB,UAAA;AAAA,IAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QAAgB,WAAU;AAAA,QAAQ,IAAI;AAAA,QAAS,SAAS;AAAA,QAAS,SAAQ;AAAA,QAAW,OAAM;AAAA,QACzE,GAAI,YAAY,EAAC,CAAC,wBAAwB,GAAG,OAAA;AAAA,QAAU,OAAO,WAAW,iBAAiB,EAAE,mBAAmB,CAAC,IAAI;AAAA,QACjI,UAAA;AAAA,MAAA;AAAA,IAAA;AAAA,IAEJ;AAAA,EAAA,GACL;AAER;AAGA,MAAM,aAAa,eAAe,mBAAmB,UAAU;"}
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsxs, jsx } from "react/jsx-runtime";
|
|
3
|
-
import { useId } from "react";
|
|
3
|
+
import { useId, useMemo } from "react";
|
|
4
4
|
import Box from "@mui/material/Box";
|
|
5
5
|
import Paper from "@mui/material/Paper";
|
|
6
6
|
import Typography from "@mui/material/Typography";
|
|
7
7
|
import { styled, useThemeProps } from "@mui/material/styles";
|
|
8
|
-
import { FormSectionContext } from "./formLayoutContext.js";
|
|
8
|
+
import { useFormSectionLayout, FormSectionContext } from "./formLayoutContext.js";
|
|
9
|
+
import { requiredMarkSxOf, REQUIRED_LABEL_ATTRIBUTE } from "./requiredMark.js";
|
|
9
10
|
import { fieldVariablesOf, accentTextStylesOf, PANEL_RADIUS, fieldWellFromVariablesOf } from "../theme/glassSurface.js";
|
|
10
11
|
import { warnUnknownProps } from "../toolkit/unknownProps.js";
|
|
12
|
+
import { useToolkitTranslation } from "../toolkit/useToolkitTranslation.js";
|
|
13
|
+
const FORM_LABEL_PLACEMENT = Object.freeze({ side: "side", topOnPhone: "topOnPhone" });
|
|
11
14
|
const FORM_ROW_CLASS = "calcita-form-row";
|
|
12
15
|
const ROW_MIN_HEIGHT_PIXELS = 52;
|
|
13
16
|
const ROW_PADDING_BLOCK_PIXELS = 8;
|
|
@@ -37,8 +40,10 @@ function rowControlSxOf(theme) {
|
|
|
37
40
|
}
|
|
38
41
|
const SECTION_THEME_NAME = "CalcitaFormSection";
|
|
39
42
|
const ROW_THEME_NAME = "CalcitaFormRow";
|
|
40
|
-
const
|
|
43
|
+
const isPartProp = (propName) => propName !== "ownerState";
|
|
44
|
+
const FormRowRoot = styled(Box, { name: ROW_THEME_NAME, slot: "Root", shouldForwardProp: isPartProp })(({ theme, ownerState }) => {
|
|
41
45
|
const palette = (theme.vars ?? theme).palette;
|
|
46
|
+
const isTopOnPhone = ownerState?.labelPlacement === FORM_LABEL_PLACEMENT.topOnPhone;
|
|
42
47
|
return theme.unstable_sx({
|
|
43
48
|
position: "relative",
|
|
44
49
|
display: "flex",
|
|
@@ -51,15 +56,22 @@ const FormRowRoot = styled(Box, { name: ROW_THEME_NAME, slot: "Root" })(({ theme
|
|
|
51
56
|
"&:has(.Mui-error) > label": { color: palette.error.main },
|
|
52
57
|
// los tonos de los campos (los del proyecto con createToolkitTheme fields, o los de Apple) para sus campos
|
|
53
58
|
...fieldVariablesOf(theme),
|
|
54
|
-
...rowControlSxOf(theme)
|
|
59
|
+
...rowControlSxOf(theme),
|
|
60
|
+
// en el teléfono, la etiqueta arriba y el campo a todo lo ancho debajo
|
|
61
|
+
...isTopOnPhone && { [theme.breakpoints.down("sm")]: { flexDirection: "column", alignItems: "stretch", gap: 0.5 } }
|
|
55
62
|
});
|
|
56
63
|
});
|
|
57
|
-
const FormRowLabel = styled(Typography, { name: ROW_THEME_NAME, slot: "Label" })(({ theme }) => theme.unstable_sx({
|
|
64
|
+
const FormRowLabel = styled(Typography, { name: ROW_THEME_NAME, slot: "Label", shouldForwardProp: isPartProp })(({ theme, ownerState }) => theme.unstable_sx({
|
|
58
65
|
flex: `0 0 ${LABEL_COLUMN.share}`,
|
|
59
66
|
maxWidth: LABEL_COLUMN.maxPixels,
|
|
60
67
|
pt: `${LABEL_TOP_PIXELS}px`,
|
|
61
68
|
lineHeight: 1.5,
|
|
62
|
-
color: "text.primary"
|
|
69
|
+
color: "text.primary",
|
|
70
|
+
...ownerState?.labelPlacement === FORM_LABEL_PLACEMENT.topOnPhone && {
|
|
71
|
+
[theme.breakpoints.down("sm")]: { flex: "none", maxWidth: "none", pt: 0 }
|
|
72
|
+
},
|
|
73
|
+
// el asterisco de lo obligatorio: aquí, debajo de lo que el proyecto ponga en su tema
|
|
74
|
+
...ownerState?.requiredMark && requiredMarkSxOf(ownerState.requiredMark)
|
|
63
75
|
}));
|
|
64
76
|
const FormRowControl = styled(Box, { name: ROW_THEME_NAME, slot: "Control" })({
|
|
65
77
|
flex: 1,
|
|
@@ -86,24 +98,43 @@ const FormSectionCard = styled(Paper, { name: SECTION_THEME_NAME, slot: "Card" }
|
|
|
86
98
|
}));
|
|
87
99
|
const FormSectionFooter = styled(Typography, { name: SECTION_THEME_NAME, slot: "Footer" })(({ theme }) => theme.unstable_sx({ px: 2, mt: 0.75 }));
|
|
88
100
|
function FormRow(props) {
|
|
89
|
-
const { label, htmlFor, labelId, children, ...unknownProps } = useThemeProps({ props, name: ROW_THEME_NAME });
|
|
101
|
+
const { label, htmlFor, labelId, required = false, children, ...unknownProps } = useThemeProps({ props, name: ROW_THEME_NAME });
|
|
90
102
|
warnUnknownProps(ROW_THEME_NAME, unknownProps);
|
|
91
|
-
|
|
92
|
-
|
|
103
|
+
const { t } = useToolkitTranslation("common");
|
|
104
|
+
const sectionLayout = useFormSectionLayout();
|
|
105
|
+
const ownerState = {
|
|
106
|
+
labelPlacement: sectionLayout ? sectionLayout.labelPlacement : FORM_LABEL_PLACEMENT.side,
|
|
107
|
+
requiredMark: required ? t("form.requiredMark") : null
|
|
108
|
+
};
|
|
109
|
+
return /* @__PURE__ */ jsxs(FormRowRoot, { className: FORM_ROW_CLASS, ownerState, children: [
|
|
110
|
+
/* @__PURE__ */ jsx(
|
|
111
|
+
FormRowLabel,
|
|
112
|
+
{
|
|
113
|
+
component: "label",
|
|
114
|
+
id: labelId,
|
|
115
|
+
htmlFor,
|
|
116
|
+
variant: "body1",
|
|
117
|
+
ownerState,
|
|
118
|
+
...required && { [REQUIRED_LABEL_ATTRIBUTE]: "true" },
|
|
119
|
+
children: label
|
|
120
|
+
}
|
|
121
|
+
),
|
|
93
122
|
/* @__PURE__ */ jsx(FormRowControl, { children })
|
|
94
123
|
] });
|
|
95
124
|
}
|
|
96
125
|
function FormSection(props) {
|
|
97
|
-
const { title, footer, children, ...unknownProps } = useThemeProps({ props, name: SECTION_THEME_NAME });
|
|
126
|
+
const { title, footer, labelPlacement = FORM_LABEL_PLACEMENT.side, children, ...unknownProps } = useThemeProps({ props, name: SECTION_THEME_NAME });
|
|
98
127
|
warnUnknownProps(SECTION_THEME_NAME, unknownProps);
|
|
99
128
|
const titleId = useId();
|
|
100
|
-
|
|
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: [
|
|
101
131
|
title && /* @__PURE__ */ jsx(FormSectionTitle, { id: titleId, variant: "subtitle2", component: "h2", color: "text.secondary", children: title }),
|
|
102
132
|
/* @__PURE__ */ jsx(FormSectionCard, { variant: "outlined", children: /* @__PURE__ */ jsx(Box, { sx: { mt: "-1px" }, children }) }),
|
|
103
133
|
footer && /* @__PURE__ */ jsx(FormSectionFooter, { variant: "caption", component: "p", color: "text.secondary", children: footer })
|
|
104
134
|
] }) });
|
|
105
135
|
}
|
|
106
136
|
export {
|
|
137
|
+
FORM_LABEL_PLACEMENT,
|
|
107
138
|
FORM_ROW_CLASS,
|
|
108
139
|
FormRow,
|
|
109
140
|
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// 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), con 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} 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} from '@/inputs/formLayoutContext.js';\nimport {PANEL_RADIUS, accentTextStylesOf, fieldVariablesOf, fieldWellFromVariablesOf} from '@/theme/glassSurface.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\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\nconst FormRowRoot = styled(Box, {name: ROW_THEME_NAME, slot: 'Root'})(({theme}) => {\n const palette = (theme.vars ?? theme).palette;\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 });\n});\n\nconst FormRowLabel = styled(Typography, {name: ROW_THEME_NAME, slot: 'Label'})(({theme}) => 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}));\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 {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, children, ...unknownProps} = useThemeProps({props, name: ROW_THEME_NAME});\n warnUnknownProps(ROW_THEME_NAME, unknownProps);\n return (\n <FormRowRoot className={FORM_ROW_CLASS}>\n <FormRowLabel component=\"label\" id={labelId} htmlFor={htmlFor} variant=\"body1\">\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 {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, children, ...unknownProps} = useThemeProps({props, name: SECTION_THEME_NAME});\n warnUnknownProps(SECTION_THEME_NAME, unknownProps);\n const titleId = useId();\n return (\n <FormSectionContext value>\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// 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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"}
|
|
@@ -26,6 +26,7 @@ function NumberInputContent(props) {
|
|
|
26
26
|
suffix,
|
|
27
27
|
maxDigits = 17,
|
|
28
28
|
disabled = false,
|
|
29
|
+
required = false,
|
|
29
30
|
inputRef,
|
|
30
31
|
messages,
|
|
31
32
|
texts: unusedTexts,
|
|
@@ -39,7 +40,7 @@ function NumberInputContent(props) {
|
|
|
39
40
|
const errorText = validationTextOf(error);
|
|
40
41
|
const valueText = value === null || value === void 0 ? "" : String(value);
|
|
41
42
|
const shownText = groupThousands(isTyping ? valueText : completeDecimals(valueText, decimals));
|
|
42
|
-
return /* @__PURE__ */ jsx(FieldFrame, { inputId, label, children: /* @__PURE__ */ jsx(
|
|
43
|
+
return /* @__PURE__ */ jsx(FieldFrame, { inputId, label, required, children: /* @__PURE__ */ jsx(
|
|
43
44
|
NumberInputField,
|
|
44
45
|
{
|
|
45
46
|
name,
|
|
@@ -47,6 +48,7 @@ function NumberInputContent(props) {
|
|
|
47
48
|
inputRef,
|
|
48
49
|
value: shownText,
|
|
49
50
|
disabled,
|
|
51
|
+
required,
|
|
50
52
|
size: "small",
|
|
51
53
|
fullWidth: true,
|
|
52
54
|
error: Boolean(errorText),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NumberInput.js","sources":["../../src/inputs/NumberInput.jsx"],"sourcesContent":["// Campo de NÚMERO suelto: se escribe y se lee con separador de miles («2,850,000.50») y al formulario (y al servidor)\n// llega el texto limpio («2850000.50»), que es lo que lee Money.fromString sin pasar por decimales.\n//\n// Por qué no type=\"number\": pinta flechitas que nadie usa, no agrupa miles, acepta «e» y en el teléfono deja escribir\n// cosas raras. Este usa el teclado numérico (inputMode decimal) y alinea a la derecha, como en una hoja de cálculo.\n//\n// Mientras se escribe se respeta lo tecleado (no completa decimales a medias); al salir se completan con ceros.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaNumberInput con defaultProps, styleOverrides de\n// sus partes (field) y slots/slotProps para cambiarlas.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useId, useState} from 'react';\nimport InputAdornment from '@mui/material/InputAdornment';\nimport TextField from '@mui/material/TextField';\n\nimport {useValidationText} from '@/forms/useValidationText.js';\nimport FieldFrame from '@/inputs/FieldFrame.jsx';\nimport {cleanNumberText, completeDecimals, groupThousands} from '@/inputs/numberText.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\nimport {piecePart, usePieceProps, withPieceSlots} from '@/theme/themePieces.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaNumberInput)\nconst THEME_NAME = 'CalcitaNumberInput';\nconst NumberInputField = piecePart(THEME_NAME, 'Field', TextField);\n\n// lo que caben los separadores y el punto además de los dígitos\nconst SEPARATORS_ROOM = 6;\n\n/**\n * @param {object} props\n * @param {string} [props.name]\n * @param {string} props.label ya traducido\n * @param {string|number|null|undefined} props.value el texto limpio ('2850000.50'); '' = vacío\n * @param {(value: string) => void} props.onChange recibe el texto limpio\n * @param {() => void} [props.onBlur]\n * @param {string|{key: string, values?: object}} [props.error]\n * @param {string} [props.helperText]\n * @param {number} [props.decimals] por omisión 2\n * @param {boolean} [props.allowNegative]\n * @param {import('react').ReactNode} [props.prefix] p. ej. «$»\n * @param {import('react').ReactNode} [props.suffix] p. ej. «MXN» o «%»\n * @param {number} [props.maxDigits] tope de dígitos (por omisión 17)\n * @param {boolean} [props.disabled]\n * @param {import('react').Ref<HTMLInputElement>} [props.inputRef]\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] se acepta como en los demás campos; este no tiene textos propios (su error usa `messages`)\n */\nfunction NumberInputContent(props) {\n const {\n name, label, value, onChange, onBlur, error, helperText = ' ', decimals = 2, allowNegative = false, prefix, suffix, maxDigits = 17,\n disabled = false, inputRef, messages, texts: unusedTexts,\n ...unknownProps\n } = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\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 generatedId = useId();\n const inputId = `${name ?? 'number'}-${generatedId}`;\n const [isTyping, setIsTyping] = useState(false);\n const errorText = validationTextOf(error);\n const valueText = value === null || value === undefined ? '' : String(value);\n // escribiendo: lo que hay; fuera del campo: con sus decimales completos\n const shownText = groupThousands(isTyping ? valueText : completeDecimals(valueText, decimals));\n\n return (\n <FieldFrame inputId={inputId} label={label}>\n <NumberInputField\n name={name}\n id={inputId}\n inputRef={inputRef}\n value={shownText}\n disabled={disabled}\n size=\"small\"\n fullWidth\n error={Boolean(errorText)}\n helperText={errorText ?? helperText}\n onFocus={(event) => {\n const input = event.target;\n // OJO: al entrar, el texto cambia («50,000.00» → «50,000») y el navegador pierde lo seleccionado; al entrar\n // con Tab estaba todo seleccionado y lo tecleado se pegaba al final (50,000,100,000). Si estaba todo\n // seleccionado, se vuelve a seleccionar ya pintado; con un clic (el cursor en un lugar) no se toca\n const wasAllSelected = input.value.length > 0 && input.selectionStart === 0 && input.selectionEnd === input.value.length;\n setIsTyping(true);\n if (wasAllSelected) {\n requestAnimationFrame(() => {\n if (document.activeElement === input) {\n input.select();\n }\n });\n }\n }}\n onChange={(event) => onChange(cleanNumberText(event.target.value, decimals, allowNegative))}\n onBlur={() => {\n setIsTyping(false);\n const completedText = completeDecimals(cleanNumberText(valueText, decimals, allowNegative), decimals);\n if (completedText !== valueText) {\n onChange(completedText);\n }\n onBlur?.();\n }}\n slotProps={{\n htmlInput: {inputMode: 'decimal', maxLength: maxDigits + SEPARATORS_ROOM, style: {textAlign: 'right'}},\n input: {\n startAdornment: prefix ? <InputAdornment position=\"start\">{prefix}</InputAdornment> : undefined,\n endAdornment: suffix ? <InputAdornment position=\"end\">{suffix}</InputAdornment> : undefined,\n },\n }}\n />\n </FieldFrame>\n );\n}\n\n// lo que se exporta: la pieza con slots y slotProps para cambiar sus partes (themePieces.js)\nconst NumberInput = withPieceSlots(NumberInputContent, THEME_NAME);\nexport default NumberInput;\n"],"names":[],"mappings":";;;;;;;;;;AAuBA,MAAM,aAAa;AACnB,MAAM,mBAAmB,UAAU,YAAY,SAAS,SAAS;AAGjE,MAAM,kBAAkB;
|
|
1
|
+
{"version":3,"file":"NumberInput.js","sources":["../../src/inputs/NumberInput.jsx"],"sourcesContent":["// Campo de NÚMERO suelto: se escribe y se lee con separador de miles («2,850,000.50») y al formulario (y al servidor)\n// llega el texto limpio («2850000.50»), que es lo que lee Money.fromString sin pasar por decimales.\n//\n// Por qué no type=\"number\": pinta flechitas que nadie usa, no agrupa miles, acepta «e» y en el teléfono deja escribir\n// cosas raras. Este usa el teclado numérico (inputMode decimal) y alinea a la derecha, como en una hoja de cálculo.\n//\n// Mientras se escribe se respeta lo tecleado (no completa decimales a medias); al salir se completan con ceros.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaNumberInput con defaultProps, styleOverrides de\n// sus partes (field) y slots/slotProps para cambiarlas.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useId, useState} from 'react';\nimport InputAdornment from '@mui/material/InputAdornment';\nimport TextField from '@mui/material/TextField';\n\nimport {useValidationText} from '@/forms/useValidationText.js';\nimport FieldFrame from '@/inputs/FieldFrame.jsx';\nimport {cleanNumberText, completeDecimals, groupThousands} from '@/inputs/numberText.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\nimport {piecePart, usePieceProps, withPieceSlots} from '@/theme/themePieces.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaNumberInput)\nconst THEME_NAME = 'CalcitaNumberInput';\nconst NumberInputField = piecePart(THEME_NAME, 'Field', TextField);\n\n// lo que caben los separadores y el punto además de los dígitos\nconst SEPARATORS_ROOM = 6;\n\n/**\n * @param {object} props\n * @param {string} [props.name]\n * @param {string} props.label ya traducido\n * @param {string|number|null|undefined} props.value el texto limpio ('2850000.50'); '' = vacío\n * @param {(value: string) => void} props.onChange recibe el texto limpio\n * @param {() => void} [props.onBlur]\n * @param {string|{key: string, values?: object}} [props.error]\n * @param {string} [props.helperText]\n * @param {number} [props.decimals] por omisión 2\n * @param {boolean} [props.allowNegative]\n * @param {import('react').ReactNode} [props.prefix] p. ej. «$»\n * @param {import('react').ReactNode} [props.suffix] p. ej. «MXN» o «%»\n * @param {number} [props.maxDigits] tope de dígitos (por omisión 17)\n * @param {boolean} [props.disabled]\n * @param {boolean} [props.required] obligatorio: el asterisco en la etiqueta y, como en MUI, required en el campo\n * @param {import('react').Ref<HTMLInputElement>} [props.inputRef]\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] se acepta como en los demás campos; este no tiene textos propios (su error usa `messages`)\n */\nfunction NumberInputContent(props) {\n const {\n name, label, value, onChange, onBlur, error, helperText = ' ', decimals = 2, allowNegative = false, prefix, suffix, maxDigits = 17,\n disabled = false, required = false, inputRef, messages, texts: unusedTexts,\n ...unknownProps\n } = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\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 generatedId = useId();\n const inputId = `${name ?? 'number'}-${generatedId}`;\n const [isTyping, setIsTyping] = useState(false);\n const errorText = validationTextOf(error);\n const valueText = value === null || value === undefined ? '' : String(value);\n // escribiendo: lo que hay; fuera del campo: con sus decimales completos\n const shownText = groupThousands(isTyping ? valueText : completeDecimals(valueText, decimals));\n\n return (\n <FieldFrame inputId={inputId} label={label} required={required}>\n <NumberInputField\n name={name}\n id={inputId}\n inputRef={inputRef}\n value={shownText}\n disabled={disabled}\n required={required}\n size=\"small\"\n fullWidth\n error={Boolean(errorText)}\n helperText={errorText ?? helperText}\n onFocus={(event) => {\n const input = event.target;\n // OJO: al entrar, el texto cambia («50,000.00» → «50,000») y el navegador pierde lo seleccionado; al entrar\n // con Tab estaba todo seleccionado y lo tecleado se pegaba al final (50,000,100,000). Si estaba todo\n // seleccionado, se vuelve a seleccionar ya pintado; con un clic (el cursor en un lugar) no se toca\n const wasAllSelected = input.value.length > 0 && input.selectionStart === 0 && input.selectionEnd === input.value.length;\n setIsTyping(true);\n if (wasAllSelected) {\n requestAnimationFrame(() => {\n if (document.activeElement === input) {\n input.select();\n }\n });\n }\n }}\n onChange={(event) => onChange(cleanNumberText(event.target.value, decimals, allowNegative))}\n onBlur={() => {\n setIsTyping(false);\n const completedText = completeDecimals(cleanNumberText(valueText, decimals, allowNegative), decimals);\n if (completedText !== valueText) {\n onChange(completedText);\n }\n onBlur?.();\n }}\n slotProps={{\n htmlInput: {inputMode: 'decimal', maxLength: maxDigits + SEPARATORS_ROOM, style: {textAlign: 'right'}},\n input: {\n startAdornment: prefix ? <InputAdornment position=\"start\">{prefix}</InputAdornment> : undefined,\n endAdornment: suffix ? <InputAdornment position=\"end\">{suffix}</InputAdornment> : undefined,\n },\n }}\n />\n </FieldFrame>\n );\n}\n\n// lo que se exporta: la pieza con slots y slotProps para cambiar sus partes (themePieces.js)\nconst NumberInput = withPieceSlots(NumberInputContent, THEME_NAME);\nexport default NumberInput;\n"],"names":[],"mappings":";;;;;;;;;;AAuBA,MAAM,aAAa;AACnB,MAAM,mBAAmB,UAAU,YAAY,SAAS,SAAS;AAGjE,MAAM,kBAAkB;AAuBxB,SAAS,mBAAmB,OAAO;AAC/B,QAAM;AAAA,IACF;AAAA,IAAM;AAAA,IAAO;AAAA,IAAO;AAAA,IAAU;AAAA,IAAQ;AAAA,IAAO,aAAa;AAAA,IAAK,WAAW;AAAA,IAAG,gBAAgB;AAAA,IAAO;AAAA,IAAQ;AAAA,IAAQ,YAAY;AAAA,IAChI,WAAW;AAAA,IAAO,WAAW;AAAA,IAAO;AAAA,IAAU;AAAA,IAAU,OAAO;AAAA,IAC/D,GAAG;AAAA,EAAA,IACH,cAAc,OAAO,UAAU;AACnC,mBAAiB,YAAY,YAAY;AAEzC,QAAM,mBAAmB,kBAAkB,EAAC,MAAM,OAAO,UAAS;AAClE,QAAM,cAAc,MAAA;AACpB,QAAM,UAAU,GAAG,QAAQ,QAAQ,IAAI,WAAW;AAClD,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,KAAK;AAC9C,QAAM,YAAY,iBAAiB,KAAK;AACxC,QAAM,YAAY,UAAU,QAAQ,UAAU,SAAY,KAAK,OAAO,KAAK;AAE3E,QAAM,YAAY,eAAe,WAAW,YAAY,iBAAiB,WAAW,QAAQ,CAAC;AAE7F,SACI,oBAAC,YAAA,EAAW,SAAkB,OAAc,UACxC,UAAA;AAAA,IAAC;AAAA,IAAA;AAAA,MACG;AAAA,MACA,IAAI;AAAA,MACJ;AAAA,MACA,OAAO;AAAA,MACP;AAAA,MACA;AAAA,MACA,MAAK;AAAA,MACL,WAAS;AAAA,MACT,OAAO,QAAQ,SAAS;AAAA,MACxB,YAAY,aAAa;AAAA,MACzB,SAAS,CAAC,UAAU;AAChB,cAAM,QAAQ,MAAM;AAIpB,cAAM,iBAAiB,MAAM,MAAM,SAAS,KAAK,MAAM,mBAAmB,KAAK,MAAM,iBAAiB,MAAM,MAAM;AAClH,oBAAY,IAAI;AAChB,YAAI,gBAAgB;AAChB,gCAAsB,MAAM;AACxB,gBAAI,SAAS,kBAAkB,OAAO;AAClC,oBAAM,OAAA;AAAA,YACV;AAAA,UACJ,CAAC;AAAA,QACL;AAAA,MACJ;AAAA,MACA,UAAU,CAAC,UAAU,SAAS,gBAAgB,MAAM,OAAO,OAAO,UAAU,aAAa,CAAC;AAAA,MAC1F,QAAQ,MAAM;AACV,oBAAY,KAAK;AACjB,cAAM,gBAAgB,iBAAiB,gBAAgB,WAAW,UAAU,aAAa,GAAG,QAAQ;AACpG,YAAI,kBAAkB,WAAW;AAC7B,mBAAS,aAAa;AAAA,QAC1B;AACA,iBAAA;AAAA,MACJ;AAAA,MACA,WAAW;AAAA,QACP,WAAW,EAAC,WAAW,WAAW,WAAW,YAAY,iBAAiB,OAAO,EAAC,WAAW,UAAO;AAAA,QACpG,OAAO;AAAA,UACH,gBAAgB,SAAS,oBAAC,kBAAe,UAAS,SAAS,kBAAO,IAAoB;AAAA,UACtF,cAAc,SAAS,oBAAC,kBAAe,UAAS,OAAO,kBAAO,IAAoB;AAAA,QAAA;AAAA,MACtF;AAAA,IACJ;AAAA,EAAA,GAER;AAER;AAGA,MAAM,cAAc,eAAe,oBAAoB,UAAU;"}
|
|
@@ -29,6 +29,7 @@ function SelectInputContent(props) {
|
|
|
29
29
|
helperText = " ",
|
|
30
30
|
placeholder,
|
|
31
31
|
disabled = false,
|
|
32
|
+
required = false,
|
|
32
33
|
inputRef,
|
|
33
34
|
multiple = false,
|
|
34
35
|
selectAll = false,
|
|
@@ -70,7 +71,7 @@ function SelectInputContent(props) {
|
|
|
70
71
|
}
|
|
71
72
|
onChange(values);
|
|
72
73
|
};
|
|
73
|
-
return /* @__PURE__ */ jsx(FieldFrame, { inputId, labelId, label, children: /* @__PURE__ */ jsxs(
|
|
74
|
+
return /* @__PURE__ */ jsx(FieldFrame, { inputId, labelId, label, required, children: /* @__PURE__ */ jsxs(
|
|
74
75
|
TextField,
|
|
75
76
|
{
|
|
76
77
|
name,
|
|
@@ -80,6 +81,7 @@ function SelectInputContent(props) {
|
|
|
80
81
|
size: "small",
|
|
81
82
|
fullWidth: true,
|
|
82
83
|
disabled,
|
|
84
|
+
required,
|
|
83
85
|
value: currentValue,
|
|
84
86
|
onChange: handleChange,
|
|
85
87
|
onBlur,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SelectInput.js","sources":["../../src/inputs/SelectInput.jsx"],"sourcesContent":["// Lista desplegable suelta, con la etiqueta arriba (mismo look que TextInput): la que pintan FormSelectField (formik) y la\n// de react-hook-form, y la que se usa sin formulario.\n//\n// Cada opción puede traer una explicación (description) que se lee en la lista para escoger; en la caja queda solo el\n// nombre de lo elegido. OJO: sin renderValue, MUI copiaba la opción completa a la caja y se leía «MenudeoCompra por pieza…».\n//\n// Con `multiple` se eligen varias: la lista se queda abierta, cada elegida lleva su palomita y en la caja quedan sus nombres\n// separados por coma, en el orden de las opciones. Al formulario llega la lista de valores (Yup.array().min(1) la valida).\n// Para catálogos largos, CatalogInput con `multiple` (busca y deja chips). Con varias, además: selectAll («Elegir todos»\n// arriba, con su casilla a medias si hay algunos) y checkboxes (una casilla en cada opción en lugar de la palomita).\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaSelectInput con defaultProps y\n// styleOverrides de sus partes: option, optionText.\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 Checkbox from '@mui/material/Checkbox';\nimport MenuItem from '@mui/material/MenuItem';\nimport TextField from '@mui/material/TextField';\nimport Typography from '@mui/material/Typography';\n\nimport {useValidationText} from '@/forms/useValidationText.js';\nimport FieldFrame from '@/inputs/FieldFrame.jsx';\nimport {selectAllStateOf} from '@/inputs/selectAllState.js';\nimport {piecePart, usePieceProps, withPieceSlots} from '@/theme/themePieces.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaSelectInput)\nconst THEME_NAME = 'CalcitaSelectInput';\nconst SelectOption = piecePart(THEME_NAME, 'Option', MenuItem);\nconst SelectOptionText = piecePart(THEME_NAME, 'OptionText', Box);\n// la opción de «Elegir todos»: no es de la lista y nunca llega al formulario\nconst SELECT_ALL_VALUE = '__calcita_select_all__';\n// con casillas, la palomita que el tema le pone al elegido sobra\nconst WITHOUT_CHECKMARK = Object.freeze({'&.Mui-selected::after': {content: 'none'}});\n\n/**\n * @param {object} props\n * @param {string} [props.name]\n * @param {string} props.label ya traducido\n * @param {string|number|Array<string|number>|null|undefined} props.value '' = sin elegir; con multiple, la lista ([] = ninguna)\n * @param {(value: any) => void} props.onChange recibe el valor elegido (con multiple, la lista), no el evento\n * @param {() => void} [props.onBlur]\n * @param {Array<{value: string|number, label: string, description?: string, disabled?: boolean}>} props.options\n * @param {string|{key: string, values?: object}} [props.error]\n * @param {string} [props.helperText] ayuda fija; el error la reemplaza\n * @param {string} [props.placeholder] texto de la opción vacía\n * @param {boolean} [props.disabled]\n * @param {import('react').Ref<HTMLInputElement>} [props.inputRef]\n * @param {boolean} [props.multiple] elegir varias opciones\n * @param {boolean} [props.selectAll] con multiple: «Elegir todos» arriba de la lista\n * @param {boolean} [props.checkboxes] con multiple: una casilla en cada opció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 SelectInputContent(props) {\n // lo que el proyecto puso en su tema (components.CalcitaSelectInput.defaultProps) y encima lo que llega aquí\n const {\n name, label, value, onChange, onBlur, options, error, helperText = ' ', placeholder, disabled = false, inputRef, multiple = false,\n selectAll = false, checkboxes = false, 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 generatedId = useId();\n const inputId = `${name ?? 'select'}-${generatedId}`;\n // OJO: la lista desplegable de MUI es un div y no un <input>: el `for` de la etiqueta no la une. Se nombra con su etiqueta\n // por aria-labelledby (labelId); sin esto el lector de pantalla no decía «Ciudad», solo lo elegido\n const labelId = `${inputId}-label`;\n const errorText = validationTextOf(error);\n const placeholderText = (\n <Typography component=\"span\"\n // OJO: el placeholder de un select activo se lee (text.secondary); text.disabled solo con el campo inactivo\n color={disabled ? 'text.disabled' : 'text.secondary'}>{placeholder ?? t('form.selectPlaceholder')}</Typography>\n );\n const renderSelectedValue = (selectedValue) => {\n if (multiple) {\n const selectedLabels = options.filter((option) => selectedValue.includes(option.value)).map((option) => option.label);\n return selectedLabels.length > 0 ? selectedLabels.join(', ') : placeholderText;\n }\n return options.find((option) => option.value === selectedValue)?.label ?? placeholderText;\n };\n const currentValue = multiple ? (Array.isArray(value) ? value : []) : (value ?? '');\n // «Elegir todos» solo existe con varias. OJO, trampa pagada: se calculaba también con una sola, y un valor que es número\n // (un recordatorio de 60 minutos) tumbaba la pantalla: new Set(60) no es una lista\n const selectAllState = multiple\n ? selectAllStateOf(currentValue, options.filter((option) => !option.disabled).map((option) => option.value))\n : null;\n // OJO: con multiple, el autollenado del navegador puede mandar el valor como texto separado por comas (lo dice MUI)\n const handleChange = (event) => {\n const nextValue = event.target.value;\n const values = multiple && typeof nextValue === 'string' ? nextValue.split(',') : nextValue;\n // «Elegir todos» llega como una opción más: se cambia por todos (o por ninguno, si ya estaban todos)\n if (multiple && values.includes(SELECT_ALL_VALUE)) {\n onChange(selectAllState.toggled());\n return;\n }\n onChange(values);\n };\n\n return (\n <FieldFrame inputId={inputId} labelId={labelId} label={label}>\n <TextField\n name={name}\n id={inputId}\n inputRef={inputRef}\n select\n size=\"small\"\n fullWidth\n disabled={disabled}\n value={currentValue}\n onChange={handleChange}\n onBlur={onBlur}\n error={Boolean(errorText)}\n helperText={errorText ?? helperText}\n slotProps={{select: {multiple, displayEmpty: true, renderValue: renderSelectedValue, labelId}}}>\n {/* la opción vacía solo con una: con varias, se quitan picándolas otra vez */}\n {!multiple && <MenuItem value=\"\" disabled>{placeholderText}</MenuItem>}\n {multiple && selectAll && (\n <SelectOption value={SELECT_ALL_VALUE} ownSx={{fontWeight: 600, ...WITHOUT_CHECKMARK}}>\n <Checkbox size=\"small\" tabIndex={-1} disableRipple sx={{p: 0, mr: 1}}\n checked={selectAllState.allChosen} indeterminate={selectAllState.someChosen} />\n {selectAllState.allChosen ? t('form.unselectAll') : t('form.selectAll', {count: options.length})}\n </SelectOption>\n )}\n {options.map((option) => (\n // la opción en renglón y su nombre con su explicación en un bloque: con la opción en columna, la palomita del\n // elegido (el tema la pinta al final) se bajaba a otro renglón (lo cachó una captura de Tareas)\n <SelectOption key={option.value} value={option.value} disabled={option.disabled}\n ownSx={{whiteSpace: 'normal', ...(multiple && checkboxes && WITHOUT_CHECKMARK)}}>\n {multiple && checkboxes && (\n <Checkbox size=\"small\" tabIndex={-1} disableRipple sx={{p: 0, mr: 1}} checked={currentValue.includes(option.value)} />\n )}\n <SelectOptionText component=\"span\" ownSx={{display: 'flex', flexDirection: 'column', minWidth: 0, flexGrow: 1}}>\n <Typography component=\"span\" variant=\"body2\">{option.label}</Typography>\n {option.description && (\n <Typography component=\"span\" variant=\"caption\" color=\"text.secondary\">{option.description}</Typography>\n )}\n </SelectOptionText>\n </SelectOption>\n ))}\n </TextField>\n </FieldFrame>\n );\n}\n\n// lo que se exporta: la pieza con slots y slotProps para cambiar sus partes (themePieces.js)\nconst SelectInput = withPieceSlots(SelectInputContent, THEME_NAME);\nexport default SelectInput;\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SelectInput.js","sources":["../../src/inputs/SelectInput.jsx"],"sourcesContent":["// Lista desplegable suelta, con la etiqueta arriba (mismo look que TextInput): la que pintan FormSelectField (formik) y la\n// de react-hook-form, y la que se usa sin formulario.\n//\n// Cada opción puede traer una explicación (description) que se lee en la lista para escoger; en la caja queda solo el\n// nombre de lo elegido. OJO: sin renderValue, MUI copiaba la opción completa a la caja y se leía «MenudeoCompra por pieza…».\n//\n// Con `multiple` se eligen varias: la lista se queda abierta, cada elegida lleva su palomita y en la caja quedan sus nombres\n// separados por coma, en el orden de las opciones. Al formulario llega la lista de valores (Yup.array().min(1) la valida).\n// Para catálogos largos, CatalogInput con `multiple` (busca y deja chips). Con varias, además: selectAll («Elegir todos»\n// arriba, con su casilla a medias si hay algunos) y checkboxes (una casilla en cada opción en lugar de la palomita).\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaSelectInput con defaultProps y\n// styleOverrides de sus partes: option, optionText.\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 Checkbox from '@mui/material/Checkbox';\nimport MenuItem from '@mui/material/MenuItem';\nimport TextField from '@mui/material/TextField';\nimport Typography from '@mui/material/Typography';\n\nimport {useValidationText} from '@/forms/useValidationText.js';\nimport FieldFrame from '@/inputs/FieldFrame.jsx';\nimport {selectAllStateOf} from '@/inputs/selectAllState.js';\nimport {piecePart, usePieceProps, withPieceSlots} from '@/theme/themePieces.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaSelectInput)\nconst THEME_NAME = 'CalcitaSelectInput';\nconst SelectOption = piecePart(THEME_NAME, 'Option', MenuItem);\nconst SelectOptionText = piecePart(THEME_NAME, 'OptionText', Box);\n// la opción de «Elegir todos»: no es de la lista y nunca llega al formulario\nconst SELECT_ALL_VALUE = '__calcita_select_all__';\n// con casillas, la palomita que el tema le pone al elegido sobra\nconst WITHOUT_CHECKMARK = Object.freeze({'&.Mui-selected::after': {content: 'none'}});\n\n/**\n * @param {object} props\n * @param {string} [props.name]\n * @param {string} props.label ya traducido\n * @param {string|number|Array<string|number>|null|undefined} props.value '' = sin elegir; con multiple, la lista ([] = ninguna)\n * @param {(value: any) => void} props.onChange recibe el valor elegido (con multiple, la lista), no el evento\n * @param {() => void} [props.onBlur]\n * @param {Array<{value: string|number, label: string, description?: string, disabled?: boolean}>} props.options\n * @param {string|{key: string, values?: object}} [props.error]\n * @param {string} [props.helperText] ayuda fija; el error la reemplaza\n * @param {string} [props.placeholder] texto de la opción vacía\n * @param {boolean} [props.disabled]\n * @param {boolean} [props.required] obligatorio: el asterisco en la etiqueta y, como en MUI, required en el campo\n * @param {import('react').Ref<HTMLInputElement>} [props.inputRef]\n * @param {boolean} [props.multiple] elegir varias opciones\n * @param {boolean} [props.selectAll] con multiple: «Elegir todos» arriba de la lista\n * @param {boolean} [props.checkboxes] con multiple: una casilla en cada opció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 SelectInputContent(props) {\n // lo que el proyecto puso en su tema (components.CalcitaSelectInput.defaultProps) y encima lo que llega aquí\n const {\n name, label, value, onChange, onBlur, options, error, helperText = ' ', placeholder, disabled = false, required = false, inputRef, multiple = false,\n selectAll = false, checkboxes = false, 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 generatedId = useId();\n const inputId = `${name ?? 'select'}-${generatedId}`;\n // OJO: la lista desplegable de MUI es un div y no un <input>: el `for` de la etiqueta no la une. Se nombra con su etiqueta\n // por aria-labelledby (labelId); sin esto el lector de pantalla no decía «Ciudad», solo lo elegido\n const labelId = `${inputId}-label`;\n const errorText = validationTextOf(error);\n const placeholderText = (\n <Typography component=\"span\"\n // OJO: el placeholder de un select activo se lee (text.secondary); text.disabled solo con el campo inactivo\n color={disabled ? 'text.disabled' : 'text.secondary'}>{placeholder ?? t('form.selectPlaceholder')}</Typography>\n );\n const renderSelectedValue = (selectedValue) => {\n if (multiple) {\n const selectedLabels = options.filter((option) => selectedValue.includes(option.value)).map((option) => option.label);\n return selectedLabels.length > 0 ? selectedLabels.join(', ') : placeholderText;\n }\n return options.find((option) => option.value === selectedValue)?.label ?? placeholderText;\n };\n const currentValue = multiple ? (Array.isArray(value) ? value : []) : (value ?? '');\n // «Elegir todos» solo existe con varias. OJO, trampa pagada: se calculaba también con una sola, y un valor que es número\n // (un recordatorio de 60 minutos) tumbaba la pantalla: new Set(60) no es una lista\n const selectAllState = multiple\n ? selectAllStateOf(currentValue, options.filter((option) => !option.disabled).map((option) => option.value))\n : null;\n // OJO: con multiple, el autollenado del navegador puede mandar el valor como texto separado por comas (lo dice MUI)\n const handleChange = (event) => {\n const nextValue = event.target.value;\n const values = multiple && typeof nextValue === 'string' ? nextValue.split(',') : nextValue;\n // «Elegir todos» llega como una opción más: se cambia por todos (o por ninguno, si ya estaban todos)\n if (multiple && values.includes(SELECT_ALL_VALUE)) {\n onChange(selectAllState.toggled());\n return;\n }\n onChange(values);\n };\n\n return (\n <FieldFrame inputId={inputId} labelId={labelId} label={label} required={required}>\n <TextField\n name={name}\n id={inputId}\n inputRef={inputRef}\n select\n size=\"small\"\n fullWidth\n disabled={disabled}\n required={required}\n value={currentValue}\n onChange={handleChange}\n onBlur={onBlur}\n error={Boolean(errorText)}\n helperText={errorText ?? helperText}\n slotProps={{select: {multiple, displayEmpty: true, renderValue: renderSelectedValue, labelId}}}>\n {/* la opción vacía solo con una: con varias, se quitan picándolas otra vez */}\n {!multiple && <MenuItem value=\"\" disabled>{placeholderText}</MenuItem>}\n {multiple && selectAll && (\n <SelectOption value={SELECT_ALL_VALUE} ownSx={{fontWeight: 600, ...WITHOUT_CHECKMARK}}>\n <Checkbox size=\"small\" tabIndex={-1} disableRipple sx={{p: 0, mr: 1}}\n checked={selectAllState.allChosen} indeterminate={selectAllState.someChosen} />\n {selectAllState.allChosen ? t('form.unselectAll') : t('form.selectAll', {count: options.length})}\n </SelectOption>\n )}\n {options.map((option) => (\n // la opción en renglón y su nombre con su explicación en un bloque: con la opción en columna, la palomita del\n // elegido (el tema la pinta al final) se bajaba a otro renglón (lo cachó una captura de Tareas)\n <SelectOption key={option.value} value={option.value} disabled={option.disabled}\n ownSx={{whiteSpace: 'normal', ...(multiple && checkboxes && WITHOUT_CHECKMARK)}}>\n {multiple && checkboxes && (\n <Checkbox size=\"small\" tabIndex={-1} disableRipple sx={{p: 0, mr: 1}} checked={currentValue.includes(option.value)} />\n )}\n <SelectOptionText component=\"span\" ownSx={{display: 'flex', flexDirection: 'column', minWidth: 0, flexGrow: 1}}>\n <Typography component=\"span\" variant=\"body2\">{option.label}</Typography>\n {option.description && (\n <Typography component=\"span\" variant=\"caption\" color=\"text.secondary\">{option.description}</Typography>\n )}\n </SelectOptionText>\n </SelectOption>\n ))}\n </TextField>\n </FieldFrame>\n );\n}\n\n// lo que se exporta: la pieza con slots y slotProps para cambiar sus partes (themePieces.js)\nconst SelectInput = withPieceSlots(SelectInputContent, THEME_NAME);\nexport default SelectInput;\n"],"names":[],"mappings":"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"}
|
package/dist/inputs/TextInput.js
CHANGED
|
@@ -76,7 +76,7 @@ function TextInput(props) {
|
|
|
76
76
|
}
|
|
77
77
|
onBlur?.();
|
|
78
78
|
};
|
|
79
|
-
return /* @__PURE__ */ jsx(FieldFrame, { inputId, label, children: /* @__PURE__ */ jsx(
|
|
79
|
+
return /* @__PURE__ */ jsx(FieldFrame, { inputId, label, required: Boolean(textFieldProps.required), children: /* @__PURE__ */ jsx(
|
|
80
80
|
InputField,
|
|
81
81
|
{
|
|
82
82
|
...textFieldProps,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TextInput.js","sources":["../../src/inputs/TextInput.jsx"],"sourcesContent":["// Campo de texto suelto, con la etiqueta arriba: el que pintan FormTextField (formik) y el de react-hook-form, y el que se\n// usa sin formulario (controlado: value / onChange).\n//\n// Lo que resuelve, y por lo que existe en vez de un <TextField> suelto:\n// - el renglón de ayuda está reservado desde el principio, así la tarjeta no brinca cuando aparece un error;\n// - las contraseñas traen el ojo para revelarlas (con aria-label traducido);\n// - con texto, la ⓧ para borrarlo de un toque, como los campos de Apple («Display a Clear button in the trailing end of\n// a text field», HIG «Text fields»). Sale con el cursor encima o al escribir, para que un formulario lleno no se vea\n// lleno de ⓧ; no entra al recorrido del teclado (con el teclado se borra seleccionando el texto);\n// - normalizeOnBlur deja el valor como lo va a mandar el formulario (el correo en minúsculas y sin espacios) al salir del\n// campo: la persona ve lo que se envía;\n// - uppercase normaliza a mayúsculas mientras se escribe (claves, RFC);\n// - el error llega como LLAVE de yup ({key, values}) o como texto ya traducido (el del servidor) y se pinta en el idioma\n// activo. Cuándo enseñarlo (ya se tocó, ya se envió) lo decide quien lo conecta al formulario.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaTextInput con defaultProps y\n// styleOverrides.field (el TextField). El sx de la pantalla sigue ganando a todo.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useId, useState} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport IconButton from '@mui/material/IconButton';\nimport InputAdornment from '@mui/material/InputAdornment';\nimport TextField from '@mui/material/TextField';\nimport Tooltip from '@mui/material/Tooltip';\nimport CancelIcon from '@mui/icons-material/Cancel';\nimport VisibilityOffOutlinedIcon from '@mui/icons-material/VisibilityOffOutlined';\nimport VisibilityOutlinedIcon from '@mui/icons-material/VisibilityOutlined';\n\nimport {useValidationText} from '@/forms/useValidationText.js';\nimport FieldFrame from '@/inputs/FieldFrame.jsx';\nimport {piecePart, usePieceProps} from '@/theme/themePieces.js';\n\n// el nombre de la pieza en el tema y su parte (theme.components.CalcitaTextInput): el campo de MUI con lo de Calcita abajo,\n// el tema del proyecto encima y el sx de la pantalla al final\nconst THEME_NAME = 'CalcitaTextInput';\nconst InputField = piecePart(THEME_NAME, 'Field', TextField);\n\n// la ⓧ de borrar: escondida hasta que el campo tiene el cursor encima o se está escribiendo en él\nconst CLEAR_BUTTON_CLASS = 'calcita-clear-button';\nconst CLEAR_BUTTON_SX = Object.freeze({\n [`& .${CLEAR_BUTTON_CLASS}`]: {visibility: 'hidden', color: 'text.secondary'},\n [`& .MuiInputBase-root:hover .${CLEAR_BUTTON_CLASS}, & .MuiInputBase-root.Mui-focused .${CLEAR_BUTTON_CLASS}`]: {visibility: 'visible'},\n // un campo de búsqueda (type=\"search\") trae la × del navegador: con la ⓧ de la casa salían dos para lo mismo (lo cachó\n // una captura de Tareas). Se esconde la del navegador; la de la casa es la que se ve igual en todos\n '& input[type=\"search\"]::-webkit-search-cancel-button': {WebkitAppearance: 'none', display: 'none'},\n});\n\n/**\n * @param {object} props\n * @param {string} [props.name]\n * @param {string} props.label ya traducido\n * @param {string|null|undefined} props.value\n * @param {(value: string) => void} props.onChange recibe el texto, no el evento\n * @param {() => void} [props.onBlur]\n * @param {string|{key: string, values?: object}} [props.error] el error a enseñar (undefined = sin error)\n * @param {string} [props.type] 'password' agrega el ojo\n * @param {string} [props.helperText] ayuda fija; el error la reemplaza. Por omisión el campo deja su renglón vacío para que\n * no brinque al salir un error; con helperText=\"\" no lo deja (un campo con botones al lado en el mismo renglón)\n * @param {string} [props.placeholder] ya traducido; el ejemplo de qué escribir (se va al escribir)\n * @param {import('react').ReactNode} [props.startAdornment]\n * @param {import('react').ReactNode} [props.endAdornment] adorno al final (en contraseñas manda el ojo)\n * @param {boolean} [props.uppercase] normaliza a mayúsculas mientras se escribe\n * @param {(value: string) => string} [props.normalizeOnBlur]\n * @param {import('react').Ref<HTMLInputElement>} [props.inputRef] para enfocar el campo (react-hook-form lo usa)\n * @param {boolean} [props.clearable] la ⓧ para borrar el texto; por omisión sí (no en contraseñas ni con adorno propio)\n * @param {boolean} [props.multiline] (y lo demás de TextField de MUI: minRows, autoComplete, autoFocus, slotProps, sx…)\n * @param {number} [props.minRows]\n * @param {number} [props.maxRows]\n * @param {string} [props.autoComplete]\n * @param {boolean} [props.autoFocus]\n * @param {boolean} [props.disabled]\n * @param {boolean} [props.required]\n * @param {boolean} [props.fullWidth]\n * @param {string} [props.id]\n * @param {object} [props.slotProps]\n * @param {object} [props.sx]\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 */\nexport default function TextInput(props) {\n // lo que el proyecto puso en su tema (components.CalcitaTextInput.defaultProps) y encima lo que llega aquí\n const {\n name, label, value, onChange, onBlur, error, type = 'text', helperText = ' ', startAdornment, endAdornment: customEndAdornment,\n uppercase = false, normalizeOnBlur, inputRef, clearable = true, messages, texts, ...textFieldProps\n } = usePieceProps(props, THEME_NAME, {ownSlotProps: true}); // su slotProps es el del TextField de MUI (input, htmlInput…)\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 [isRevealed, setIsRevealed] = useState(false);\n const generatedId = useId();\n const inputId = textFieldProps.id ?? `${name ?? 'text'}-${generatedId}`;\n const errorText = validationTextOf(error);\n const isPassword = type === 'password';\n const revealLabel = isRevealed ? t('form.hidePassword') : t('form.showPassword');\n\n const passwordAdornment = isPassword ? (\n <InputAdornment position=\"end\">\n <Tooltip title={revealLabel}>\n <IconButton size=\"small\" edge=\"end\" aria-label={revealLabel} onClick={() => setIsRevealed((currentValue) => !currentValue)}>\n {isRevealed ? <VisibilityOffOutlinedIcon fontSize=\"small\" /> : <VisibilityOutlinedIcon fontSize=\"small\" />}\n </IconButton>\n </Tooltip>\n </InputAdornment>\n ) : undefined;\n const isEditable = !textFieldProps.disabled && !textFieldProps.slotProps?.htmlInput?.readOnly;\n const canClear = clearable && !isPassword && !customEndAdornment && isEditable && Boolean(value);\n // onMouseDown sin acción: el foco se queda en el campo al borrar (si no, el campo pierde el foco y se valida de más)\n const clearAdornment = canClear ? (\n <InputAdornment position=\"end\">\n <IconButton size=\"small\" edge=\"end\" tabIndex={-1} aria-label={t('form.clear')} className={CLEAR_BUTTON_CLASS}\n onMouseDown={(event) => event.preventDefault()} onClick={() => onChange('')}>\n <CancelIcon fontSize=\"small\" />\n </IconButton>\n </InputAdornment>\n ) : undefined;\n const endAdornment = customEndAdornment ? <InputAdornment position=\"end\">{customEndAdornment}</InputAdornment> : (passwordAdornment ?? clearAdornment);\n\n const handleBlur = () => {\n if (normalizeOnBlur) {\n const normalizedValue = normalizeOnBlur(value ?? '');\n // solo si cambió: un cambio de más dispara una validación de más\n if (normalizedValue !== value) {\n onChange(normalizedValue);\n }\n }\n onBlur?.();\n };\n\n return (\n <FieldFrame inputId={inputId} label={label}>\n <InputField\n {...textFieldProps}\n name={name}\n id={inputId}\n inputRef={inputRef}\n value={value ?? ''}\n onChange={(event) => onChange(uppercase ? event.target.value.toUpperCase() : event.target.value)}\n onBlur={handleBlur}\n type={isPassword && isRevealed ? 'text' : type}\n ownSx={CLEAR_BUTTON_SX}\n sx={textFieldProps.sx}\n error={Boolean(errorText)}\n helperText={errorText ?? helperText}\n fullWidth\n size=\"small\"\n // OJO: lo que el proyecto pasó en slotProps (htmlInput: {maxLength}) se suma a los adornos de la casa. Antes se\n // pisaba entero y el tope de caracteres de una pantalla se perdía sin avisar\n slotProps={{\n ...textFieldProps.slotProps,\n input: {\n ...textFieldProps.slotProps?.input,\n startAdornment: startAdornment ? <InputAdornment position=\"start\">{startAdornment}</InputAdornment> : undefined,\n endAdornment,\n },\n }}\n />\n </FieldFrame>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;AAmCA,MAAM,aAAa;AACnB,MAAM,aAAa,UAAU,YAAY,SAAS,SAAS;AAG3D,MAAM,qBAAqB;AAC3B,MAAM,kBAAkB,OAAO,OAAO;AAAA,EAClC,CAAC,MAAM,kBAAkB,EAAE,GAAG,EAAC,YAAY,UAAU,OAAO,iBAAA;AAAA,EAC5D,CAAC,+BAA+B,kBAAkB,uCAAuC,kBAAkB,EAAE,GAAG,EAAC,YAAY,UAAA;AAAA;AAAA;AAAA,EAG7H,wDAAwD,EAAC,kBAAkB,QAAQ,SAAS,OAAA;AAChG,CAAC;AAmCD,SAAwB,UAAU,OAAO;AAErC,QAAM;AAAA,IACF;AAAA,IAAM;AAAA,IAAO;AAAA,IAAO;AAAA,IAAU;AAAA,IAAQ;AAAA,IAAO,OAAO;AAAA,IAAQ,aAAa;AAAA,IAAK;AAAA,IAAgB,cAAc;AAAA,IAC5G,YAAY;AAAA,IAAO;AAAA,IAAiB;AAAA,IAAU,YAAY;AAAA,IAAM;AAAA,IAAU;AAAA,IAAO,GAAG;AAAA,EAAA,IACpF,cAAc,OAAO,YAAY,EAAC,cAAc,MAAK;AACzD,QAAM,EAAC,EAAA,IAAK,sBAAsB,UAAU,KAAK;AAEjD,QAAM,mBAAmB,kBAAkB,EAAC,MAAM,OAAO,UAAS;AAClE,QAAM,CAAC,YAAY,aAAa,IAAI,SAAS,KAAK;AAClD,QAAM,cAAc,MAAA;AACpB,QAAM,UAAU,eAAe,MAAM,GAAG,QAAQ,MAAM,IAAI,WAAW;AACrE,QAAM,YAAY,iBAAiB,KAAK;AACxC,QAAM,aAAa,SAAS;AAC5B,QAAM,cAAc,aAAa,EAAE,mBAAmB,IAAI,EAAE,mBAAmB;AAE/E,QAAM,oBAAoB,aACtB,oBAAC,gBAAA,EAAe,UAAS,OACrB,UAAA,oBAAC,SAAA,EAAQ,OAAO,aACZ,8BAAC,YAAA,EAAW,MAAK,SAAQ,MAAK,OAAM,cAAY,aAAa,SAAS,MAAM,cAAc,CAAC,iBAAiB,CAAC,YAAY,GACpH,UAAA,iCAAc,2BAAA,EAA0B,UAAS,QAAA,CAAQ,wBAAM,wBAAA,EAAuB,UAAS,SAAQ,GAC5G,EAAA,CACJ,GACJ,IACA;AACJ,QAAM,aAAa,CAAC,eAAe,YAAY,CAAC,eAAe,WAAW,WAAW;AACrF,QAAM,WAAW,aAAa,CAAC,cAAc,CAAC,sBAAsB,cAAc,QAAQ,KAAK;AAE/F,QAAM,iBAAiB,WACnB,oBAAC,gBAAA,EAAe,UAAS,OACrB,UAAA;AAAA,IAAC;AAAA,IAAA;AAAA,MAAW,MAAK;AAAA,MAAQ,MAAK;AAAA,MAAM,UAAU;AAAA,MAAI,cAAY,EAAE,YAAY;AAAA,MAAG,WAAW;AAAA,MAC9E,aAAa,CAAC,UAAU,MAAM,eAAA;AAAA,MAAkB,SAAS,MAAM,SAAS,EAAE;AAAA,MAClF,UAAA,oBAAC,YAAA,EAAW,UAAS,QAAA,CAAQ;AAAA,IAAA;AAAA,EAAA,GAErC,IACA;AACJ,QAAM,eAAe,qBAAqB,oBAAC,gBAAA,EAAe,UAAS,OAAO,UAAA,oBAAmB,IAAqB,qBAAqB;AAEvI,QAAM,aAAa,MAAM;AACrB,QAAI,iBAAiB;AACjB,YAAM,kBAAkB,gBAAgB,SAAS,EAAE;AAEnD,UAAI,oBAAoB,OAAO;AAC3B,iBAAS,eAAe;AAAA,MAC5B;AAAA,IACJ;AACA,aAAA;AAAA,EACJ;AAEA,SACI,oBAAC,YAAA,EAAW,SAAkB,OAC1B,UAAA;AAAA,IAAC;AAAA,IAAA;AAAA,MACI,GAAG;AAAA,MACJ;AAAA,MACA,IAAI;AAAA,MACJ;AAAA,MACA,OAAO,SAAS;AAAA,MAChB,UAAU,CAAC,UAAU,SAAS,YAAY,MAAM,OAAO,MAAM,YAAA,IAAgB,MAAM,OAAO,KAAK;AAAA,MAC/F,QAAQ;AAAA,MACR,MAAM,cAAc,aAAa,SAAS;AAAA,MAC1C,OAAO;AAAA,MACP,IAAI,eAAe;AAAA,MACnB,OAAO,QAAQ,SAAS;AAAA,MACxB,YAAY,aAAa;AAAA,MACzB,WAAS;AAAA,MACT,MAAK;AAAA,MAGL,WAAW;AAAA,QACP,GAAG,eAAe;AAAA,QAClB,OAAO;AAAA,UACH,GAAG,eAAe,WAAW;AAAA,UAC7B,gBAAgB,iBAAiB,oBAAC,kBAAe,UAAS,SAAS,0BAAe,IAAoB;AAAA,UACtG;AAAA,QAAA;AAAA,MACJ;AAAA,IACJ;AAAA,EAAA,GAER;AAER;"}
|
|
1
|
+
{"version":3,"file":"TextInput.js","sources":["../../src/inputs/TextInput.jsx"],"sourcesContent":["// Campo de texto suelto, con la etiqueta arriba: el que pintan FormTextField (formik) y el de react-hook-form, y el que se\n// usa sin formulario (controlado: value / onChange).\n//\n// Lo que resuelve, y por lo que existe en vez de un <TextField> suelto:\n// - el renglón de ayuda está reservado desde el principio, así la tarjeta no brinca cuando aparece un error;\n// - las contraseñas traen el ojo para revelarlas (con aria-label traducido);\n// - con texto, la ⓧ para borrarlo de un toque, como los campos de Apple («Display a Clear button in the trailing end of\n// a text field», HIG «Text fields»). Sale con el cursor encima o al escribir, para que un formulario lleno no se vea\n// lleno de ⓧ; no entra al recorrido del teclado (con el teclado se borra seleccionando el texto);\n// - normalizeOnBlur deja el valor como lo va a mandar el formulario (el correo en minúsculas y sin espacios) al salir del\n// campo: la persona ve lo que se envía;\n// - uppercase normaliza a mayúsculas mientras se escribe (claves, RFC);\n// - el error llega como LLAVE de yup ({key, values}) o como texto ya traducido (el del servidor) y se pinta en el idioma\n// activo. Cuándo enseñarlo (ya se tocó, ya se envió) lo decide quien lo conecta al formulario.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaTextInput con defaultProps y\n// styleOverrides.field (el TextField). El sx de la pantalla sigue ganando a todo.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useId, useState} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport IconButton from '@mui/material/IconButton';\nimport InputAdornment from '@mui/material/InputAdornment';\nimport TextField from '@mui/material/TextField';\nimport Tooltip from '@mui/material/Tooltip';\nimport CancelIcon from '@mui/icons-material/Cancel';\nimport VisibilityOffOutlinedIcon from '@mui/icons-material/VisibilityOffOutlined';\nimport VisibilityOutlinedIcon from '@mui/icons-material/VisibilityOutlined';\n\nimport {useValidationText} from '@/forms/useValidationText.js';\nimport FieldFrame from '@/inputs/FieldFrame.jsx';\nimport {piecePart, usePieceProps} from '@/theme/themePieces.js';\n\n// el nombre de la pieza en el tema y su parte (theme.components.CalcitaTextInput): el campo de MUI con lo de Calcita abajo,\n// el tema del proyecto encima y el sx de la pantalla al final\nconst THEME_NAME = 'CalcitaTextInput';\nconst InputField = piecePart(THEME_NAME, 'Field', TextField);\n\n// la ⓧ de borrar: escondida hasta que el campo tiene el cursor encima o se está escribiendo en él\nconst CLEAR_BUTTON_CLASS = 'calcita-clear-button';\nconst CLEAR_BUTTON_SX = Object.freeze({\n [`& .${CLEAR_BUTTON_CLASS}`]: {visibility: 'hidden', color: 'text.secondary'},\n [`& .MuiInputBase-root:hover .${CLEAR_BUTTON_CLASS}, & .MuiInputBase-root.Mui-focused .${CLEAR_BUTTON_CLASS}`]: {visibility: 'visible'},\n // un campo de búsqueda (type=\"search\") trae la × del navegador: con la ⓧ de la casa salían dos para lo mismo (lo cachó\n // una captura de Tareas). Se esconde la del navegador; la de la casa es la que se ve igual en todos\n '& input[type=\"search\"]::-webkit-search-cancel-button': {WebkitAppearance: 'none', display: 'none'},\n});\n\n/**\n * @param {object} props\n * @param {string} [props.name]\n * @param {string} props.label ya traducido\n * @param {string|null|undefined} props.value\n * @param {(value: string) => void} props.onChange recibe el texto, no el evento\n * @param {() => void} [props.onBlur]\n * @param {string|{key: string, values?: object}} [props.error] el error a enseñar (undefined = sin error)\n * @param {string} [props.type] 'password' agrega el ojo\n * @param {string} [props.helperText] ayuda fija; el error la reemplaza. Por omisión el campo deja su renglón vacío para que\n * no brinque al salir un error; con helperText=\"\" no lo deja (un campo con botones al lado en el mismo renglón)\n * @param {string} [props.placeholder] ya traducido; el ejemplo de qué escribir (se va al escribir)\n * @param {import('react').ReactNode} [props.startAdornment]\n * @param {import('react').ReactNode} [props.endAdornment] adorno al final (en contraseñas manda el ojo)\n * @param {boolean} [props.uppercase] normaliza a mayúsculas mientras se escribe\n * @param {(value: string) => string} [props.normalizeOnBlur]\n * @param {import('react').Ref<HTMLInputElement>} [props.inputRef] para enfocar el campo (react-hook-form lo usa)\n * @param {boolean} [props.clearable] la ⓧ para borrar el texto; por omisión sí (no en contraseñas ni con adorno propio)\n * @param {boolean} [props.multiline] (y lo demás de TextField de MUI: minRows, autoComplete, autoFocus, slotProps, sx…)\n * @param {number} [props.minRows]\n * @param {number} [props.maxRows]\n * @param {string} [props.autoComplete]\n * @param {boolean} [props.autoFocus]\n * @param {boolean} [props.disabled]\n * @param {boolean} [props.required] obligatorio: el asterisco en la etiqueta y, como en MUI, required en el campo\n * @param {boolean} [props.fullWidth]\n * @param {string} [props.id]\n * @param {object} [props.slotProps]\n * @param {object} [props.sx]\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 */\nexport default function TextInput(props) {\n // lo que el proyecto puso en su tema (components.CalcitaTextInput.defaultProps) y encima lo que llega aquí\n const {\n name, label, value, onChange, onBlur, error, type = 'text', helperText = ' ', startAdornment, endAdornment: customEndAdornment,\n uppercase = false, normalizeOnBlur, inputRef, clearable = true, messages, texts, ...textFieldProps\n } = usePieceProps(props, THEME_NAME, {ownSlotProps: true}); // su slotProps es el del TextField de MUI (input, htmlInput…)\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 [isRevealed, setIsRevealed] = useState(false);\n const generatedId = useId();\n const inputId = textFieldProps.id ?? `${name ?? 'text'}-${generatedId}`;\n const errorText = validationTextOf(error);\n const isPassword = type === 'password';\n const revealLabel = isRevealed ? t('form.hidePassword') : t('form.showPassword');\n\n const passwordAdornment = isPassword ? (\n <InputAdornment position=\"end\">\n <Tooltip title={revealLabel}>\n <IconButton size=\"small\" edge=\"end\" aria-label={revealLabel} onClick={() => setIsRevealed((currentValue) => !currentValue)}>\n {isRevealed ? <VisibilityOffOutlinedIcon fontSize=\"small\" /> : <VisibilityOutlinedIcon fontSize=\"small\" />}\n </IconButton>\n </Tooltip>\n </InputAdornment>\n ) : undefined;\n const isEditable = !textFieldProps.disabled && !textFieldProps.slotProps?.htmlInput?.readOnly;\n const canClear = clearable && !isPassword && !customEndAdornment && isEditable && Boolean(value);\n // onMouseDown sin acción: el foco se queda en el campo al borrar (si no, el campo pierde el foco y se valida de más)\n const clearAdornment = canClear ? (\n <InputAdornment position=\"end\">\n <IconButton size=\"small\" edge=\"end\" tabIndex={-1} aria-label={t('form.clear')} className={CLEAR_BUTTON_CLASS}\n onMouseDown={(event) => event.preventDefault()} onClick={() => onChange('')}>\n <CancelIcon fontSize=\"small\" />\n </IconButton>\n </InputAdornment>\n ) : undefined;\n const endAdornment = customEndAdornment ? <InputAdornment position=\"end\">{customEndAdornment}</InputAdornment> : (passwordAdornment ?? clearAdornment);\n\n const handleBlur = () => {\n if (normalizeOnBlur) {\n const normalizedValue = normalizeOnBlur(value ?? '');\n // solo si cambió: un cambio de más dispara una validación de más\n if (normalizedValue !== value) {\n onChange(normalizedValue);\n }\n }\n onBlur?.();\n };\n\n return (\n <FieldFrame inputId={inputId} label={label} required={Boolean(textFieldProps.required)}>\n <InputField\n {...textFieldProps}\n name={name}\n id={inputId}\n inputRef={inputRef}\n value={value ?? ''}\n onChange={(event) => onChange(uppercase ? event.target.value.toUpperCase() : event.target.value)}\n onBlur={handleBlur}\n type={isPassword && isRevealed ? 'text' : type}\n ownSx={CLEAR_BUTTON_SX}\n sx={textFieldProps.sx}\n error={Boolean(errorText)}\n helperText={errorText ?? helperText}\n fullWidth\n size=\"small\"\n // OJO: lo que el proyecto pasó en slotProps (htmlInput: {maxLength}) se suma a los adornos de la casa. Antes se\n // pisaba entero y el tope de caracteres de una pantalla se perdía sin avisar\n slotProps={{\n ...textFieldProps.slotProps,\n input: {\n ...textFieldProps.slotProps?.input,\n startAdornment: startAdornment ? <InputAdornment position=\"start\">{startAdornment}</InputAdornment> : undefined,\n endAdornment,\n },\n }}\n />\n </FieldFrame>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -119,6 +119,7 @@ function TransferListInputContent(props) {
|
|
|
119
119
|
error,
|
|
120
120
|
helperText = " ",
|
|
121
121
|
disabled = false,
|
|
122
|
+
required = false,
|
|
122
123
|
sx,
|
|
123
124
|
messages,
|
|
124
125
|
texts,
|
|
@@ -209,7 +210,7 @@ function TransferListInputContent(props) {
|
|
|
209
210
|
lists,
|
|
210
211
|
/* @__PURE__ */ jsx(FormHelperText, { error: Boolean(errorText), role: errorText ? "alert" : void 0, children: errorText ?? helperText })
|
|
211
212
|
] });
|
|
212
|
-
return label ? /* @__PURE__ */ jsx(FieldFrame, { inputId, label, children: field }) : field;
|
|
213
|
+
return label ? /* @__PURE__ */ jsx(FieldFrame, { inputId, label, required, children: field }) : field;
|
|
213
214
|
}
|
|
214
215
|
const TransferListInput = withPieceSlots(TransferListInputContent, THEME_NAME);
|
|
215
216
|
export {
|