@stackcraftmx/calcita 0.32.1 → 0.47.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +263 -2
- package/cli/packageVersions.js +12 -3
- package/cli/projectFiles.js +5 -2
- package/cli/templates/module/__Name__Page.test.jsx.tpl +24 -0
- package/cli/templates/project/README.md.tpl +14 -1
- package/cli/templates/project/src/app/appRouter.jsx.tpl +5 -2
- package/cli/templates/project/src/core/accountConnector.js.tpl +4 -2
- package/cli/templates/project/src/modules/auth/SignInPage.test.jsx.tpl +20 -0
- package/cli/templates/project/src/modules/home/HomePage.test.jsx.tpl +16 -0
- package/cli/templates/project/src/testing/browserGaps.js.tpl +70 -0
- package/cli/templates/project/src/testing/openScreen.jsx.tpl +58 -0
- package/cli/templates/project/src/testing/setup.js.tpl +25 -0
- package/cli/templates/project/vite.config.js.tpl +12 -0
- package/dist/DataTable/GoToPage.js +69 -13
- package/dist/DataTable/GoToPage.js.map +1 -1
- package/dist/DataTable/ServerTable.js +2 -0
- package/dist/DataTable/ServerTable.js.map +1 -1
- package/dist/DataTable/ServerTableFilterDrawer.js +52 -0
- package/dist/DataTable/ServerTableFilterDrawer.js.map +1 -0
- package/dist/DataTable/filterContext.js.map +1 -1
- package/dist/DataTable/goToPageOptions.js +17 -0
- package/dist/DataTable/goToPageOptions.js.map +1 -0
- package/dist/date-pickers.js +28 -2
- package/dist/date-pickers.js.map +1 -1
- package/dist/datePickers/DateInput.js +15 -34
- package/dist/datePickers/DateInput.js.map +1 -1
- package/dist/datePickers/DateTimeInput.js +75 -0
- package/dist/datePickers/DateTimeInput.js.map +1 -0
- package/dist/datePickers/FormDateTimeField.js +12 -0
- package/dist/datePickers/FormDateTimeField.js.map +1 -0
- package/dist/datePickers/FormMonthField.js +12 -0
- package/dist/datePickers/FormMonthField.js.map +1 -0
- package/dist/datePickers/FormTimeField.js +12 -0
- package/dist/datePickers/FormTimeField.js.map +1 -0
- package/dist/datePickers/MonthInput.js +58 -0
- package/dist/datePickers/MonthInput.js.map +1 -0
- package/dist/datePickers/PickerFrame.js +27 -0
- package/dist/datePickers/PickerFrame.js.map +1 -0
- package/dist/datePickers/TimeInput.js +55 -0
- package/dist/datePickers/TimeInput.js.map +1 -0
- package/dist/datePickers/dateTimeRules.js +24 -0
- package/dist/datePickers/dateTimeRules.js.map +1 -0
- package/dist/datePickers/pickerValues.js +106 -0
- package/dist/datePickers/pickerValues.js.map +1 -0
- package/dist/datePickers/useCurrentMinute.js +28 -0
- package/dist/datePickers/useCurrentMinute.js.map +1 -0
- package/dist/datePickers/usePickerField.js +22 -0
- package/dist/datePickers/usePickerField.js.map +1 -0
- package/dist/dictionaries/dictionaries.js +3 -1
- package/dist/dictionaries/dictionaries.js.map +1 -1
- package/dist/forms/FormAttachmentsField.js +12 -0
- package/dist/forms/FormAttachmentsField.js.map +1 -0
- package/dist/forms/FormCheckboxGroupField.js +12 -0
- package/dist/forms/FormCheckboxGroupField.js.map +1 -0
- package/dist/forms/FormFileField.js +12 -0
- package/dist/forms/FormFileField.js.map +1 -0
- package/dist/forms/FormFileUploadField.js +12 -0
- package/dist/forms/FormFileUploadField.js.map +1 -0
- package/dist/forms/FormImageField.js +12 -0
- package/dist/forms/FormImageField.js.map +1 -0
- package/dist/forms/FormMaskedField.js +12 -0
- package/dist/forms/FormMaskedField.js.map +1 -0
- package/dist/forms/FormRepeatingFields.js +54 -0
- package/dist/forms/FormRepeatingFields.js.map +1 -0
- package/dist/forms/FormTransferListField.js +12 -0
- package/dist/forms/FormTransferListField.js.map +1 -0
- package/dist/forms/applyServerProblem.js +10 -3
- package/dist/forms/applyServerProblem.js.map +1 -1
- package/dist/forms/listRules.js +111 -0
- package/dist/forms/listRules.js.map +1 -0
- package/dist/forms/serverProblemFields.js +17 -0
- package/dist/forms/serverProblemFields.js.map +1 -1
- package/dist/forms.js +51 -29
- package/dist/forms.js.map +1 -1
- package/dist/hookForm/FormAttachmentsField.js +12 -0
- package/dist/hookForm/FormAttachmentsField.js.map +1 -0
- package/dist/hookForm/FormCheckboxGroupField.js +12 -0
- package/dist/hookForm/FormCheckboxGroupField.js.map +1 -0
- package/dist/hookForm/FormFileField.js +12 -0
- package/dist/hookForm/FormFileField.js.map +1 -0
- package/dist/hookForm/FormFileUploadField.js +12 -0
- package/dist/hookForm/FormFileUploadField.js.map +1 -0
- package/dist/hookForm/FormImageField.js +12 -0
- package/dist/hookForm/FormImageField.js.map +1 -0
- package/dist/hookForm/FormMaskedField.js +12 -0
- package/dist/hookForm/FormMaskedField.js.map +1 -0
- package/dist/hookForm/FormRepeatingFields.js +44 -0
- package/dist/hookForm/FormRepeatingFields.js.map +1 -0
- package/dist/hookForm/FormTransferListField.js +12 -0
- package/dist/hookForm/FormTransferListField.js.map +1 -0
- package/dist/hookForm/applyServerProblem.js +8 -3
- package/dist/hookForm/applyServerProblem.js.map +1 -1
- package/dist/index.js +188 -100
- package/dist/index.js.map +1 -1
- package/dist/inputs/CatalogInput.js +74 -7
- package/dist/inputs/CatalogInput.js.map +1 -1
- package/dist/inputs/CheckboxGroupInput.js +100 -0
- package/dist/inputs/CheckboxGroupInput.js.map +1 -0
- package/dist/inputs/MaskedInput.js +86 -0
- package/dist/inputs/MaskedInput.js.map +1 -0
- package/dist/inputs/NumberInput.js +12 -1
- package/dist/inputs/NumberInput.js.map +1 -1
- package/dist/inputs/RepeatingFields.js +177 -0
- package/dist/inputs/RepeatingFields.js.map +1 -0
- package/dist/inputs/SelectInput.js +43 -5
- package/dist/inputs/SelectInput.js.map +1 -1
- package/dist/inputs/TransferListInput.js +215 -0
- package/dist/inputs/TransferListInput.js.map +1 -0
- package/dist/inputs/inputMasks.js +84 -0
- package/dist/inputs/inputMasks.js.map +1 -0
- package/dist/inputs/selectAllState.js +20 -0
- package/dist/inputs/selectAllState.js.map +1 -0
- package/dist/inputs/useRowKeys.js +26 -0
- package/dist/inputs/useRowKeys.js.map +1 -0
- package/dist/legal/CookieNotice.js +79 -0
- package/dist/legal/CookieNotice.js.map +1 -0
- package/dist/legal/TermsAcceptanceDialog.js +129 -0
- package/dist/legal/TermsAcceptanceDialog.js.map +1 -0
- package/dist/legal/consentStorage.js +18 -0
- package/dist/legal/consentStorage.js.map +1 -0
- package/dist/legal/useCookieConsent.js +17 -0
- package/dist/legal/useCookieConsent.js.map +1 -0
- package/dist/locales/en.js +233 -1
- package/dist/locales/en.js.map +1 -1
- package/dist/locales/es.js +233 -1
- package/dist/locales/es.js.map +1 -1
- package/dist/mexico/CfdiSummary.js +81 -0
- package/dist/mexico/CfdiSummary.js.map +1 -0
- package/dist/mexico/EfirmaFields.js +167 -0
- package/dist/mexico/EfirmaFields.js.map +1 -0
- package/dist/mexico/certificateReader.js +79 -0
- package/dist/mexico/certificateReader.js.map +1 -0
- package/dist/mexico/cfdiReader.js +81 -0
- package/dist/mexico/cfdiReader.js.map +1 -0
- package/dist/mexico/derReader.js +81 -0
- package/dist/mexico/derReader.js.map +1 -0
- package/dist/mexico/efirmaKey.js +51 -0
- package/dist/mexico/efirmaKey.js.map +1 -0
- package/dist/mexico/mexicoIdentifiers.js +86 -0
- package/dist/mexico/mexicoIdentifiers.js.map +1 -0
- package/dist/mexico/mexicoRules.js +16 -0
- package/dist/mexico/mexicoRules.js.map +1 -0
- package/dist/mexico.js +32 -0
- package/dist/mexico.js.map +1 -0
- package/dist/react-hook-form.js +40 -17
- package/dist/react-hook-form.js.map +1 -1
- package/dist/rich-text.js +19 -0
- package/dist/rich-text.js.map +1 -0
- package/dist/richText/FormRichTextField.js +12 -0
- package/dist/richText/FormRichTextField.js.map +1 -0
- package/dist/richText/RichTextEditor.js +108 -0
- package/dist/richText/RichTextEditor.js.map +1 -0
- package/dist/richText/RichTextLinkPopover.js +57 -0
- package/dist/richText/RichTextLinkPopover.js.map +1 -0
- package/dist/richText/RichTextToolbar.js +189 -0
- package/dist/richText/RichTextToolbar.js.map +1 -0
- package/dist/richText/RichTextView.js +26 -0
- package/dist/richText/RichTextView.js.map +1 -0
- package/dist/richText/richTextContentSx.js +42 -0
- package/dist/richText/richTextContentSx.js.map +1 -0
- package/dist/richText/richTextExtensions.js +44 -0
- package/dist/richText/richTextExtensions.js.map +1 -0
- package/dist/richText/richTextRules.js +46 -0
- package/dist/richText/richTextRules.js.map +1 -0
- package/dist/security/SessionsCard.js +11 -7
- package/dist/security/SessionsCard.js.map +1 -1
- package/dist/table.js +2 -0
- package/dist/table.js.map +1 -1
- package/dist/theme/components/glassComponents.js +27 -0
- package/dist/theme/components/glassComponents.js.map +1 -1
- package/dist/theme/components/minimalComponents.js +12 -0
- package/dist/theme/components/minimalComponents.js.map +1 -1
- package/dist/types/DataTable/ServerTableFilterDrawer.d.ts +21 -0
- package/dist/types/DataTable/filterContext.d.ts +2 -1
- package/dist/types/DataTable/goToPageOptions.d.ts +15 -0
- package/dist/types/DataTable/index.d.ts +1 -0
- package/dist/types/date-pickers.d.ts +10 -1
- package/dist/types/datePickers/DateInput.d.ts +6 -4
- package/dist/types/datePickers/DateTimeInput.d.ts +50 -0
- package/dist/types/datePickers/FormDateTimeField.d.ts +7 -0
- package/dist/types/datePickers/FormMonthField.d.ts +7 -0
- package/dist/types/datePickers/FormTimeField.d.ts +7 -0
- package/dist/types/datePickers/MonthInput.d.ts +39 -0
- package/dist/types/datePickers/PickerFrame.d.ts +22 -0
- package/dist/types/datePickers/TimeInput.d.ts +38 -0
- package/dist/types/datePickers/dateTimeRules.d.ts +31 -0
- package/dist/types/datePickers/pickerValues.d.ts +114 -0
- package/dist/types/datePickers/useCurrentMinute.d.ts +5 -0
- package/dist/types/datePickers/usePickerField.d.ts +24 -0
- package/dist/types/dictionaries/dictionaries.d.ts +6 -3
- package/dist/types/forms/FormAttachmentsField.d.ts +7 -0
- package/dist/types/forms/FormCheckboxGroupField.d.ts +7 -0
- package/dist/types/forms/FormFileField.d.ts +7 -0
- package/dist/types/forms/FormFileUploadField.d.ts +7 -0
- package/dist/types/forms/FormImageField.d.ts +7 -0
- package/dist/types/forms/FormMaskedField.d.ts +7 -0
- package/dist/types/forms/FormRepeatingFields.d.ts +25 -0
- package/dist/types/forms/FormTransferListField.d.ts +7 -0
- package/dist/types/forms/index.d.ts +10 -1
- package/dist/types/forms/listRules.d.ts +48 -0
- package/dist/types/forms/serverProblemFields.d.ts +13 -0
- package/dist/types/forms.d.ts +1 -1
- package/dist/types/hookForm/FormAttachmentsField.d.ts +8 -0
- package/dist/types/hookForm/FormCheckboxGroupField.d.ts +8 -0
- package/dist/types/hookForm/FormFileField.d.ts +8 -0
- package/dist/types/hookForm/FormFileUploadField.d.ts +8 -0
- package/dist/types/hookForm/FormImageField.d.ts +8 -0
- package/dist/types/hookForm/FormMaskedField.d.ts +9 -0
- package/dist/types/hookForm/FormRepeatingFields.d.ts +27 -0
- package/dist/types/hookForm/FormTransferListField.d.ts +8 -0
- package/dist/types/index.d.ts +25 -3
- package/dist/types/inputs/CatalogInput.d.ts +7 -1
- package/dist/types/inputs/CheckboxGroupInput.d.ts +39 -0
- package/dist/types/inputs/MaskedInput.d.ts +35 -0
- package/dist/types/inputs/RepeatingFields.d.ts +52 -0
- package/dist/types/inputs/SelectInput.d.ts +6 -1
- package/dist/types/inputs/TransferListInput.d.ts +43 -0
- package/dist/types/inputs/inputMasks.d.ts +52 -0
- package/dist/types/inputs/selectAllState.d.ts +11 -0
- package/dist/types/inputs/useRowKeys.d.ts +11 -0
- package/dist/types/legal/CookieNotice.d.ts +30 -0
- package/dist/types/legal/TermsAcceptanceDialog.d.ts +32 -0
- package/dist/types/legal/consentStorage.d.ts +16 -0
- package/dist/types/legal/useCookieConsent.d.ts +13 -0
- package/dist/types/locales/en.d.ts +232 -0
- package/dist/types/locales/es.d.ts +232 -0
- package/dist/types/mexico/CfdiSummary.d.ts +17 -0
- package/dist/types/mexico/EfirmaFields.d.ts +38 -0
- package/dist/types/mexico/certificateReader.d.ts +37 -0
- package/dist/types/mexico/cfdiReader.d.ts +95 -0
- package/dist/types/mexico/derReader.d.ts +54 -0
- package/dist/types/mexico/efirmaKey.d.ts +26 -0
- package/dist/types/mexico/mexicoIdentifiers.d.ts +61 -0
- package/dist/types/mexico/mexicoRules.d.ts +29 -0
- package/dist/types/mexico.d.ts +11 -0
- package/dist/types/react-hook-form.d.ts +10 -1
- package/dist/types/rich-text.d.ts +5 -0
- package/dist/types/richText/FormRichTextField.d.ts +7 -0
- package/dist/types/richText/RichTextEditor.d.ts +42 -0
- package/dist/types/richText/RichTextLinkPopover.d.ts +17 -0
- package/dist/types/richText/RichTextToolbar.d.ts +15 -0
- package/dist/types/richText/RichTextView.d.ts +13 -0
- package/dist/types/richText/richTextContentSx.d.ts +7 -0
- package/dist/types/richText/richTextExtensions.d.ts +15 -0
- package/dist/types/richText/richTextRules.d.ts +41 -0
- package/dist/types/table.d.ts +1 -1
- package/dist/types/ui/actions/ActionsMenu.d.ts +18 -3
- package/dist/types/ui/actions/CopyButton.d.ts +7 -4
- package/dist/types/ui/actions/tableRowText.d.ts +6 -0
- package/dist/types/ui/cards/RecordCard.d.ts +65 -0
- package/dist/types/ui/cards/RecordCardSkeleton.d.ts +17 -0
- package/dist/types/ui/countdown/Countdown.d.ts +31 -0
- package/dist/types/ui/countdown/countdownParts.d.ts +39 -0
- package/dist/types/ui/countdown/useCountdown.d.ts +14 -0
- package/dist/types/ui/documents/PdfViewerDialog.d.ts +21 -0
- package/dist/types/ui/filters/FilterDrawer.d.ts +34 -0
- package/dist/types/ui/filters/filterDrawerFields.d.ts +139 -0
- package/dist/types/ui/help/HelpButton.d.ts +15 -0
- package/dist/types/ui/help/HelpCarouselDialog.d.ts +35 -0
- package/dist/types/ui/help/useHelpCarousel.d.ts +21 -0
- package/dist/types/ui/index.d.ts +10 -0
- package/dist/types/ui/labels/IconBadge.d.ts +34 -0
- package/dist/types/ui/navigation/RouteTabs.d.ts +44 -0
- package/dist/types/ui/navigation/routeTabMatching.d.ts +27 -0
- package/dist/types/upload/AttachmentsInput.d.ts +62 -0
- package/dist/types/upload/FileChip.d.ts +18 -0
- package/dist/types/upload/FileDropzone.d.ts +3 -0
- package/dist/types/upload/FileInput.d.ts +48 -0
- package/dist/types/upload/FileUploadInput.d.ts +80 -0
- package/dist/types/upload/ImageInput.d.ts +53 -0
- package/dist/types/upload/TemplateDownloadButton.d.ts +39 -0
- package/dist/types/upload/fileAppearance.d.ts +8 -0
- package/dist/types/upload/fileRules.d.ts +38 -0
- package/dist/types/upload/transferProgress.d.ts +18 -0
- package/dist/types/upload/uploadMachine.d.ts +6 -2
- package/dist/types/upload/uploadWithProgress.d.ts +6 -2
- package/dist/types/upload/useFilePicker.d.ts +39 -0
- package/dist/ui/actions/ActionsMenu.js +98 -15
- package/dist/ui/actions/ActionsMenu.js.map +1 -1
- package/dist/ui/actions/CopyButton.js +31 -11
- package/dist/ui/actions/CopyButton.js.map +1 -1
- package/dist/ui/actions/tableRowText.js +7 -0
- package/dist/ui/actions/tableRowText.js.map +1 -0
- package/dist/ui/cards/RecordCard.js +145 -0
- package/dist/ui/cards/RecordCard.js.map +1 -0
- package/dist/ui/cards/RecordCardSkeleton.js +33 -0
- package/dist/ui/cards/RecordCardSkeleton.js.map +1 -0
- package/dist/ui/countdown/Countdown.js +38 -0
- package/dist/ui/countdown/Countdown.js.map +1 -0
- package/dist/ui/countdown/countdownParts.js +34 -0
- package/dist/ui/countdown/countdownParts.js.map +1 -0
- package/dist/ui/countdown/useCountdown.js +35 -0
- package/dist/ui/countdown/useCountdown.js.map +1 -0
- package/dist/ui/documents/PdfViewerDialog.js +81 -0
- package/dist/ui/documents/PdfViewerDialog.js.map +1 -0
- package/dist/ui/filters/FilterDrawer.js +211 -0
- package/dist/ui/filters/FilterDrawer.js.map +1 -0
- package/dist/ui/filters/filterDrawerFields.js +119 -0
- package/dist/ui/filters/filterDrawerFields.js.map +1 -0
- package/dist/ui/help/HelpButton.js +17 -0
- package/dist/ui/help/HelpButton.js.map +1 -0
- package/dist/ui/help/HelpCarouselDialog.js +154 -0
- package/dist/ui/help/HelpCarouselDialog.js.map +1 -0
- package/dist/ui/help/useHelpCarousel.js +25 -0
- package/dist/ui/help/useHelpCarousel.js.map +1 -0
- package/dist/ui/labels/IconBadge.js +82 -0
- package/dist/ui/labels/IconBadge.js.map +1 -0
- package/dist/ui/navigation/RouteTabs.js +75 -0
- package/dist/ui/navigation/RouteTabs.js.map +1 -0
- package/dist/ui/navigation/routeTabMatching.js +23 -0
- package/dist/ui/navigation/routeTabMatching.js.map +1 -0
- package/dist/upload/AttachmentsInput.js +127 -0
- package/dist/upload/AttachmentsInput.js.map +1 -0
- package/dist/upload/FileChip.js +27 -0
- package/dist/upload/FileChip.js.map +1 -0
- package/dist/upload/FileDropzone.js +19 -84
- package/dist/upload/FileDropzone.js.map +1 -1
- package/dist/upload/FileInput.js +112 -0
- package/dist/upload/FileInput.js.map +1 -0
- package/dist/upload/FileUploadInput.js +285 -0
- package/dist/upload/FileUploadInput.js.map +1 -0
- package/dist/upload/ImageInput.js +125 -0
- package/dist/upload/ImageInput.js.map +1 -0
- package/dist/upload/TemplateDownloadButton.js +67 -0
- package/dist/upload/TemplateDownloadButton.js.map +1 -0
- package/dist/upload/fileAppearance.js +21 -0
- package/dist/upload/fileAppearance.js.map +1 -0
- package/dist/upload/fileRules.js +27 -1
- package/dist/upload/fileRules.js.map +1 -1
- package/dist/upload/transferProgress.js +23 -0
- package/dist/upload/transferProgress.js.map +1 -0
- package/dist/upload/uploadMachine.js +8 -8
- package/dist/upload/uploadMachine.js.map +1 -1
- package/dist/upload/uploadWithProgress.js +1 -1
- package/dist/upload/uploadWithProgress.js.map +1 -1
- package/dist/upload/useFilePicker.js +101 -0
- package/dist/upload/useFilePicker.js.map +1 -0
- package/package.json +44 -1
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import dayjs from "dayjs";
|
|
2
|
+
import timezone from "dayjs/plugin/timezone.js";
|
|
3
|
+
import utc from "dayjs/plugin/utc.js";
|
|
4
|
+
const CALENDAR_DATE_FORMAT = "YYYY-MM-DD";
|
|
5
|
+
const CALENDAR_MONTH_FORMAT = "YYYY-MM";
|
|
6
|
+
const TIME_OF_DAY_FORMAT = "HH:mm";
|
|
7
|
+
const INSTANT_FORMAT = "YYYY-MM-DDTHH:mm:ss[Z]";
|
|
8
|
+
const DEFAULT_MINUTES_STEP = 1;
|
|
9
|
+
const MILLISECONDS_PER_MINUTE = 6e4;
|
|
10
|
+
const TIME_OF_DAY_REFERENCE_DATE = "2000-01-03";
|
|
11
|
+
const CALENDAR_MONTH_PATTERN = /^\d{4}-\d{2}$/;
|
|
12
|
+
const TIME_OF_DAY_PATTERN = /^\d{2}:\d{2}$/;
|
|
13
|
+
const DISPLAY_FORMATS_BY_LANGUAGE = Object.freeze({
|
|
14
|
+
es: { date: "DD/MM/YYYY", month: "MMMM YYYY" },
|
|
15
|
+
en: { date: "MM/DD/YYYY", month: "MMMM YYYY" }
|
|
16
|
+
});
|
|
17
|
+
const TIME_DISPLAY_FORMATS = Object.freeze({ h23: "HH:mm", h12: "hh:mm a" });
|
|
18
|
+
function zonedDayjs() {
|
|
19
|
+
dayjs.extend(utc);
|
|
20
|
+
dayjs.extend(timezone);
|
|
21
|
+
return dayjs;
|
|
22
|
+
}
|
|
23
|
+
function displayFormatsOf(language, hourCycle = "h23") {
|
|
24
|
+
const formats = DISPLAY_FORMATS_BY_LANGUAGE[language] ?? DISPLAY_FORMATS_BY_LANGUAGE.es;
|
|
25
|
+
const time = TIME_DISPLAY_FORMATS[hourCycle] ?? TIME_DISPLAY_FORMATS.h23;
|
|
26
|
+
return { ...formats, time, dateTime: `${formats.date} ${time}`, ampm: hourCycle === "h12" };
|
|
27
|
+
}
|
|
28
|
+
function timeZoneNameOf(timeZone, intlLocale, at = /* @__PURE__ */ new Date()) {
|
|
29
|
+
try {
|
|
30
|
+
const name = new Intl.DateTimeFormat(intlLocale, { timeZone, timeZoneName: "longGeneric" }).formatToParts(at).find((part) => part.type === "timeZoneName")?.value;
|
|
31
|
+
return name ? name.charAt(0).toLocaleUpperCase(intlLocale) + name.slice(1) : timeZone;
|
|
32
|
+
} catch {
|
|
33
|
+
return timeZone;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function pickerInstantOf(instantText, timeZone) {
|
|
37
|
+
const zoned = zonedDayjs();
|
|
38
|
+
if (!instantText) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
const instant = zoned.utc(instantText);
|
|
42
|
+
return instant.isValid() ? instant.tz(timeZone) : null;
|
|
43
|
+
}
|
|
44
|
+
function instantTextOf(pickedValue) {
|
|
45
|
+
return pickedValue?.isValid() ? pickedValue.utc().format(INSTANT_FORMAT) : "";
|
|
46
|
+
}
|
|
47
|
+
function earliestInstantFromNow(minutes, now = Date.now()) {
|
|
48
|
+
const nowMilliseconds = now instanceof Date ? now.getTime() : now;
|
|
49
|
+
const startOfMinute = Math.floor(nowMilliseconds / MILLISECONDS_PER_MINUTE) * MILLISECONDS_PER_MINUTE;
|
|
50
|
+
return instantTextOf(zonedDayjs()(startOfMinute + minutes * MILLISECONDS_PER_MINUTE));
|
|
51
|
+
}
|
|
52
|
+
function instantPlusMinutes(instantText, minutes) {
|
|
53
|
+
if (!instantText) {
|
|
54
|
+
return "";
|
|
55
|
+
}
|
|
56
|
+
const instant = zonedDayjs()(instantText);
|
|
57
|
+
return instant.isValid() ? instantTextOf(instant.add(minutes, "minute")) : "";
|
|
58
|
+
}
|
|
59
|
+
function latestInstantOf(...instantTexts) {
|
|
60
|
+
const present = instantTexts.filter(Boolean);
|
|
61
|
+
if (present.length === 0) {
|
|
62
|
+
return "";
|
|
63
|
+
}
|
|
64
|
+
return present.reduce((latest, candidate) => Date.parse(candidate) > Date.parse(latest) ? candidate : latest);
|
|
65
|
+
}
|
|
66
|
+
function pickerTimeOf(timeText) {
|
|
67
|
+
if (!timeText || !TIME_OF_DAY_PATTERN.test(timeText)) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
const time = dayjs(`${TIME_OF_DAY_REFERENCE_DATE}T${timeText}`);
|
|
71
|
+
return time.isValid() ? time : null;
|
|
72
|
+
}
|
|
73
|
+
function timeTextOf(pickedValue) {
|
|
74
|
+
return pickedValue?.isValid() ? pickedValue.format(TIME_OF_DAY_FORMAT) : "";
|
|
75
|
+
}
|
|
76
|
+
function pickerMonthOf(monthText) {
|
|
77
|
+
if (!monthText || !CALENDAR_MONTH_PATTERN.test(monthText)) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
const month = dayjs(`${monthText}-01`);
|
|
81
|
+
return month.isValid() ? month : null;
|
|
82
|
+
}
|
|
83
|
+
function monthTextOf(pickedValue) {
|
|
84
|
+
return pickedValue?.isValid() ? pickedValue.format(CALENDAR_MONTH_FORMAT) : "";
|
|
85
|
+
}
|
|
86
|
+
export {
|
|
87
|
+
CALENDAR_DATE_FORMAT,
|
|
88
|
+
CALENDAR_MONTH_FORMAT,
|
|
89
|
+
DEFAULT_MINUTES_STEP,
|
|
90
|
+
INSTANT_FORMAT,
|
|
91
|
+
MILLISECONDS_PER_MINUTE,
|
|
92
|
+
TIME_OF_DAY_FORMAT,
|
|
93
|
+
displayFormatsOf,
|
|
94
|
+
earliestInstantFromNow,
|
|
95
|
+
instantPlusMinutes,
|
|
96
|
+
instantTextOf,
|
|
97
|
+
latestInstantOf,
|
|
98
|
+
monthTextOf,
|
|
99
|
+
pickerInstantOf,
|
|
100
|
+
pickerMonthOf,
|
|
101
|
+
pickerTimeOf,
|
|
102
|
+
timeTextOf,
|
|
103
|
+
timeZoneNameOf,
|
|
104
|
+
zonedDayjs
|
|
105
|
+
};
|
|
106
|
+
//# sourceMappingURL=pickerValues.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pickerValues.js","sources":["../../src/datePickers/pickerValues.js"],"sourcesContent":["// Lo que comparten los campos de fecha, fecha con hora, hora y mes (@stackcraftmx/calcita/date-pickers): cómo viaja cada\n// valor, cómo se ve en cada idioma y cómo se nombra la zona horaria en pantalla.\n//\n// Cada campo viaja en texto, nunca como Date (que movería una fecha de día según la zona del navegador):\n// fecha 'AAAA-MM-DD' una fecha de calendario: no tiene hora ni zona\n// mes 'AAAA-MM' lo mismo, sin el día\n// hora 'HH:mm', de 24 horas una hora del día («abre a las 09:00»): tampoco tiene zona\n// fecha con hora '2026-11-01T18:30:00Z' un INSTANTE: viaja en UTC y se pinta en la zona del proveedor, rotulada\n// '' = vacío en los cuatro. Lo que llega del servidor con otra forma (un instante con -06:00) también se lee.\n//\n// Puro (sin React): se prueba con node (pickerValues.test.js).\n//\n// Genérico del paquete: function (regla de la casa).\nimport dayjs from 'dayjs';\nimport timezone from 'dayjs/plugin/timezone.js';\nimport utc from 'dayjs/plugin/utc.js';\n\n/** Cómo viaja una fecha de calendario. */\nexport const CALENDAR_DATE_FORMAT = 'YYYY-MM-DD';\n/** Cómo viaja un mes. */\nexport const CALENDAR_MONTH_FORMAT = 'YYYY-MM';\n/** Cómo viaja una hora del día (de 24 horas, aunque en pantalla se vea con a.m. y p.m.). */\nexport const TIME_OF_DAY_FORMAT = 'HH:mm';\n/** Cómo viaja un instante: en UTC, con la Z al final. */\nexport const INSTANT_FORMAT = 'YYYY-MM-DDTHH:mm:ss[Z]';\n/** De cuánto en cuánto van los minutos en la fecha con hora y en la hora: de uno en uno (MUI X trae de 5 en 5). */\nexport const DEFAULT_MINUTES_STEP = 1;\n/** Un minuto. */\nexport const MILLISECONDS_PER_MINUTE = 60_000;\n\n// una hora del día no tiene fecha, pero el selector de MUI X trabaja con fechas: se le pega a un día cualquiera sin cambio\n// de horario de verano en ningún país\nconst TIME_OF_DAY_REFERENCE_DATE = '2000-01-03';\nconst CALENDAR_MONTH_PATTERN = /^\\d{4}-\\d{2}$/;\nconst TIME_OF_DAY_PATTERN = /^\\d{2}:\\d{2}$/;\n\n// cómo se ve cada campo en pantalla según el idioma; el que no está aquí sale como el español (como DateInput desde antes)\nconst DISPLAY_FORMATS_BY_LANGUAGE = Object.freeze({\n es: {date: 'DD/MM/YYYY', month: 'MMMM YYYY'},\n en: {date: 'MM/DD/YYYY', month: 'MMMM YYYY'},\n});\n// la hora en pantalla según el hourCycle del proveedor: 15:10 o 03:10 p. m.\nconst TIME_DISPLAY_FORMATS = Object.freeze({h23: 'HH:mm', h12: 'hh:mm a'});\n\n/**\n * dayjs con las zonas horarias puestas: el selector de MUI X las necesita para pintar un instante en otra zona.\n * OJO: se llama al usarla y no al importar el archivo: el paquete se declara sin efectos al importar (sideEffects: false)\n * y un import solo por su efecto se lo puede saltar el empaquetador del proyecto.\n *\n * @returns {typeof dayjs}\n */\nexport function zonedDayjs() {\n // extend no repite: cada complemento se instala una sola vez\n dayjs.extend(utc);\n dayjs.extend(timezone);\n return dayjs;\n}\n\n/**\n * Cómo se ven en pantalla los cuatro campos.\n *\n * @param {string} language el del proveedor más cercano\n * @param {'h23'|'h12'} [hourCycle] el del proveedor; por omisión de 24 horas\n * @returns {{date: string, month: string, time: string, dateTime: string, ampm: boolean}}\n */\nexport function displayFormatsOf(language, hourCycle = 'h23') {\n const formats = DISPLAY_FORMATS_BY_LANGUAGE[language] ?? DISPLAY_FORMATS_BY_LANGUAGE.es;\n const time = TIME_DISPLAY_FORMATS[hourCycle] ?? TIME_DISPLAY_FORMATS.h23;\n return {...formats, time, dateTime: `${formats.date} ${time}`, ampm: hourCycle === 'h12'};\n}\n\n/**\n * El nombre de la zona horaria como lo dice una persona («Hora estándar central», «Pacific Time»), para rotular la fecha\n * con hora. Si el navegador no sabe nombrarla, la zona tal cual (America/Mexico_City).\n *\n * @param {string} timeZone la zona IANA del proveedor\n * @param {string} intlLocale es-MX, en-US…\n * @param {Date} [at] el momento que se nombra (en las zonas con horario de verano el nombre puede cambiar)\n * @returns {string}\n */\nexport function timeZoneNameOf(timeZone, intlLocale, at = new Date()) {\n try {\n const name = new Intl.DateTimeFormat(intlLocale, {timeZone, timeZoneName: 'longGeneric'})\n .formatToParts(at)\n .find((part) => part.type === 'timeZoneName')?.value;\n return name ? name.charAt(0).toLocaleUpperCase(intlLocale) + name.slice(1) : timeZone;\n } catch {\n return timeZone;\n }\n}\n\n/**\n * Un instante como lo quiere el selector, en la zona en que se pinta; null si está vacío o no se entiende.\n *\n * @param {string|null|undefined} instantText '2026-11-01T18:30:00Z' o con su desfase ('…-06:00')\n * @param {string} timeZone la zona en que se pinta\n * @returns {import('dayjs').Dayjs|null}\n */\nexport function pickerInstantOf(instantText, timeZone) {\n // OJO: las zonas se ponen ANTES de ver si está vacío. El selector con timezone las pide al pintarse aunque no tenga\n // valor («Missing UTC plugin»): un «Posponer hasta…» vacío tumbaba la pantalla\n const zoned = zonedDayjs();\n if (!instantText) {\n return null;\n }\n const instant = zoned.utc(instantText);\n return instant.isValid() ? instant.tz(timeZone) : null;\n}\n\n/**\n * Lo que eligió la persona como instante en UTC; '' si está vacío o a medias.\n *\n * @param {import('dayjs').Dayjs|null} pickedValue\n * @returns {string}\n */\nexport function instantTextOf(pickedValue) {\n return pickedValue?.isValid() ? pickedValue.utc().format(INSTANT_FORMAT) : '';\n}\n\n/**\n * El primer instante que se puede elegir cuando la regla es «al menos N minutos después de ahora» (publicar algo con\n * anticipación): este minuto, sin segundos, más N minutos. Sin segundos para que la regla no cambie entre que la persona\n * mira la hora y la elige.\n *\n * @param {number} minutes\n * @param {Date|number} [now]\n * @returns {string} el instante en UTC ('2026-11-02T18:35:00Z')\n */\nexport function earliestInstantFromNow(minutes, now = Date.now()) {\n const nowMilliseconds = now instanceof Date ? now.getTime() : now;\n const startOfMinute = Math.floor(nowMilliseconds / MILLISECONDS_PER_MINUTE) * MILLISECONDS_PER_MINUTE;\n return instantTextOf(zonedDayjs()(startOfMinute + minutes * MILLISECONDS_PER_MINUTE));\n}\n\n/**\n * Un instante más N minutos (el fin de algo que dura al menos N minutos desde su inicio). '' si el instante viene vacío\n * o no se entiende.\n *\n * @param {string|null|undefined} instantText\n * @param {number} minutes\n * @returns {string}\n */\nexport function instantPlusMinutes(instantText, minutes) {\n if (!instantText) {\n return '';\n }\n const instant = zonedDayjs()(instantText);\n return instant.isValid() ? instantTextOf(instant.add(minutes, 'minute')) : '';\n}\n\n/**\n * El más tarde de varios instantes (los vacíos no cuentan): el mínimo que de verdad aplica cuando hay más de una regla.\n *\n * @param {...(string|null|undefined)} instantTexts\n * @returns {string} '' si todos vienen vacíos\n */\nexport function latestInstantOf(...instantTexts) {\n const present = instantTexts.filter(Boolean);\n if (present.length === 0) {\n return '';\n }\n return present.reduce((latest, candidate) => (Date.parse(candidate) > Date.parse(latest) ? candidate : latest));\n}\n\n/**\n * Una hora del día 'HH:mm' como la quiere el selector; null si está vacía o no tiene esa forma.\n *\n * @param {string|null|undefined} timeText\n * @returns {import('dayjs').Dayjs|null}\n */\nexport function pickerTimeOf(timeText) {\n if (!timeText || !TIME_OF_DAY_PATTERN.test(timeText)) {\n return null;\n }\n const time = dayjs(`${TIME_OF_DAY_REFERENCE_DATE}T${timeText}`);\n return time.isValid() ? time : null;\n}\n\n/**\n * Lo que eligió la persona como 'HH:mm'; '' si está vacío o a medias.\n *\n * @param {import('dayjs').Dayjs|null} pickedValue\n * @returns {string}\n */\nexport function timeTextOf(pickedValue) {\n return pickedValue?.isValid() ? pickedValue.format(TIME_OF_DAY_FORMAT) : '';\n}\n\n/**\n * Un mes 'AAAA-MM' como lo quiere el selector (su día 1); null si está vacío o no tiene esa forma.\n *\n * @param {string|null|undefined} monthText\n * @returns {import('dayjs').Dayjs|null}\n */\nexport function pickerMonthOf(monthText) {\n if (!monthText || !CALENDAR_MONTH_PATTERN.test(monthText)) {\n return null;\n }\n // dayjs lee 'AAAA-MM-DD' como día local (new Date lo tomaría en UTC y lo movería de día)\n const month = dayjs(`${monthText}-01`);\n return month.isValid() ? month : null;\n}\n\n/**\n * Lo que eligió la persona como 'AAAA-MM'; '' si está vacío o a medias.\n *\n * @param {import('dayjs').Dayjs|null} pickedValue\n * @returns {string}\n */\nexport function monthTextOf(pickedValue) {\n return pickedValue?.isValid() ? pickedValue.format(CALENDAR_MONTH_FORMAT) : '';\n}\n"],"names":[],"mappings":";;;AAkBY,MAAC,uBAAuB;AAExB,MAAC,wBAAwB;AAEzB,MAAC,qBAAqB;AAEtB,MAAC,iBAAiB;AAElB,MAAC,uBAAuB;AAExB,MAAC,0BAA0B;AAIvC,MAAM,6BAA6B;AACnC,MAAM,yBAAyB;AAC/B,MAAM,sBAAsB;AAG5B,MAAM,8BAA8B,OAAO,OAAO;AAAA,EAC9C,IAAI,EAAC,MAAM,cAAc,OAAO,YAAW;AAAA,EAC3C,IAAI,EAAC,MAAM,cAAc,OAAO,YAAW;AAC/C,CAAC;AAED,MAAM,uBAAuB,OAAO,OAAO,EAAC,KAAK,SAAS,KAAK,UAAS,CAAC;AASlE,SAAS,aAAa;AAEzB,QAAM,OAAO,GAAG;AAChB,QAAM,OAAO,QAAQ;AACrB,SAAO;AACX;AASO,SAAS,iBAAiB,UAAU,YAAY,OAAO;AAC1D,QAAM,UAAU,4BAA4B,QAAQ,KAAK,4BAA4B;AACrF,QAAM,OAAO,qBAAqB,SAAS,KAAK,qBAAqB;AACrE,SAAO,EAAC,GAAG,SAAS,MAAM,UAAU,GAAG,QAAQ,IAAI,IAAI,IAAI,IAAI,MAAM,cAAc,MAAK;AAC5F;AAWO,SAAS,eAAe,UAAU,YAAY,KAAK,oBAAI,KAAI,GAAI;AAClE,MAAI;AACA,UAAM,OAAO,IAAI,KAAK,eAAe,YAAY,EAAC,UAAU,cAAc,cAAa,CAAC,EACnF,cAAc,EAAE,EAChB,KAAK,CAAC,SAAS,KAAK,SAAS,cAAc,GAAG;AACnD,WAAO,OAAO,KAAK,OAAO,CAAC,EAAE,kBAAkB,UAAU,IAAI,KAAK,MAAM,CAAC,IAAI;AAAA,EACjF,QAAQ;AACJ,WAAO;AAAA,EACX;AACJ;AASO,SAAS,gBAAgB,aAAa,UAAU;AAGnD,QAAM,QAAQ,WAAU;AACxB,MAAI,CAAC,aAAa;AACd,WAAO;AAAA,EACX;AACA,QAAM,UAAU,MAAM,IAAI,WAAW;AACrC,SAAO,QAAQ,QAAO,IAAK,QAAQ,GAAG,QAAQ,IAAI;AACtD;AAQO,SAAS,cAAc,aAAa;AACvC,SAAO,aAAa,QAAO,IAAK,YAAY,IAAG,EAAG,OAAO,cAAc,IAAI;AAC/E;AAWO,SAAS,uBAAuB,SAAS,MAAM,KAAK,IAAG,GAAI;AAC9D,QAAM,kBAAkB,eAAe,OAAO,IAAI,QAAO,IAAK;AAC9D,QAAM,gBAAgB,KAAK,MAAM,kBAAkB,uBAAuB,IAAI;AAC9E,SAAO,cAAc,WAAU,EAAG,gBAAgB,UAAU,uBAAuB,CAAC;AACxF;AAUO,SAAS,mBAAmB,aAAa,SAAS;AACrD,MAAI,CAAC,aAAa;AACd,WAAO;AAAA,EACX;AACA,QAAM,UAAU,WAAU,EAAG,WAAW;AACxC,SAAO,QAAQ,QAAO,IAAK,cAAc,QAAQ,IAAI,SAAS,QAAQ,CAAC,IAAI;AAC/E;AAQO,SAAS,mBAAmB,cAAc;AAC7C,QAAM,UAAU,aAAa,OAAO,OAAO;AAC3C,MAAI,QAAQ,WAAW,GAAG;AACtB,WAAO;AAAA,EACX;AACA,SAAO,QAAQ,OAAO,CAAC,QAAQ,cAAe,KAAK,MAAM,SAAS,IAAI,KAAK,MAAM,MAAM,IAAI,YAAY,MAAO;AAClH;AAQO,SAAS,aAAa,UAAU;AACnC,MAAI,CAAC,YAAY,CAAC,oBAAoB,KAAK,QAAQ,GAAG;AAClD,WAAO;AAAA,EACX;AACA,QAAM,OAAO,MAAM,GAAG,0BAA0B,IAAI,QAAQ,EAAE;AAC9D,SAAO,KAAK,YAAY,OAAO;AACnC;AAQO,SAAS,WAAW,aAAa;AACpC,SAAO,aAAa,QAAO,IAAK,YAAY,OAAO,kBAAkB,IAAI;AAC7E;AAQO,SAAS,cAAc,WAAW;AACrC,MAAI,CAAC,aAAa,CAAC,uBAAuB,KAAK,SAAS,GAAG;AACvD,WAAO;AAAA,EACX;AAEA,QAAM,QAAQ,MAAM,GAAG,SAAS,KAAK;AACrC,SAAO,MAAM,YAAY,QAAQ;AACrC;AAQO,SAAS,YAAY,aAAa;AACrC,SAAO,aAAa,QAAO,IAAK,YAAY,OAAO,qBAAqB,IAAI;AAChF;"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useState, useEffect } from "react";
|
|
3
|
+
import { MILLISECONDS_PER_MINUTE } from "./pickerValues.js";
|
|
4
|
+
const startOfMinuteOf = (milliseconds) => Math.floor(milliseconds / MILLISECONDS_PER_MINUTE) * MILLISECONDS_PER_MINUTE;
|
|
5
|
+
function useCurrentMinute(enabled) {
|
|
6
|
+
const [currentMinute, setCurrentMinute] = useState(() => startOfMinuteOf(Date.now()));
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
if (!enabled) {
|
|
9
|
+
return void 0;
|
|
10
|
+
}
|
|
11
|
+
let timer;
|
|
12
|
+
const waitForNextMinute = () => {
|
|
13
|
+
const untilNextMinute = MILLISECONDS_PER_MINUTE - Date.now() % MILLISECONDS_PER_MINUTE;
|
|
14
|
+
timer = setTimeout(() => {
|
|
15
|
+
setCurrentMinute(startOfMinuteOf(Date.now()));
|
|
16
|
+
waitForNextMinute();
|
|
17
|
+
}, untilNextMinute);
|
|
18
|
+
};
|
|
19
|
+
setCurrentMinute(startOfMinuteOf(Date.now()));
|
|
20
|
+
waitForNextMinute();
|
|
21
|
+
return () => clearTimeout(timer);
|
|
22
|
+
}, [enabled]);
|
|
23
|
+
return currentMinute;
|
|
24
|
+
}
|
|
25
|
+
export {
|
|
26
|
+
useCurrentMinute
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=useCurrentMinute.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useCurrentMinute.js","sources":["../../src/datePickers/useCurrentMinute.js"],"sourcesContent":["// El minuto de ahora, que se actualiza solo al empezar cada minuto: para una regla como «al menos 5 minutos después de\n// ahora», que se va recorriendo mientras la pantalla está abierta (si la persona deja el formulario abierto diez minutos,\n// el mínimo ya no es el de cuando lo abrió).\n//\n// Un solo temporizador, hasta el siguiente minuto en punto; se apaga al salir de la pantalla o si enabled es false.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useEffect, useState} from 'react';\n\nimport {MILLISECONDS_PER_MINUTE} from '@/datePickers/pickerValues.js';\n\nconst startOfMinuteOf = (milliseconds) => Math.floor(milliseconds / MILLISECONDS_PER_MINUTE) * MILLISECONDS_PER_MINUTE;\n\n/**\n * @param {boolean} enabled sin regla que dependa de la hora no hace falta el temporizador\n * @returns {number} el inicio del minuto de ahora, en milisegundos\n */\nexport function useCurrentMinute(enabled) {\n const [currentMinute, setCurrentMinute] = useState(() => startOfMinuteOf(Date.now()));\n\n useEffect(() => {\n if (!enabled) {\n return undefined;\n }\n let timer;\n const waitForNextMinute = () => {\n const untilNextMinute = MILLISECONDS_PER_MINUTE - (Date.now() % MILLISECONDS_PER_MINUTE);\n timer = setTimeout(() => {\n setCurrentMinute(startOfMinuteOf(Date.now()));\n waitForNextMinute();\n }, untilNextMinute);\n };\n // al volver a encender, el minuto puede haber cambiado mientras estaba apagado\n setCurrentMinute(startOfMinuteOf(Date.now()));\n waitForNextMinute();\n return () => clearTimeout(timer);\n }, [enabled]);\n\n return currentMinute;\n}\n"],"names":[],"mappings":";;;AAWA,MAAM,kBAAkB,CAAC,iBAAiB,KAAK,MAAM,eAAe,uBAAuB,IAAI;AAMxF,SAAS,iBAAiB,SAAS;AACtC,QAAM,CAAC,eAAe,gBAAgB,IAAI,SAAS,MAAM,gBAAgB,KAAK,IAAG,CAAE,CAAC;AAEpF,YAAU,MAAM;AACZ,QAAI,CAAC,SAAS;AACV,aAAO;AAAA,IACX;AACA,QAAI;AACJ,UAAM,oBAAoB,MAAM;AAC5B,YAAM,kBAAkB,0BAA2B,KAAK,IAAG,IAAK;AAChE,cAAQ,WAAW,MAAM;AACrB,yBAAiB,gBAAgB,KAAK,IAAG,CAAE,CAAC;AAC5C,0BAAiB;AAAA,MACrB,GAAG,eAAe;AAAA,IACtB;AAEA,qBAAiB,gBAAgB,KAAK,IAAG,CAAE,CAAC;AAC5C,sBAAiB;AACjB,WAAO,MAAM,aAAa,KAAK;AAAA,EACnC,GAAG,CAAC,OAAO,CAAC;AAEZ,SAAO;AACX;"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useId } from "react";
|
|
3
|
+
import { displayFormatsOf } from "./pickerValues.js";
|
|
4
|
+
import { translateValidationMessage } from "../forms/validationMessages.js";
|
|
5
|
+
import { useToolkitSettings } from "../toolkit/toolkitContext.js";
|
|
6
|
+
import { useToolkitTranslation } from "../toolkit/useToolkitTranslation.js";
|
|
7
|
+
function usePickerField({ name, kind, error, texts }) {
|
|
8
|
+
const { t, language } = useToolkitTranslation("common", texts);
|
|
9
|
+
const { timeZone, hourCycle } = useToolkitSettings();
|
|
10
|
+
const generatedId = useId();
|
|
11
|
+
return {
|
|
12
|
+
language,
|
|
13
|
+
timeZone,
|
|
14
|
+
inputId: `${name ?? kind}-${generatedId}`,
|
|
15
|
+
errorText: translateValidationMessage(t, error),
|
|
16
|
+
formats: displayFormatsOf(language, hourCycle)
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export {
|
|
20
|
+
usePickerField
|
|
21
|
+
};
|
|
22
|
+
//# sourceMappingURL=usePickerField.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usePickerField.js","sources":["../../src/datePickers/usePickerField.js"],"sourcesContent":["// Lo que necesita cada campo de fecha antes de pintarse: los textos (con los de la pantalla encima), el id que une la\n// etiqueta con el campo, el error ya traducido y cómo se ve en el idioma y el hourCycle del proveedor más cercano.\n//\n// Hook genérico: function (regla de la casa).\nimport {useId} from 'react';\n\nimport {displayFormatsOf} from '@/datePickers/pickerValues.js';\nimport {translateValidationMessage} from '@/forms/validationMessages.js';\nimport {useToolkitSettings} from '@/toolkit/toolkitContext.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\n\n/**\n * @param {object} field\n * @param {string} [field.name]\n * @param {string} field.kind de qué campo es (date, dateTime, time, month): da el principio del id si no hay name\n * @param {string|{key: string, values?: object}} [field.error]\n * @param {object} [field.texts] los textos que cambian, con la forma de `common`\n * @returns {{language: string, timeZone: string, inputId: string, errorText: string|undefined, formats: ReturnType<typeof displayFormatsOf>}}\n */\nexport function usePickerField({name, kind, error, texts}) {\n const {t, language} = useToolkitTranslation('common', texts);\n const {timeZone, hourCycle} = useToolkitSettings();\n const generatedId = useId();\n\n return {\n language,\n timeZone,\n inputId: `${name ?? kind}-${generatedId}`,\n errorText: translateValidationMessage(t, error),\n formats: displayFormatsOf(language, hourCycle),\n };\n}\n"],"names":[],"mappings":";;;;;;AAmBO,SAAS,eAAe,EAAC,MAAM,MAAM,OAAO,MAAK,GAAG;AACvD,QAAM,EAAC,GAAG,SAAQ,IAAI,sBAAsB,UAAU,KAAK;AAC3D,QAAM,EAAC,UAAU,UAAS,IAAI,mBAAkB;AAChD,QAAM,cAAc,MAAK;AAEzB,SAAO;AAAA,IACH;AAAA,IACA;AAAA,IACA,SAAS,GAAG,QAAQ,IAAI,IAAI,WAAW;AAAA,IACvC,WAAW,2BAA2B,GAAG,KAAK;AAAA,IAC9C,SAAS,iBAAiB,UAAU,SAAS;AAAA,EACrD;AACA;"}
|
|
@@ -8,12 +8,14 @@ const UNKNOWN_ENTRY = Object.freeze({
|
|
|
8
8
|
color: "default",
|
|
9
9
|
isKnown: false
|
|
10
10
|
});
|
|
11
|
-
function dictionaryEntryOf(value, keyPrefix, color, { hasShortLabel = false } = {}) {
|
|
11
|
+
function dictionaryEntryOf(value, keyPrefix, color, { hasShortLabel = false, icon } = {}) {
|
|
12
12
|
return Object.freeze({
|
|
13
13
|
value,
|
|
14
14
|
labelKey: `${NAMESPACE}:${keyPrefix}.label`,
|
|
15
15
|
tooltipKey: `${NAMESPACE}:${keyPrefix}.tooltip`,
|
|
16
16
|
...hasShortLabel ? { shortLabelKey: `${NAMESPACE}:${keyPrefix}.shortLabel` } : {},
|
|
17
|
+
// el ícono es solo una referencia: el diccionario sigue sin React
|
|
18
|
+
...icon ? { icon } : {},
|
|
17
19
|
color,
|
|
18
20
|
isKnown: true
|
|
19
21
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dictionaries.js","sources":["../../src/dictionaries/dictionaries.js"],"sourcesContent":["// Diccionarios de valores cerrados que llegan del API → etiqueta, tooltip y color del chip (StatusChip, DictionaryText).\n//\n// El paquete trae el mecanismo; los diccionarios los arma cada proyecto con dictionaryEntryOf:\n//\n// export const ORDER_STATUS = Object.freeze({\n// pending: dictionaryEntryOf('pending', 'orderStatus.pending', 'warning'),\n// paid: dictionaryEntryOf('paid', 'orderStatus.paid', 'success'),\n// });\n//\n// Forma de cada entrada: {value, labelKey, tooltipKey, color, isKnown} y, solo en algunas, shortLabelKey\n// - value: el código del API tal cual.\n// - labelKey / tooltipKey: llaves de texto del namespace \"dictionaries\" (los textos los pone el proyecto con `texts` o\n// `translate` de ToolkitProvider). El texto NUNCA va aquí: así cambia de idioma sin tocar el diccionario.\n// - shortLabelKey: palabra corta para una columna angosta («Rechazada» por «Rechazada por estructura»). Solo la traen\n// las entradas cuyo nombre completo no cabe; quien la pinta deja el nombre completo en el tooltip (StatusChip, short).\n// - color: color de paleta de MUI (success, warning, error, info, default).\n// - isKnown: false solo en la entrada \"unknown\"; sirve para pintar el código crudo cuando no hay texto (p. ej. un\n// estatus nuevo que el backend empezó a mandar).\n//\n// Se congelan con Object.freeze para que nadie los mute por accidente desde una pantalla.\n// Un valor que el API mande y no esté en el diccionario no truena la pantalla: lookup regresa la entrada \"unknown\" y lo\n// anota en el log para agregarlo.\n//\n// Funciones puras, no renderizan: function (regla de la casa).\nimport {createLogger} from '@/observability/logger.js';\n\nconst log = createLogger('dictionaries');\n\nconst NAMESPACE = 'dictionaries';\n\nconst UNKNOWN_ENTRY = Object.freeze({\n value: null,\n labelKey: `${NAMESPACE}:unknown.label`,\n tooltipKey: `${NAMESPACE}:unknown.tooltip`,\n color: 'default',\n isKnown: false,\n});\n\n/**\n * Una entrada de diccionario, congelada.\n *\n * @param {string} value el código del API\n * @param {string} keyPrefix dónde viven sus textos dentro del namespace dictionaries ('orderStatus.paid' → orderStatus.paid.label)\n * @param {string} color color de paleta de MUI\n * @param {{hasShortLabel?: boolean}} [options] hasShortLabel: trae palabra corta (keyPrefix.shortLabel)\n * @returns {{value: string, labelKey: string, tooltipKey: string, shortLabelKey?: string, color: string, isKnown: boolean}}\n */\nexport function dictionaryEntryOf(value, keyPrefix, color, {hasShortLabel = false} = {}) {\n return Object.freeze({\n value,\n labelKey: `${NAMESPACE}:${keyPrefix}.label`,\n tooltipKey: `${NAMESPACE}:${keyPrefix}.tooltip`,\n ...(hasShortLabel ? {shortLabelKey: `${NAMESPACE}:${keyPrefix}.shortLabel`} : {}),\n color,\n isKnown: true,\n });\n}\n\n/**\n * La entrada del diccionario para un valor del API.\n *\n * @param {Record<string, object>} dictionary p. ej. ORDER_STATUS\n * @param {string|null|undefined} value código del API\n * @returns {{value: string|null, labelKey: string, tooltipKey: string, color: string, isKnown: boolean}}\n */\nexport function lookup(dictionary, value) {\n if (value !== null && value !== undefined && Object.hasOwn(dictionary, value)) {\n return dictionary[value];\n }\n if (value !== null && value !== undefined) {\n log.warn('valor sin entrada en el diccionario', {value});\n }\n return {...UNKNOWN_ENTRY, value: value ?? null};\n}\n\n/**\n * Texto que se pinta para una entrada: su etiqueta traducida o, si el valor no está en el diccionario, el código\n * crudo. Un estatus nuevo se lee con su código y no como \"Sin estatus\".\n *\n * @param {{value: string|null, labelKey: string, isKnown: boolean}} dictionaryEntry lo que regresa lookup\n * @param {(key: string) => string} translate el t de useToolkitTranslation\n * @returns {string}\n */\nexport function labelOf(dictionaryEntry, translate) {\n if (dictionaryEntry.isKnown || dictionaryEntry.value === null) {\n return translate(dictionaryEntry.labelKey);\n }\n return dictionaryEntry.value;\n}\n\n/**\n * La palabra corta de una entrada, para una columna angosta. Una entrada sin palabra corta regresa su etiqueta de siempre.\n *\n * @param {object} dictionaryEntry entrada del diccionario (lookup)\n * @param {Function} translate traductor\n * @returns {string}\n */\nexport function shortLabelOf(dictionaryEntry, translate) {\n if (dictionaryEntry.isKnown && dictionaryEntry.shortLabelKey) {\n return translate(dictionaryEntry.shortLabelKey);\n }\n return labelOf(dictionaryEntry, translate);\n}\n"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"dictionaries.js","sources":["../../src/dictionaries/dictionaries.js"],"sourcesContent":["// Diccionarios de valores cerrados que llegan del API → etiqueta, tooltip y color del chip (StatusChip, DictionaryText).\n//\n// El paquete trae el mecanismo; los diccionarios los arma cada proyecto con dictionaryEntryOf:\n//\n// export const ORDER_STATUS = Object.freeze({\n// pending: dictionaryEntryOf('pending', 'orderStatus.pending', 'warning'),\n// paid: dictionaryEntryOf('paid', 'orderStatus.paid', 'success'),\n// });\n//\n// Forma de cada entrada: {value, labelKey, tooltipKey, color, isKnown} y, solo en algunas, shortLabelKey\n// - value: el código del API tal cual.\n// - labelKey / tooltipKey: llaves de texto del namespace \"dictionaries\" (los textos los pone el proyecto con `texts` o\n// `translate` de ToolkitProvider). El texto NUNCA va aquí: así cambia de idioma sin tocar el diccionario.\n// - shortLabelKey: palabra corta para una columna angosta («Rechazada» por «Rechazada por estructura»). Solo la traen\n// las entradas cuyo nombre completo no cabe; quien la pinta deja el nombre completo en el tooltip (StatusChip, short).\n// - color: color de paleta de MUI (success, warning, error, info, default).\n// - icon (opcional): el ícono con que la pinta una insignia (IconBadge); el diccionario solo guarda la referencia.\n// - isKnown: false solo en la entrada \"unknown\"; sirve para pintar el código crudo cuando no hay texto (p. ej. un\n// estatus nuevo que el backend empezó a mandar).\n//\n// Se congelan con Object.freeze para que nadie los mute por accidente desde una pantalla.\n// Un valor que el API mande y no esté en el diccionario no truena la pantalla: lookup regresa la entrada \"unknown\" y lo\n// anota en el log para agregarlo.\n//\n// Funciones puras, no renderizan: function (regla de la casa).\nimport {createLogger} from '@/observability/logger.js';\n\nconst log = createLogger('dictionaries');\n\nconst NAMESPACE = 'dictionaries';\n\nconst UNKNOWN_ENTRY = Object.freeze({\n value: null,\n labelKey: `${NAMESPACE}:unknown.label`,\n tooltipKey: `${NAMESPACE}:unknown.tooltip`,\n color: 'default',\n isKnown: false,\n});\n\n/**\n * Una entrada de diccionario, congelada.\n *\n * @param {string} value el código del API\n * @param {string} keyPrefix dónde viven sus textos dentro del namespace dictionaries ('orderStatus.paid' → orderStatus.paid.label)\n * @param {string} color color de paleta de MUI\n * @param {{hasShortLabel?: boolean, icon?: import('react').ElementType}} [options] hasShortLabel: trae palabra corta\n * (keyPrefix.shortLabel); icon: el ícono con que se pinta en una insignia (IconBadge), p. ej. CreditCardOutlinedIcon\n * @returns {{value: string, labelKey: string, tooltipKey: string, shortLabelKey?: string, icon?: import('react').ElementType, color: string, isKnown: boolean}}\n */\nexport function dictionaryEntryOf(value, keyPrefix, color, {hasShortLabel = false, icon} = {}) {\n return Object.freeze({\n value,\n labelKey: `${NAMESPACE}:${keyPrefix}.label`,\n tooltipKey: `${NAMESPACE}:${keyPrefix}.tooltip`,\n ...(hasShortLabel ? {shortLabelKey: `${NAMESPACE}:${keyPrefix}.shortLabel`} : {}),\n // el ícono es solo una referencia: el diccionario sigue sin React\n ...(icon ? {icon} : {}),\n color,\n isKnown: true,\n });\n}\n\n/**\n * La entrada del diccionario para un valor del API.\n *\n * @param {Record<string, object>} dictionary p. ej. ORDER_STATUS\n * @param {string|null|undefined} value código del API\n * @returns {{value: string|null, labelKey: string, tooltipKey: string, color: string, isKnown: boolean}}\n */\nexport function lookup(dictionary, value) {\n if (value !== null && value !== undefined && Object.hasOwn(dictionary, value)) {\n return dictionary[value];\n }\n if (value !== null && value !== undefined) {\n log.warn('valor sin entrada en el diccionario', {value});\n }\n return {...UNKNOWN_ENTRY, value: value ?? null};\n}\n\n/**\n * Texto que se pinta para una entrada: su etiqueta traducida o, si el valor no está en el diccionario, el código\n * crudo. Un estatus nuevo se lee con su código y no como \"Sin estatus\".\n *\n * @param {{value: string|null, labelKey: string, isKnown: boolean}} dictionaryEntry lo que regresa lookup\n * @param {(key: string) => string} translate el t de useToolkitTranslation\n * @returns {string}\n */\nexport function labelOf(dictionaryEntry, translate) {\n if (dictionaryEntry.isKnown || dictionaryEntry.value === null) {\n return translate(dictionaryEntry.labelKey);\n }\n return dictionaryEntry.value;\n}\n\n/**\n * La palabra corta de una entrada, para una columna angosta. Una entrada sin palabra corta regresa su etiqueta de siempre.\n *\n * @param {object} dictionaryEntry entrada del diccionario (lookup)\n * @param {Function} translate traductor\n * @returns {string}\n */\nexport function shortLabelOf(dictionaryEntry, translate) {\n if (dictionaryEntry.isKnown && dictionaryEntry.shortLabelKey) {\n return translate(dictionaryEntry.shortLabelKey);\n }\n return labelOf(dictionaryEntry, translate);\n}\n"],"names":[],"mappings":";AA2BA,MAAM,MAAM,aAAa,cAAc;AAEvC,MAAM,YAAY;AAElB,MAAM,gBAAgB,OAAO,OAAO;AAAA,EAChC,OAAO;AAAA,EACP,UAAU,GAAG,SAAS;AAAA,EACtB,YAAY,GAAG,SAAS;AAAA,EACxB,OAAO;AAAA,EACP,SAAS;AACb,CAAC;AAYM,SAAS,kBAAkB,OAAO,WAAW,OAAO,EAAC,gBAAgB,OAAO,KAAI,IAAI,IAAI;AAC3F,SAAO,OAAO,OAAO;AAAA,IACjB;AAAA,IACA,UAAU,GAAG,SAAS,IAAI,SAAS;AAAA,IACnC,YAAY,GAAG,SAAS,IAAI,SAAS;AAAA,IACrC,GAAI,gBAAgB,EAAC,eAAe,GAAG,SAAS,IAAI,SAAS,cAAa,IAAI;;IAE9E,GAAI,OAAO,EAAC,KAAI,IAAI;IACpB;AAAA,IACA,SAAS;AAAA,EACjB,CAAK;AACL;AASO,SAAS,OAAO,YAAY,OAAO;AACtC,MAAI,UAAU,QAAQ,UAAU,UAAa,OAAO,OAAO,YAAY,KAAK,GAAG;AAC3E,WAAO,WAAW,KAAK;AAAA,EAC3B;AACA,MAAI,UAAU,QAAQ,UAAU,QAAW;AACvC,QAAI,KAAK,uCAAuC,EAAC,MAAK,CAAC;AAAA,EAC3D;AACA,SAAO,EAAC,GAAG,eAAe,OAAO,SAAS,KAAI;AAClD;AAUO,SAAS,QAAQ,iBAAiB,WAAW;AAChD,MAAI,gBAAgB,WAAW,gBAAgB,UAAU,MAAM;AAC3D,WAAO,UAAU,gBAAgB,QAAQ;AAAA,EAC7C;AACA,SAAO,gBAAgB;AAC3B;AASO,SAAS,aAAa,iBAAiB,WAAW;AACrD,MAAI,gBAAgB,WAAW,gBAAgB,eAAe;AAC1D,WAAO,UAAU,gBAAgB,aAAa;AAAA,EAClD;AACA,SAAO,QAAQ,iBAAiB,SAAS;AAC7C;"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useFormikField } from "./useFormikField.js";
|
|
4
|
+
import AttachmentsInput from "../upload/AttachmentsInput.js";
|
|
5
|
+
function FormAttachmentsField({ name, ...inputProps }) {
|
|
6
|
+
const fieldProps = useFormikField(name);
|
|
7
|
+
return /* @__PURE__ */ jsx(AttachmentsInput, { ...inputProps, ...fieldProps });
|
|
8
|
+
}
|
|
9
|
+
export {
|
|
10
|
+
FormAttachmentsField as default
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=FormAttachmentsField.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FormAttachmentsField.js","sources":["../../src/forms/FormAttachmentsField.jsx"],"sourcesContent":["// Campo de varios archivos de formik: AttachmentsInput (upload/) conectado a su campo. Al formulario llega la lista de\n// archivos; subirlos es de la pantalla al guardar (uploadWithProgress con su FormData).\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useFormikField} from '@/forms/useFormikField.js';\nimport AttachmentsInput from '@/upload/AttachmentsInput.jsx';\n\n/**\n * @param {{name: string} & Omit<Parameters<typeof import('@/upload/AttachmentsInput.jsx').default>[0], 'name'|'value'|'onChange'|'onBlur'|'error'>} props las de\n * AttachmentsInput (accept, maxFiles, maxTotalSize, template…), sin value / onChange / onBlur / error\n */\nexport default function FormAttachmentsField({name, ...inputProps}) {\n const fieldProps = useFormikField(name);\n return <AttachmentsInput {...inputProps} {...fieldProps} />;\n}\n"],"names":[],"mappings":";;;;AAWA,SAAwB,qBAAqB,EAAC,MAAM,GAAG,cAAa;AAChE,QAAM,aAAa,eAAe,IAAI;AACtC,SAAO,oBAAC,kBAAA,EAAkB,GAAG,YAAa,GAAG,WAAA,CAAY;AAC7D;"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useFormikField } from "./useFormikField.js";
|
|
4
|
+
import CheckboxGroupInput from "../inputs/CheckboxGroupInput.js";
|
|
5
|
+
function FormCheckboxGroupField({ name, ...inputProps }) {
|
|
6
|
+
const fieldProps = useFormikField(name);
|
|
7
|
+
return /* @__PURE__ */ jsx(CheckboxGroupInput, { ...inputProps, ...fieldProps });
|
|
8
|
+
}
|
|
9
|
+
export {
|
|
10
|
+
FormCheckboxGroupField as default
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=FormCheckboxGroupField.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FormCheckboxGroupField.js","sources":["../../src/forms/FormCheckboxGroupField.jsx"],"sourcesContent":["// El grupo de casillas de formik: CheckboxGroupInput (inputs/) conectado a su campo por name. Al formulario llega la\n// lista de las marcadas.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useFormikField} from '@/forms/useFormikField.js';\nimport CheckboxGroupInput from '@/inputs/CheckboxGroupInput.jsx';\n\n/**\n * @param {{name: string} & Omit<Parameters<typeof import('@/inputs/CheckboxGroupInput.jsx').default>[0], 'name'|'value'|'onChange'|'onBlur'|'error'>} props las de\n * CheckboxGroupInput, sin value / onChange / onBlur / error\n */\nexport default function FormCheckboxGroupField({name, ...inputProps}) {\n const fieldProps = useFormikField(name);\n return <CheckboxGroupInput {...inputProps} {...fieldProps} />;\n}\n"],"names":[],"mappings":";;;;AAWA,SAAwB,uBAAuB,EAAC,MAAM,GAAG,cAAa;AAClE,QAAM,aAAa,eAAe,IAAI;AACtC,SAAO,oBAAC,oBAAA,EAAoB,GAAG,YAAa,GAAG,WAAA,CAAY;AAC/D;"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useFormikField } from "./useFormikField.js";
|
|
4
|
+
import FileInput from "../upload/FileInput.js";
|
|
5
|
+
function FormFileField({ name, ...inputProps }) {
|
|
6
|
+
const fieldProps = useFormikField(name);
|
|
7
|
+
return /* @__PURE__ */ jsx(FileInput, { ...inputProps, ...fieldProps });
|
|
8
|
+
}
|
|
9
|
+
export {
|
|
10
|
+
FormFileField as default
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=FormFileField.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FormFileField.js","sources":["../../src/forms/FormFileField.jsx"],"sourcesContent":["// Campo de un archivo de formik: FileInput (upload/) conectado a su campo. Al formulario llega el File (o lo que ya estaba\n// guardado) o null; subirlo es de la pantalla al guardar (uploadWithProgress con su FormData).\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useFormikField} from '@/forms/useFormikField.js';\nimport FileInput from '@/upload/FileInput.jsx';\n\n/**\n * @param {{name: string} & Omit<Parameters<typeof import('@/upload/FileInput.jsx').default>[0], 'name'|'value'|'onChange'|'onBlur'|'error'>} props las de\n * FileInput (accept, maxSize, template…), sin value / onChange / onBlur / error\n */\nexport default function FormFileField({name, ...inputProps}) {\n const fieldProps = useFormikField(name);\n return <FileInput {...inputProps} {...fieldProps} />;\n}\n"],"names":[],"mappings":";;;;AAWA,SAAwB,cAAc,EAAC,MAAM,GAAG,cAAa;AACzD,QAAM,aAAa,eAAe,IAAI;AACtC,SAAO,oBAAC,WAAA,EAAW,GAAG,YAAa,GAAG,WAAA,CAAY;AACtD;"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useFormikField } from "./useFormikField.js";
|
|
4
|
+
import FileUploadInput from "../upload/FileUploadInput.js";
|
|
5
|
+
function FormFileUploadField({ name, ...inputProps }) {
|
|
6
|
+
const fieldProps = useFormikField(name);
|
|
7
|
+
return /* @__PURE__ */ jsx(FileUploadInput, { ...inputProps, ...fieldProps });
|
|
8
|
+
}
|
|
9
|
+
export {
|
|
10
|
+
FormFileUploadField as default
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=FormFileUploadField.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FormFileUploadField.js","sources":["../../src/forms/FormFileUploadField.jsx"],"sourcesContent":["// El cargador mini de formik: FileUploadInput (upload/) conectado a su campo. Sube en cuanto se elige y al formulario llega\n// solo el id que dio el servidor ('' mientras sube o si se quita): con Yup.string().required() pide el documento.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useFormikField} from '@/forms/useFormikField.js';\nimport FileUploadInput from '@/upload/FileUploadInput.jsx';\n\n/**\n * @param {{name: string} & Omit<Parameters<typeof import('@/upload/FileUploadInput.jsx').default>[0], 'name'|'value'|'onChange'|'onBlur'|'error'>} props las de\n * FileUploadInput (upload, accept, maxSize, download…), sin value / onChange / onBlur / error\n */\nexport default function FormFileUploadField({name, ...inputProps}) {\n const fieldProps = useFormikField(name);\n return <FileUploadInput {...inputProps} {...fieldProps} />;\n}\n"],"names":[],"mappings":";;;;AAWA,SAAwB,oBAAoB,EAAC,MAAM,GAAG,cAAa;AAC/D,QAAM,aAAa,eAAe,IAAI;AACtC,SAAO,oBAAC,iBAAA,EAAiB,GAAG,YAAa,GAAG,WAAA,CAAY;AAC5D;"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useFormikField } from "./useFormikField.js";
|
|
4
|
+
import ImageInput from "../upload/ImageInput.js";
|
|
5
|
+
function FormImageField({ name, ...inputProps }) {
|
|
6
|
+
const fieldProps = useFormikField(name);
|
|
7
|
+
return /* @__PURE__ */ jsx(ImageInput, { ...inputProps, ...fieldProps });
|
|
8
|
+
}
|
|
9
|
+
export {
|
|
10
|
+
FormImageField as default
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=FormImageField.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FormImageField.js","sources":["../../src/forms/FormImageField.jsx"],"sourcesContent":["// Campo de una imagen de formik: ImageInput (upload/) conectado a su campo. Al formulario llega el File, la dirección de la\n// imagen guardada o null; subirla es de la pantalla al guardar (uploadWithProgress con su FormData).\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useFormikField} from '@/forms/useFormikField.js';\nimport ImageInput from '@/upload/ImageInput.jsx';\n\n/**\n * @param {{name: string} & Omit<Parameters<typeof import('@/upload/ImageInput.jsx').default>[0], 'name'|'value'|'onChange'|'onBlur'|'error'>} props las de\n * ImageInput (shape, size, maxSize…), sin value / onChange / onBlur / error\n */\nexport default function FormImageField({name, ...inputProps}) {\n const fieldProps = useFormikField(name);\n return <ImageInput {...inputProps} {...fieldProps} />;\n}\n"],"names":[],"mappings":";;;;AAWA,SAAwB,eAAe,EAAC,MAAM,GAAG,cAAa;AAC1D,QAAM,aAAa,eAAe,IAAI;AACtC,SAAO,oBAAC,YAAA,EAAY,GAAG,YAAa,GAAG,WAAA,CAAY;AACvD;"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useFormikField } from "./useFormikField.js";
|
|
4
|
+
import MaskedInput from "../inputs/MaskedInput.js";
|
|
5
|
+
function FormMaskedField({ name, ...inputProps }) {
|
|
6
|
+
const fieldProps = useFormikField(name);
|
|
7
|
+
return /* @__PURE__ */ jsx(MaskedInput, { ...inputProps, ...fieldProps });
|
|
8
|
+
}
|
|
9
|
+
export {
|
|
10
|
+
FormMaskedField as default
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=FormMaskedField.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FormMaskedField.js","sources":["../../src/forms/FormMaskedField.jsx"],"sourcesContent":["// Campo con máscara de formik: MaskedInput (inputs/) conectado a su campo. Al formulario llega el valor LIMPIO (sin\n// separadores, en mayúsculas): «5551234567», no «555 123 4567».\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useFormikField} from '@/forms/useFormikField.js';\nimport MaskedInput from '@/inputs/MaskedInput.jsx';\n\n/**\n * @param {{name: string} & Omit<Parameters<typeof import('@/inputs/MaskedInput.jsx').default>[0], 'name'|'value'|'onChange'|'onBlur'|'error'>} props\n * las de MaskedInput (mask, label, helperText, placeholder…)\n */\nexport default function FormMaskedField({name, ...inputProps}) {\n const fieldProps = useFormikField(name);\n return <MaskedInput {...inputProps} {...fieldProps} />;\n}\n"],"names":[],"mappings":";;;;AAWA,SAAwB,gBAAgB,EAAC,MAAM,GAAG,cAAa;AAC3D,QAAM,aAAa,eAAe,IAAI;AACtC,SAAO,oBAAC,aAAA,EAAa,GAAG,YAAa,GAAG,WAAA,CAAY;AACxD;"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useFormikContext, getIn } from "formik";
|
|
4
|
+
import RepeatingFields from "../inputs/RepeatingFields.js";
|
|
5
|
+
import { useRowKeys } from "../inputs/useRowKeys.js";
|
|
6
|
+
const EMPTY_ROWS = Object.freeze([]);
|
|
7
|
+
function rowErrorsOf(errors, name, index) {
|
|
8
|
+
const rowErrors = getIn(errors, `${name}.${index}`);
|
|
9
|
+
return rowErrors && typeof rowErrors === "object" ? rowErrors : null;
|
|
10
|
+
}
|
|
11
|
+
function FormRepeatingFields({ name, emptyRow, renderRow, removeBlockedReason, confirmRemove, onAdd, onRemove, ...viewProps }) {
|
|
12
|
+
const { values, errors, touched, submitCount, setFieldValue } = useFormikContext();
|
|
13
|
+
const rows = getIn(values, name) ?? EMPTY_ROWS;
|
|
14
|
+
const rowKeys = useRowKeys(rows.length);
|
|
15
|
+
const listError = getIn(errors, name);
|
|
16
|
+
const isListLevelError = typeof listError === "string" || Boolean(listError?.key);
|
|
17
|
+
const wasTouched = submitCount > 0 || Boolean(getIn(touched, name));
|
|
18
|
+
const add = () => {
|
|
19
|
+
rowKeys.added();
|
|
20
|
+
setFieldValue(name, [...rows, { ...emptyRow }]);
|
|
21
|
+
onAdd?.(rows.length);
|
|
22
|
+
};
|
|
23
|
+
const remove = (index) => {
|
|
24
|
+
const removedRow = rows[index];
|
|
25
|
+
rowKeys.removed(index);
|
|
26
|
+
setFieldValue(name, rows.filter((unused, rowIndex) => rowIndex !== index));
|
|
27
|
+
onRemove?.(removedRow, index);
|
|
28
|
+
};
|
|
29
|
+
const clear = (index) => {
|
|
30
|
+
const clearedRow = rows[index];
|
|
31
|
+
rowKeys.renewed(index);
|
|
32
|
+
setFieldValue(name, rows.map((row, rowIndex) => rowIndex === index ? { ...emptyRow } : row));
|
|
33
|
+
onRemove?.(clearedRow, index);
|
|
34
|
+
};
|
|
35
|
+
return /* @__PURE__ */ jsx(
|
|
36
|
+
RepeatingFields,
|
|
37
|
+
{
|
|
38
|
+
...viewProps,
|
|
39
|
+
rowKeys: rowKeys.keys,
|
|
40
|
+
renderRow: (index) => renderRow({ index, row: rows[index], nameOf: (field) => `${name}.${index}.${field}` }),
|
|
41
|
+
onAdd: add,
|
|
42
|
+
onRemove: remove,
|
|
43
|
+
onClear: clear,
|
|
44
|
+
removeBlockedReasonOf: removeBlockedReason ? (index) => removeBlockedReason(rows[index], index) : void 0,
|
|
45
|
+
confirmRemoveOf: confirmRemove ? (index) => confirmRemove(rows[index], index) : void 0,
|
|
46
|
+
rowHasErrorOf: (index) => wasTouched && rowErrorsOf(errors, name, index) !== null,
|
|
47
|
+
error: wasTouched && isListLevelError ? listError : void 0
|
|
48
|
+
}
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
export {
|
|
52
|
+
FormRepeatingFields as default
|
|
53
|
+
};
|
|
54
|
+
//# sourceMappingURL=FormRepeatingFields.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FormRepeatingFields.js","sources":["../../src/forms/FormRepeatingFields.jsx"],"sourcesContent":["// Renglones que se repiten en formik: RepeatingFields (inputs/) conectado a una lista del formulario por name. Agregar,\n// quitar y limpiar cambian la lista; cada campo de adentro se conecta solo con su nombre (nameOf('email') →\n// 'contacts.2.email') y enseña su error de yup.\n//\n// <FormRepeatingFields name=\"contacts\" label=\"Contactos\" emptyRow={{name: '', email: ''}} maxRows={5}\n// renderRow={({nameOf}) => (<>\n// <FormTextField name={nameOf('name')} label=\"Nombre\" />\n// <FormTextField name={nameOf('email')} label=\"Correo\" />\n// </>)} />\n//\n// Lo que dice si un renglón se puede quitar (removeBlockedReason, confirmRemove) y los avisos (onAdd, onRemove) reciben el\n// renglón con sus valores. El error de toda la lista (la suma se pasa del tope) sale abajo de «Agregar».\n//\n// OJO, de formik: guarda en el mismo lugar el error de la lista y los de sus renglones, y si hay de los dos se queda con\n// los de los renglones. El de la lista sale en cuanto los renglones ya no tienen errores (mientras, no se guarda). Para\n// que se vea mientras se escribe, un resumen en vivo en `footer` (listTotalOf). Con react-hook-form salen los dos.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {getIn, useFormikContext} from 'formik';\n\nimport RepeatingFields from '@/inputs/RepeatingFields.jsx';\nimport {useRowKeys} from '@/inputs/useRowKeys.js';\n\nconst EMPTY_ROWS = Object.freeze([]);\n\n/**\n * Los errores de los campos de un renglón ({email: …}); null si no tiene. Ojo: el error de toda la lista también vive en\n * errors[name] (como {key, values}), junto a los de cada renglón.\n *\n * @param {object} errors los de formik\n * @param {string} name\n * @param {number} index\n * @returns {object|null}\n */\nfunction rowErrorsOf(errors, name, index) {\n const rowErrors = getIn(errors, `${name}.${index}`);\n return rowErrors && typeof rowErrors === 'object' ? rowErrors : null;\n}\n\n/**\n * @param {{\n * name: string,\n * emptyRow: object,\n * renderRow: (row: {index: number, row: any, nameOf: (field: string) => string}) => import('react').ReactNode,\n * removeBlockedReason?: (row: any, index: number) => string|null,\n * confirmRemove?: (row: any, index: number) => boolean,\n * onAdd?: (index: number) => void,\n * onRemove?: (row: any, index: number) => void,\n * } & Omit<Parameters<typeof import('@/inputs/RepeatingFields.jsx').default>[0], 'rowKeys'|'renderRow'|'onAdd'|'onRemove'|'onClear'|'removeBlockedReasonOf'|'confirmRemoveOf'|'rowHasErrorOf'|'error'>} props\n * name: la lista del formulario; emptyRow: cómo empieza un renglón nuevo; lo demás, lo de RepeatingFields\n */\nexport default function FormRepeatingFields({name, emptyRow, renderRow, removeBlockedReason, confirmRemove, onAdd, onRemove, ...viewProps}) {\n const {values, errors, touched, submitCount, setFieldValue} = useFormikContext();\n const rows = getIn(values, name) ?? EMPTY_ROWS;\n const rowKeys = useRowKeys(rows.length);\n const listError = getIn(errors, name);\n // el error de toda la lista: un texto o {key, values} (los de los renglones vienen en sus campos)\n const isListLevelError = typeof listError === 'string' || Boolean(listError?.key);\n const wasTouched = submitCount > 0 || Boolean(getIn(touched, name));\n\n const add = () => {\n rowKeys.added();\n setFieldValue(name, [...rows, {...emptyRow}]);\n onAdd?.(rows.length);\n };\n const remove = (index) => {\n const removedRow = rows[index];\n rowKeys.removed(index);\n setFieldValue(name, rows.filter((unused, rowIndex) => rowIndex !== index));\n onRemove?.(removedRow, index);\n };\n const clear = (index) => {\n const clearedRow = rows[index];\n rowKeys.renewed(index);\n setFieldValue(name, rows.map((row, rowIndex) => (rowIndex === index ? {...emptyRow} : row)));\n onRemove?.(clearedRow, index);\n };\n\n return (\n <RepeatingFields\n {...viewProps}\n rowKeys={rowKeys.keys}\n renderRow={(index) => renderRow({index, row: rows[index], nameOf: (field) => `${name}.${index}.${field}`})}\n onAdd={add}\n onRemove={remove}\n onClear={clear}\n removeBlockedReasonOf={removeBlockedReason ? (index) => removeBlockedReason(rows[index], index) : undefined}\n confirmRemoveOf={confirmRemove ? (index) => confirmRemove(rows[index], index) : undefined}\n rowHasErrorOf={(index) => wasTouched && rowErrorsOf(errors, name, index) !== null}\n error={wasTouched && isListLevelError ? listError : undefined}\n />\n );\n}\n"],"names":[],"mappings":";;;;;AAuBA,MAAM,aAAa,OAAO,OAAO,EAAE;AAWnC,SAAS,YAAY,QAAQ,MAAM,OAAO;AACtC,QAAM,YAAY,MAAM,QAAQ,GAAG,IAAI,IAAI,KAAK,EAAE;AAClD,SAAO,aAAa,OAAO,cAAc,WAAW,YAAY;AACpE;AAcA,SAAwB,oBAAoB,EAAC,MAAM,UAAU,WAAW,qBAAqB,eAAe,OAAO,UAAU,GAAG,aAAY;AACxI,QAAM,EAAC,QAAQ,QAAQ,SAAS,aAAa,cAAA,IAAiB,iBAAA;AAC9D,QAAM,OAAO,MAAM,QAAQ,IAAI,KAAK;AACpC,QAAM,UAAU,WAAW,KAAK,MAAM;AACtC,QAAM,YAAY,MAAM,QAAQ,IAAI;AAEpC,QAAM,mBAAmB,OAAO,cAAc,YAAY,QAAQ,WAAW,GAAG;AAChF,QAAM,aAAa,cAAc,KAAK,QAAQ,MAAM,SAAS,IAAI,CAAC;AAElE,QAAM,MAAM,MAAM;AACd,YAAQ,MAAA;AACR,kBAAc,MAAM,CAAC,GAAG,MAAM,EAAC,GAAG,SAAA,CAAS,CAAC;AAC5C,YAAQ,KAAK,MAAM;AAAA,EACvB;AACA,QAAM,SAAS,CAAC,UAAU;AACtB,UAAM,aAAa,KAAK,KAAK;AAC7B,YAAQ,QAAQ,KAAK;AACrB,kBAAc,MAAM,KAAK,OAAO,CAAC,QAAQ,aAAa,aAAa,KAAK,CAAC;AACzE,eAAW,YAAY,KAAK;AAAA,EAChC;AACA,QAAM,QAAQ,CAAC,UAAU;AACrB,UAAM,aAAa,KAAK,KAAK;AAC7B,YAAQ,QAAQ,KAAK;AACrB,kBAAc,MAAM,KAAK,IAAI,CAAC,KAAK,aAAc,aAAa,QAAQ,EAAC,GAAG,SAAA,IAAY,GAAI,CAAC;AAC3F,eAAW,YAAY,KAAK;AAAA,EAChC;AAEA,SACI;AAAA,IAAC;AAAA,IAAA;AAAA,MACI,GAAG;AAAA,MACJ,SAAS,QAAQ;AAAA,MACjB,WAAW,CAAC,UAAU,UAAU,EAAC,OAAO,KAAK,KAAK,KAAK,GAAG,QAAQ,CAAC,UAAU,GAAG,IAAI,IAAI,KAAK,IAAI,KAAK,IAAG;AAAA,MACzG,OAAO;AAAA,MACP,UAAU;AAAA,MACV,SAAS;AAAA,MACT,uBAAuB,sBAAsB,CAAC,UAAU,oBAAoB,KAAK,KAAK,GAAG,KAAK,IAAI;AAAA,MAClG,iBAAiB,gBAAgB,CAAC,UAAU,cAAc,KAAK,KAAK,GAAG,KAAK,IAAI;AAAA,MAChF,eAAe,CAAC,UAAU,cAAc,YAAY,QAAQ,MAAM,KAAK,MAAM;AAAA,MAC7E,OAAO,cAAc,mBAAmB,YAAY;AAAA,IAAA;AAAA,EAAA;AAGhE;"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useFormikField } from "./useFormikField.js";
|
|
4
|
+
import TransferListInput from "../inputs/TransferListInput.js";
|
|
5
|
+
function FormTransferListField({ name, ...inputProps }) {
|
|
6
|
+
const fieldProps = useFormikField(name);
|
|
7
|
+
return /* @__PURE__ */ jsx(TransferListInput, { ...inputProps, ...fieldProps });
|
|
8
|
+
}
|
|
9
|
+
export {
|
|
10
|
+
FormTransferListField as default
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=FormTransferListField.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FormTransferListField.js","sources":["../../src/forms/FormTransferListField.jsx"],"sourcesContent":["// La lista de transferencia de formik: TransferListInput (inputs/) conectada a su campo por name. Al formulario llega la\n// lista de los elegidos.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useFormikField} from '@/forms/useFormikField.js';\nimport TransferListInput from '@/inputs/TransferListInput.jsx';\n\n/**\n * @param {{name: string} & Omit<Parameters<typeof import('@/inputs/TransferListInput.jsx').default>[0], 'name'|'value'|'onChange'|'onBlur'|'error'>} props las de\n * TransferListInput, sin value / onChange / onBlur / error\n */\nexport default function FormTransferListField({name, ...inputProps}) {\n const fieldProps = useFormikField(name);\n return <TransferListInput {...inputProps} {...fieldProps} />;\n}\n"],"names":[],"mappings":";;;;AAWA,SAAwB,sBAAsB,EAAC,MAAM,GAAG,cAAa;AACjE,QAAM,aAAa,eAAe,IAAI;AACtC,SAAO,oBAAC,mBAAA,EAAmB,GAAG,YAAa,GAAG,WAAA,CAAY;AAC9D;"}
|
|
@@ -1,13 +1,20 @@
|
|
|
1
|
-
|
|
1
|
+
"use client";
|
|
2
|
+
import { getIn } from "formik";
|
|
3
|
+
import { splitRowFieldErrors, splitServerFieldErrors, formLevelMessageOf } from "./serverProblemFields.js";
|
|
2
4
|
import { fieldErrorsOf } from "../problems.js";
|
|
3
5
|
function applyServerProblem(error, { setFieldError, setStatus, values }) {
|
|
4
|
-
const
|
|
6
|
+
const { rowFieldErrors, otherErrors } = splitRowFieldErrors(error?.problem?.errors, (path) => getIn(values, path) !== void 0);
|
|
7
|
+
for (const [path, message] of Object.entries(rowFieldErrors)) {
|
|
8
|
+
setFieldError(path, message);
|
|
9
|
+
}
|
|
10
|
+
const errorsByField = fieldErrorsOf(error?.problem ? { ...error.problem, errors: otherErrors } : null);
|
|
5
11
|
const formFieldNames = Object.keys(values ?? {});
|
|
6
12
|
const { fieldErrors } = splitServerFieldErrors(errorsByField, formFieldNames);
|
|
7
13
|
for (const [fieldName, message] of Object.entries(fieldErrors)) {
|
|
8
14
|
setFieldError(fieldName, message);
|
|
9
15
|
}
|
|
10
|
-
|
|
16
|
+
const hasRowErrors = Object.keys(rowFieldErrors).length > 0;
|
|
17
|
+
setStatus(hasRowErrors && Object.keys(errorsByField).length === 0 ? null : formLevelMessageOf(errorsByField, formFieldNames, error?.message));
|
|
11
18
|
}
|
|
12
19
|
export {
|
|
13
20
|
applyServerProblem
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"applyServerProblem.js","sources":["../../src/forms/applyServerProblem.js"],"sourcesContent":["// Del error del servidor a los campos del formulario de Formik (validación de los dos lados).\n//\n// El backend responde RFC 9457 con dos tipos de queja:\n// - un 422 trae `errors` por campo ({email: ['...']}) y eso se pinta EN ese campo: un error de campo mostrado como\n// aviso obliga a adivinar cuál está mal;\n// - lo demás (401, 409, 429, 500) es del formulario entero y va al aviso (status de Formik).\n// Si el campo del que se queja no existe en el formulario, el mensaje no se pierde: va al aviso.\n//\n// Función pura, no renderiza: function (regla de la casa).\nimport {formLevelMessageOf, splitServerFieldErrors} from '@/forms/serverProblemFields.js';\nimport {fieldErrorsOf} from '@/problems.js';\n\n/**\n * @param {unknown} error normalmente un ApiError (trae problem y message ya traducidos)\n * @param {{setFieldError: Function, setStatus: Function, values: object}} formikHelpers\n */\nexport function applyServerProblem(error, {setFieldError, setStatus, values}) {\n const errorsByField = fieldErrorsOf(error?.problem);\n const formFieldNames = Object.keys(values ?? {});\n const {fieldErrors} = splitServerFieldErrors(errorsByField, formFieldNames);\n\n for (const [fieldName, message] of Object.entries(fieldErrors)) {\n setFieldError(fieldName, message);\n }\n setStatus(formLevelMessageOf(errorsByField, formFieldNames, error?.message));\n}\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"applyServerProblem.js","sources":["../../src/forms/applyServerProblem.js"],"sourcesContent":["// Del error del servidor a los campos del formulario de Formik (validación de los dos lados).\n//\n// El backend responde RFC 9457 con dos tipos de queja:\n// - un 422 trae `errors` por campo ({email: ['...']}) y eso se pinta EN ese campo: un error de campo mostrado como\n// aviso obliga a adivinar cuál está mal;\n// - lo demás (401, 409, 429, 500) es del formulario entero y va al aviso (status de Formik).\n// Si el campo del que se queja no existe en el formulario, el mensaje no se pierde: va al aviso. El de un campo dentro de un\n// renglón que el formulario tiene ('contacts.2.email', FormRepeatingFields) va a ese renglón.\n//\n// Función pura, no renderiza: function (regla de la casa).\nimport {getIn} from 'formik';\n\nimport {formLevelMessageOf, splitRowFieldErrors, splitServerFieldErrors} from '@/forms/serverProblemFields.js';\nimport {fieldErrorsOf} from '@/problems.js';\n\n/**\n * @param {unknown} error normalmente un ApiError (trae problem y message ya traducidos)\n * @param {{setFieldError: Function, setStatus: Function, values: object}} formikHelpers\n */\nexport function applyServerProblem(error, {setFieldError, setStatus, values}) {\n // primero los de un renglón ('contacts.2.email'), con su ruta completa; los demás, como siempre\n const {rowFieldErrors, otherErrors} = splitRowFieldErrors(error?.problem?.errors, (path) => getIn(values, path) !== undefined);\n for (const [path, message] of Object.entries(rowFieldErrors)) {\n setFieldError(path, message);\n }\n const errorsByField = fieldErrorsOf(error?.problem ? {...error.problem, errors: otherErrors} : null);\n const formFieldNames = Object.keys(values ?? {});\n const {fieldErrors} = splitServerFieldErrors(errorsByField, formFieldNames);\n\n for (const [fieldName, message] of Object.entries(fieldErrors)) {\n setFieldError(fieldName, message);\n }\n // si todo cayó en renglones, tampoco se repite arriba\n const hasRowErrors = Object.keys(rowFieldErrors).length > 0;\n setStatus(hasRowErrors && Object.keys(errorsByField).length === 0 ? null : formLevelMessageOf(errorsByField, formFieldNames, error?.message));\n}\n"],"names":[],"mappings":";;;;AAmBO,SAAS,mBAAmB,OAAO,EAAC,eAAe,WAAW,OAAM,GAAG;AAE1E,QAAM,EAAC,gBAAgB,YAAW,IAAI,oBAAoB,OAAO,SAAS,QAAQ,CAAC,SAAS,MAAM,QAAQ,IAAI,MAAM,MAAS;AAC7H,aAAW,CAAC,MAAM,OAAO,KAAK,OAAO,QAAQ,cAAc,GAAG;AAC1D,kBAAc,MAAM,OAAO;AAAA,EAC/B;AACA,QAAM,gBAAgB,cAAc,OAAO,UAAU,EAAC,GAAG,MAAM,SAAS,QAAQ,YAAW,IAAI,IAAI;AACnG,QAAM,iBAAiB,OAAO,KAAK,UAAU,CAAA,CAAE;AAC/C,QAAM,EAAC,YAAW,IAAI,uBAAuB,eAAe,cAAc;AAE1E,aAAW,CAAC,WAAW,OAAO,KAAK,OAAO,QAAQ,WAAW,GAAG;AAC5D,kBAAc,WAAW,OAAO;AAAA,EACpC;AAEA,QAAM,eAAe,OAAO,KAAK,cAAc,EAAE,SAAS;AAC1D,YAAU,gBAAgB,OAAO,KAAK,aAAa,EAAE,WAAW,IAAI,OAAO,mBAAmB,eAAe,gBAAgB,OAAO,OAAO,CAAC;AAChJ;"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { Money } from "../money/money.js";
|
|
2
|
+
function rowsOf(context, listName) {
|
|
3
|
+
return context.from?.[1]?.value?.[listName] ?? [];
|
|
4
|
+
}
|
|
5
|
+
function numberOf(value) {
|
|
6
|
+
if (value === "" || value === null || value === void 0) {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
const number = Number(value);
|
|
10
|
+
return Number.isFinite(number) ? number : null;
|
|
11
|
+
}
|
|
12
|
+
function uniqueInListRule(listName, field) {
|
|
13
|
+
return {
|
|
14
|
+
name: "uniqueInList",
|
|
15
|
+
message: { key: "validation:listDuplicate" },
|
|
16
|
+
test: (value, context) => {
|
|
17
|
+
if (value === "" || value === null || value === void 0) {
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
const firstIndex = rowsOf(context, listName).findIndex((row) => String(row?.[field] ?? "") === String(value));
|
|
21
|
+
if (firstIndex === -1 || firstIndex >= context.options.index) {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
return context.createError({ message: { key: "validation:listDuplicate", values: { row: firstIndex + 1 } } });
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function orderedInListRule(listName, field, { ascending = true, allowEqual = false, displayOf = (text) => text } = {}) {
|
|
29
|
+
const isInOrder = (lower, higher) => allowEqual ? lower <= higher : lower < higher;
|
|
30
|
+
return {
|
|
31
|
+
name: "orderedInList",
|
|
32
|
+
message: { key: "validation:listOrderBetween" },
|
|
33
|
+
test: (value, context) => {
|
|
34
|
+
const current = numberOf(value);
|
|
35
|
+
if (current === null) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
const rows = rowsOf(context, listName);
|
|
39
|
+
const index = context.options.index;
|
|
40
|
+
const hasValue = (rowIndex) => numberOf(rows[rowIndex]?.[field]) !== null;
|
|
41
|
+
let previousIndex = index - 1;
|
|
42
|
+
while (previousIndex >= 0 && !hasValue(previousIndex)) {
|
|
43
|
+
previousIndex -= 1;
|
|
44
|
+
}
|
|
45
|
+
let nextIndex = index + 1;
|
|
46
|
+
while (nextIndex < rows.length && !hasValue(nextIndex)) {
|
|
47
|
+
nextIndex += 1;
|
|
48
|
+
}
|
|
49
|
+
const previous = previousIndex >= 0 ? numberOf(rows[previousIndex][field]) : null;
|
|
50
|
+
const next = nextIndex < rows.length ? numberOf(rows[nextIndex][field]) : null;
|
|
51
|
+
const fitsPrevious = previous === null || (ascending ? isInOrder(previous, current) : isInOrder(current, previous));
|
|
52
|
+
const fitsNext = next === null || (ascending ? isInOrder(current, next) : isInOrder(next, current));
|
|
53
|
+
if (fitsPrevious && fitsNext) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
const values = {
|
|
57
|
+
previous: previous === null ? "" : displayOf(String(rows[previousIndex][field])),
|
|
58
|
+
previousRow: previousIndex + 1,
|
|
59
|
+
next: next === null ? "" : displayOf(String(rows[nextIndex][field])),
|
|
60
|
+
nextRow: nextIndex + 1
|
|
61
|
+
};
|
|
62
|
+
let key = "validation:listOrderBetween";
|
|
63
|
+
if (fitsNext) {
|
|
64
|
+
key = ascending ? "validation:listOrderAbovePrevious" : "validation:listOrderBelowPrevious";
|
|
65
|
+
} else if (fitsPrevious) {
|
|
66
|
+
key = ascending ? "validation:listOrderBelowNext" : "validation:listOrderAboveNext";
|
|
67
|
+
}
|
|
68
|
+
return context.createError({ message: { key, values } });
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function listTotalOf(rows, field, { currency } = {}) {
|
|
73
|
+
return (rows ?? []).reduce((total, row) => {
|
|
74
|
+
try {
|
|
75
|
+
const text = String(row?.[field] ?? "").trim();
|
|
76
|
+
return text ? total.plus(Money.fromString(text, { currency })) : total;
|
|
77
|
+
} catch {
|
|
78
|
+
return total;
|
|
79
|
+
}
|
|
80
|
+
}, Money.zero({ currency }));
|
|
81
|
+
}
|
|
82
|
+
function listTotalAtMostRule(field, max, { currency } = {}) {
|
|
83
|
+
return {
|
|
84
|
+
name: "listTotalAtMost",
|
|
85
|
+
message: { key: "validation:listTotalMax" },
|
|
86
|
+
test: (rows, context) => {
|
|
87
|
+
const maxText = typeof max === "function" ? max(context.parent ?? {}) : max;
|
|
88
|
+
if (maxText === "" || maxText === null || maxText === void 0) {
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
let limit;
|
|
92
|
+
try {
|
|
93
|
+
limit = Money.fromString(String(maxText), { currency });
|
|
94
|
+
} catch {
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
const total = listTotalOf(Array.isArray(rows) ? rows : [], field, { currency });
|
|
98
|
+
if (!total.isGreaterThan(limit)) {
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
return context.createError({ message: { key: "validation:listTotalMax", values: { total: total.format(), max: limit.format() } } });
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
export {
|
|
106
|
+
listTotalAtMostRule,
|
|
107
|
+
listTotalOf,
|
|
108
|
+
orderedInListRule,
|
|
109
|
+
uniqueInListRule
|
|
110
|
+
};
|
|
111
|
+
//# sourceMappingURL=listRules.js.map
|