@stackcraftmx/calcita 0.47.0 → 0.52.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 +192 -13
- package/cli/calcita.js +17 -0
- package/cli/checkTexts.js +266 -0
- package/cli/packageVersions.js +6 -1
- package/cli/projectFiles.js +4 -2
- package/cli/templates/project/README.md.tpl +21 -1
- package/cli/templates/project/eslint.config.js.tpl +27 -0
- package/cli/templates/project/src/app/appTexts.js.tpl +2 -1
- package/cli/templates/project/src/core/publicPaths.js.tpl +10 -2
- package/cli/templates/project/vite.config.js.tpl +8 -1
- package/dist/DataTable/AuditTrailDialog.js +3 -2
- package/dist/DataTable/AuditTrailDialog.js.map +1 -1
- package/dist/DataTable/ColumnFilters/HeaderFilterPopover.js +3 -2
- package/dist/DataTable/ColumnFilters/HeaderFilterPopover.js.map +1 -1
- package/dist/DataTable/ColumnFilters/MultiSelectColumnFilter.js +3 -2
- package/dist/DataTable/ColumnFilters/MultiSelectColumnFilter.js.map +1 -1
- package/dist/DataTable/ColumnFilters/SortableFilterHeader.js +3 -2
- package/dist/DataTable/ColumnFilters/SortableFilterHeader.js.map +1 -1
- package/dist/DataTable/EmptyState.js +3 -2
- package/dist/DataTable/EmptyState.js.map +1 -1
- package/dist/DataTable/FilterChips.js +3 -2
- package/dist/DataTable/FilterChips.js.map +1 -1
- package/dist/DataTable/GoToPage.js +3 -2
- package/dist/DataTable/GoToPage.js.map +1 -1
- package/dist/DataTable/ServerTable.js +95 -28
- package/dist/DataTable/ServerTable.js.map +1 -1
- package/dist/DataTable/ServerTableFilterDrawer.js +8 -3
- package/dist/DataTable/ServerTableFilterDrawer.js.map +1 -1
- package/dist/DataTable/StackedCell.js +60 -0
- package/dist/DataTable/StackedCell.js.map +1 -0
- package/dist/DataTable/TableCardsView.js +88 -0
- package/dist/DataTable/TableCardsView.js.map +1 -0
- package/dist/DataTable/columnWidths.js +30 -0
- package/dist/DataTable/columnWidths.js.map +1 -0
- package/dist/DataTable/columns.js +47 -1
- package/dist/DataTable/columns.js.map +1 -1
- package/dist/DataTable/fixedColumns.js +24 -0
- package/dist/DataTable/fixedColumns.js.map +1 -0
- package/dist/DataTable/useTableView.js +29 -0
- package/dist/DataTable/useTableView.js.map +1 -0
- package/dist/access/PermissionMatrix.js +3 -2
- package/dist/access/PermissionMatrix.js.map +1 -1
- package/dist/account/AccountProvider.js +1 -1
- package/dist/account/AccountSecuritySection.js +3 -2
- package/dist/account/AccountSecuritySection.js.map +1 -1
- package/dist/account/DemoTotpCodeButton.js +3 -2
- package/dist/account/DemoTotpCodeButton.js.map +1 -1
- package/dist/account/ProfileScreen.js +3 -2
- package/dist/account/ProfileScreen.js.map +1 -1
- package/dist/account/SignInScreen.js +3 -2
- package/dist/account/SignInScreen.js.map +1 -1
- package/dist/appShell/AccountMenu.js +3 -2
- package/dist/appShell/AccountMenu.js.map +1 -1
- package/dist/appShell/AppLayout.js +3 -2
- package/dist/appShell/AppLayout.js.map +1 -1
- package/dist/appShell/AppearanceSettings.js +3 -2
- package/dist/appShell/AppearanceSettings.js.map +1 -1
- package/dist/appShell/AuthLayout.js +3 -2
- package/dist/appShell/AuthLayout.js.map +1 -1
- package/dist/appShell/BarActionButton.js +11 -4
- package/dist/appShell/BarActionButton.js.map +1 -1
- package/dist/appShell/BarActions.js +3 -2
- package/dist/appShell/BarActions.js.map +1 -1
- package/dist/appShell/BarCapsule.js +3 -2
- package/dist/appShell/BarCapsule.js.map +1 -1
- package/dist/appShell/BarSearch.js +3 -2
- package/dist/appShell/BarSearch.js.map +1 -1
- package/dist/appShell/HeaderTools.js +3 -2
- package/dist/appShell/HeaderTools.js.map +1 -1
- package/dist/appShell/MobileTabBar.js +3 -2
- package/dist/appShell/MobileTabBar.js.map +1 -1
- package/dist/appShell/SettingsMenu.js +3 -2
- package/dist/appShell/SettingsMenu.js.map +1 -1
- package/dist/appShell/SideMenu.js +3 -2
- package/dist/appShell/SideMenu.js.map +1 -1
- package/dist/appShell/SideMenuEntry.js +3 -2
- package/dist/appShell/SideMenuEntry.js.map +1 -1
- package/dist/appShell/SubmenuActionsMenu.js +3 -2
- package/dist/appShell/SubmenuActionsMenu.js.map +1 -1
- package/dist/appShell/TabBarEditor.js +3 -2
- package/dist/appShell/TabBarEditor.js.map +1 -1
- package/dist/appShell/TopMenu.js +3 -2
- package/dist/appShell/TopMenu.js.map +1 -1
- package/dist/appShell/Wordmark.js +3 -2
- package/dist/appShell/Wordmark.js.map +1 -1
- package/dist/auth/PasswordChecklist.js +3 -2
- package/dist/auth/PasswordChecklist.js.map +1 -1
- package/dist/calendar/EventCalendar.js +3 -2
- package/dist/calendar/EventCalendar.js.map +1 -1
- package/dist/calendar/EventDetails.js +3 -2
- package/dist/calendar/EventDetails.js.map +1 -1
- package/dist/datePickers/DateInput.js +28 -22
- package/dist/datePickers/DateInput.js.map +1 -1
- package/dist/datePickers/DateTimeInput.js +33 -27
- package/dist/datePickers/DateTimeInput.js.map +1 -1
- package/dist/datePickers/MonthInput.js +27 -21
- package/dist/datePickers/MonthInput.js.map +1 -1
- package/dist/datePickers/TimeInput.js +26 -20
- package/dist/datePickers/TimeInput.js.map +1 -1
- package/dist/datePickers/usePickerField.js +5 -4
- package/dist/datePickers/usePickerField.js.map +1 -1
- package/dist/downloads/DownloadManager.js +3 -2
- package/dist/downloads/DownloadManager.js.map +1 -1
- package/dist/feedback/ConfirmDialog.js +3 -2
- package/dist/feedback/ConfirmDialog.js.map +1 -1
- package/dist/feedback/ModalWaiting.js +3 -2
- package/dist/feedback/ModalWaiting.js.map +1 -1
- package/dist/feedback/NewBuildNotice.js +3 -2
- package/dist/feedback/NewBuildNotice.js.map +1 -1
- package/dist/filepond/FileDropzone.js +3 -2
- package/dist/filepond/FileDropzone.js.map +1 -1
- package/dist/forms/FormProblemAlert.js +3 -2
- package/dist/forms/FormProblemAlert.js.map +1 -1
- package/dist/forms/FormWizard.js +21 -6
- package/dist/forms/FormWizard.js.map +1 -1
- package/dist/forms/SubmitButton.js +7 -2
- package/dist/forms/SubmitButton.js.map +1 -1
- package/dist/forms/auth/SignInForm.js +3 -2
- package/dist/forms/auth/SignInForm.js.map +1 -1
- package/dist/forms/fieldRules.js +30 -0
- package/dist/forms/fieldRules.js.map +1 -0
- package/dist/forms/security/SecondStepForm.js +3 -2
- package/dist/forms/security/SecondStepForm.js.map +1 -1
- package/dist/forms/security/TwoStepVerificationCard.js +3 -2
- package/dist/forms/security/TwoStepVerificationCard.js.map +1 -1
- package/dist/forms/useValidationText.js +18 -0
- package/dist/forms/useValidationText.js.map +1 -0
- package/dist/forms/validation.js +35 -31
- package/dist/forms/validation.js.map +1 -1
- package/dist/forms/validationMessages.js +64 -7
- package/dist/forms/validationMessages.js.map +1 -1
- package/dist/forms.js +3 -0
- package/dist/forms.js.map +1 -1
- package/dist/hookForm/FormProblemAlert.js +3 -2
- package/dist/hookForm/FormProblemAlert.js.map +1 -1
- package/dist/hookForm/FormWizard.js +19 -5
- package/dist/hookForm/FormWizard.js.map +1 -1
- package/dist/hookForm/SubmitButton.js +7 -2
- package/dist/hookForm/SubmitButton.js.map +1 -1
- package/dist/inbox/InboxLayout.js +3 -2
- package/dist/inbox/InboxLayout.js.map +1 -1
- package/dist/inbox/MessageList.js +3 -2
- package/dist/inbox/MessageList.js.map +1 -1
- package/dist/inbox/MessageView.js +3 -2
- package/dist/inbox/MessageView.js.map +1 -1
- package/dist/index.js +193 -163
- package/dist/index.js.map +1 -1
- package/dist/inputs/CatalogInput.js +32 -25
- package/dist/inputs/CatalogInput.js.map +1 -1
- package/dist/inputs/CheckboxGroupInput.js +8 -5
- package/dist/inputs/CheckboxGroupInput.js.map +1 -1
- package/dist/inputs/ColorInput.js +7 -4
- package/dist/inputs/ColorInput.js.map +1 -1
- package/dist/inputs/FieldFrame.js +10 -4
- package/dist/inputs/FieldFrame.js.map +1 -1
- package/dist/inputs/FormSection.js +1 -1
- package/dist/inputs/MaskedInput.js +1 -0
- package/dist/inputs/MaskedInput.js.map +1 -1
- package/dist/inputs/NumberInput.js +29 -24
- package/dist/inputs/NumberInput.js.map +1 -1
- package/dist/inputs/RepeatingFields.js +8 -5
- package/dist/inputs/RepeatingFields.js.map +1 -1
- package/dist/inputs/SelectInput.js +8 -5
- package/dist/inputs/SelectInput.js.map +1 -1
- package/dist/inputs/TextInput.js +5 -3
- package/dist/inputs/TextInput.js.map +1 -1
- package/dist/inputs/TransferListInput.js +8 -5
- package/dist/inputs/TransferListInput.js.map +1 -1
- package/dist/inputs/WizardLayout.js +48 -19
- package/dist/inputs/WizardLayout.js.map +1 -1
- package/dist/inputs/useWizardSteps.js +36 -0
- package/dist/inputs/useWizardSteps.js.map +1 -0
- package/dist/inputs/wizardSteps.js +32 -0
- package/dist/inputs/wizardSteps.js.map +1 -0
- package/dist/kanban/KanbanBoard.js +3 -2
- package/dist/kanban/KanbanBoard.js.map +1 -1
- package/dist/legal/CookieNotice.js +3 -2
- package/dist/legal/CookieNotice.js.map +1 -1
- package/dist/legal/TermsAcceptanceDialog.js +3 -2
- package/dist/legal/TermsAcceptanceDialog.js.map +1 -1
- package/dist/locales/en.js +24 -0
- package/dist/locales/en.js.map +1 -1
- package/dist/locales/es.js +24 -0
- package/dist/locales/es.js.map +1 -1
- package/dist/maps/MapView.js +3 -2
- package/dist/maps/MapView.js.map +1 -1
- package/dist/mexico/CfdiSummary.js +3 -2
- package/dist/mexico/CfdiSummary.js.map +1 -1
- package/dist/mexico/EfirmaFields.js +3 -2
- package/dist/mexico/EfirmaFields.js.map +1 -1
- package/dist/mexico/mexicoIdentifiers.js +5 -0
- package/dist/mexico/mexicoIdentifiers.js.map +1 -1
- package/dist/mexico/mexicoRules.js +9 -4
- package/dist/mexico/mexicoRules.js.map +1 -1
- package/dist/mexico.js +4 -2
- package/dist/pwa/InstallAppSetting.js +3 -2
- package/dist/pwa/InstallAppSetting.js.map +1 -1
- package/dist/pwa/InstallInstructionsDialog.js +3 -2
- package/dist/pwa/InstallInstructionsDialog.js.map +1 -1
- package/dist/pwa/InstallPromptBanner.js +3 -2
- package/dist/pwa/InstallPromptBanner.js.map +1 -1
- package/dist/react-hook-form.js +3 -0
- package/dist/react-hook-form.js.map +1 -1
- package/dist/richText/RichTextEditor.js +7 -4
- package/dist/richText/RichTextEditor.js.map +1 -1
- package/dist/richText/RichTextView.js +3 -2
- package/dist/richText/RichTextView.js.map +1 -1
- package/dist/security/ActivityLog.js +3 -2
- package/dist/security/ActivityLog.js.map +1 -1
- package/dist/security/RecoveryCodesDialog.js +3 -2
- package/dist/security/RecoveryCodesDialog.js.map +1 -1
- package/dist/security/SessionsCard.js +3 -2
- package/dist/security/SessionsCard.js.map +1 -1
- package/dist/table.js +21 -2
- package/dist/table.js.map +1 -1
- package/dist/theme/themePieces.js +70 -4
- package/dist/theme/themePieces.js.map +1 -1
- package/dist/toolkit/ToolkitProvider.js +8 -1
- package/dist/toolkit/ToolkitProvider.js.map +1 -1
- package/dist/toolkit/browserNavigation.js +19 -4
- package/dist/toolkit/browserNavigation.js.map +1 -1
- package/dist/toolkit/combineTexts.js +32 -0
- package/dist/toolkit/combineTexts.js.map +1 -0
- package/dist/toolkit/hashNavigation.js +17 -3
- package/dist/toolkit/hashNavigation.js.map +1 -1
- package/dist/toolkit/navigationPaths.js +15 -0
- package/dist/toolkit/navigationPaths.js.map +1 -1
- package/dist/toolkit/pathIndex.js +66 -0
- package/dist/toolkit/pathIndex.js.map +1 -0
- package/dist/toolkit/toolkitSettings.js +19 -1
- package/dist/toolkit/toolkitSettings.js.map +1 -1
- package/dist/types/DataTable/AuditTrailDialog.d.ts +6 -17
- package/dist/types/DataTable/ColumnFilters/HeaderFilterPopover.d.ts +6 -5
- package/dist/types/DataTable/ColumnFilters/MultiSelectColumnFilter.d.ts +6 -9
- package/dist/types/DataTable/ColumnFilters/SortableFilterHeader.d.ts +6 -14
- package/dist/types/DataTable/EmptyState.d.ts +6 -9
- package/dist/types/DataTable/FilterChips.d.ts +6 -11
- package/dist/types/DataTable/GoToPage.d.ts +6 -9
- package/dist/types/DataTable/ServerTable.d.ts +16 -80
- package/dist/types/DataTable/ServerTableFilterDrawer.d.ts +6 -13
- package/dist/types/DataTable/StackedCell.d.ts +12 -0
- package/dist/types/DataTable/TableCardsView.d.ts +27 -0
- package/dist/types/DataTable/columnWidths.d.ts +21 -0
- package/dist/types/DataTable/columns.d.ts +111 -0
- package/dist/types/DataTable/fixedColumns.d.ts +29 -0
- package/dist/types/DataTable/index.d.ts +6 -1
- package/dist/types/DataTable/useTableView.d.ts +22 -0
- package/dist/types/access/PermissionMatrix.d.ts +6 -10
- package/dist/types/account/AccountSecuritySection.d.ts +6 -7
- package/dist/types/account/DemoTotpCodeButton.d.ts +6 -5
- package/dist/types/account/ProfileScreen.d.ts +6 -18
- package/dist/types/account/SignInScreen.d.ts +6 -13
- package/dist/types/appShell/AccountMenu.d.ts +6 -11
- package/dist/types/appShell/AppLayout.d.ts +3 -40
- package/dist/types/appShell/AppearanceSettings.d.ts +6 -12
- package/dist/types/appShell/AuthLayout.d.ts +3 -11
- package/dist/types/appShell/BarActionButton.d.ts +7 -10
- package/dist/types/appShell/BarActions.d.ts +6 -15
- package/dist/types/appShell/BarCapsule.d.ts +6 -9
- package/dist/types/appShell/BarSearch.d.ts +6 -15
- package/dist/types/appShell/HeaderTools.d.ts +6 -16
- package/dist/types/appShell/MobileTabBar.d.ts +6 -13
- package/dist/types/appShell/SettingsMenu.d.ts +6 -10
- package/dist/types/appShell/SideMenu.d.ts +6 -20
- package/dist/types/appShell/SideMenuEntry.d.ts +6 -11
- package/dist/types/appShell/SubmenuActionsMenu.d.ts +6 -12
- package/dist/types/appShell/TabBarEditor.d.ts +6 -9
- package/dist/types/appShell/TopMenu.d.ts +6 -5
- package/dist/types/appShell/Wordmark.d.ts +6 -7
- package/dist/types/auth/PasswordChecklist.d.ts +6 -9
- package/dist/types/calendar/EventCalendar.d.ts +6 -23
- package/dist/types/calendar/EventDetails.d.ts +6 -10
- package/dist/types/datePickers/DateInput.d.ts +7 -22
- package/dist/types/datePickers/DateTimeInput.d.ts +7 -27
- package/dist/types/datePickers/MonthInput.d.ts +7 -21
- package/dist/types/datePickers/TimeInput.d.ts +7 -21
- package/dist/types/datePickers/usePickerField.d.ts +5 -1
- package/dist/types/downloads/DownloadManager.d.ts +6 -6
- package/dist/types/feedback/ConfirmDialog.d.ts +6 -18
- package/dist/types/feedback/ModalWaiting.d.ts +6 -9
- package/dist/types/feedback/NewBuildNotice.d.ts +6 -10
- package/dist/types/filepond/FileDropzone.d.ts +6 -29
- package/dist/types/forms/FormProblemAlert.d.ts +6 -5
- package/dist/types/forms/FormWizard.d.ts +12 -1
- package/dist/types/forms/SubmitButton.d.ts +6 -6
- package/dist/types/forms/auth/SignInForm.d.ts +6 -11
- package/dist/types/forms/fieldRules.d.ts +44 -0
- package/dist/types/forms/index.d.ts +1 -0
- package/dist/types/forms/security/SecondStepForm.d.ts +6 -14
- package/dist/types/forms/security/TwoStepVerificationCard.d.ts +6 -21
- package/dist/types/forms/useValidationText.d.ts +12 -0
- package/dist/types/forms/validation.d.ts +117 -174
- package/dist/types/forms/validationMessages.d.ts +54 -6
- package/dist/types/forms.d.ts +1 -1
- package/dist/types/hookForm/FormProblemAlert.d.ts +6 -5
- package/dist/types/hookForm/FormWizard.d.ts +12 -1
- package/dist/types/hookForm/SubmitButton.d.ts +6 -13
- package/dist/types/inbox/InboxLayout.d.ts +6 -17
- package/dist/types/inbox/MessageList.d.ts +6 -18
- package/dist/types/inbox/MessageView.d.ts +6 -14
- package/dist/types/index.d.ts +10 -2
- package/dist/types/inputs/CatalogInput.d.ts +7 -24
- package/dist/types/inputs/CheckboxGroupInput.d.ts +7 -18
- package/dist/types/inputs/ColorInput.d.ts +7 -16
- package/dist/types/inputs/FieldFrame.d.ts +6 -10
- package/dist/types/inputs/MaskedInput.d.ts +3 -0
- package/dist/types/inputs/NumberInput.d.ts +7 -20
- package/dist/types/inputs/RepeatingFields.d.ts +7 -28
- package/dist/types/inputs/SelectInput.d.ts +7 -20
- package/dist/types/inputs/TextInput.d.ts +3 -0
- package/dist/types/inputs/TransferListInput.d.ts +7 -20
- package/dist/types/inputs/WizardLayout.d.ts +11 -21
- package/dist/types/inputs/useWizardSteps.d.ts +23 -0
- package/dist/types/inputs/wizardSteps.d.ts +65 -0
- package/dist/types/kanban/KanbanBoard.d.ts +6 -25
- package/dist/types/legal/CookieNotice.d.ts +6 -16
- package/dist/types/legal/TermsAcceptanceDialog.d.ts +6 -18
- package/dist/types/locales/en.d.ts +24 -0
- package/dist/types/locales/es.d.ts +24 -0
- package/dist/types/maps/MapView.d.ts +6 -17
- package/dist/types/mexico/CfdiSummary.d.ts +6 -11
- package/dist/types/mexico/EfirmaFields.d.ts +6 -16
- package/dist/types/mexico/mexicoIdentifiers.d.ts +7 -0
- package/dist/types/mexico/mexicoRules.d.ts +19 -3
- package/dist/types/mexico.d.ts +2 -2
- package/dist/types/pwa/InstallAppSetting.d.ts +6 -6
- package/dist/types/pwa/InstallInstructionsDialog.d.ts +6 -9
- package/dist/types/pwa/InstallPromptBanner.d.ts +6 -11
- package/dist/types/react-hook-form.d.ts +1 -0
- package/dist/types/richText/RichTextEditor.d.ts +7 -22
- package/dist/types/richText/RichTextView.d.ts +6 -9
- package/dist/types/security/ActivityLog.d.ts +6 -9
- package/dist/types/security/RecoveryCodesDialog.d.ts +6 -11
- package/dist/types/security/SessionsCard.d.ts +6 -11
- package/dist/types/table.d.ts +3 -1
- package/dist/types/theme/themePieces.d.ts +55 -3
- package/dist/types/toolkit/ToolkitProvider.d.ts +13 -1
- package/dist/types/toolkit/browserNavigation.d.ts +7 -3
- package/dist/types/toolkit/combineTexts.d.ts +6 -0
- package/dist/types/toolkit/hashNavigation.d.ts +4 -2
- package/dist/types/toolkit/navigationPaths.d.ts +35 -0
- package/dist/types/toolkit/pathIndex.d.ts +27 -0
- package/dist/types/toolkit/toolkitSettings.d.ts +7 -4
- package/dist/types/ui/actions/ActionsMenu.d.ts +9 -11
- package/dist/types/ui/actions/CopyButton.d.ts +6 -12
- package/dist/types/ui/actions/DownloadMenu.d.ts +6 -29
- package/dist/types/ui/actions/SecretField.d.ts +6 -11
- package/dist/types/ui/actions/SelectionActions.d.ts +6 -13
- package/dist/types/ui/cards/RecordCard.d.ts +16 -29
- package/dist/types/ui/cards/RecordCardSkeleton.d.ts +6 -11
- package/dist/types/ui/countdown/Countdown.d.ts +6 -15
- package/dist/types/ui/documents/PdfViewerDialog.d.ts +6 -13
- package/dist/types/ui/filters/DateRangeFilter.d.ts +6 -10
- package/dist/types/ui/filters/FilterDrawer.d.ts +6 -17
- package/dist/types/ui/filters/PeriodRangeSelector.d.ts +6 -13
- package/dist/types/ui/filters/QuickFilters.d.ts +6 -12
- package/dist/types/ui/help/HelpButton.d.ts +6 -10
- package/dist/types/ui/help/HelpCarouselDialog.d.ts +6 -14
- package/dist/types/ui/index.d.ts +1 -1
- package/dist/types/ui/labels/MarkedText.d.ts +10 -0
- package/dist/types/ui/labels/Tag.d.ts +6 -12
- package/dist/types/ui/labels/markedTextPieces.d.ts +22 -0
- package/dist/types/ui/layout/CollapsibleSection.d.ts +6 -12
- package/dist/types/ui/layout/ContentDialog.d.ts +6 -10
- package/dist/types/ui/layout/DefinitionList.d.ts +6 -9
- package/dist/types/ui/layout/DetailHeader.d.ts +6 -10
- package/dist/types/ui/layout/ErrorPage.d.ts +6 -17
- package/dist/types/ui/layout/FullPageLoader.d.ts +6 -6
- package/dist/types/ui/layout/GlassPanel.d.ts +6 -8
- package/dist/types/ui/layout/PageHeader.d.ts +6 -10
- package/dist/types/ui/layout/PageHeading.d.ts +6 -11
- package/dist/types/ui/layout/ProfileHeader.d.ts +6 -11
- package/dist/types/ui/layout/ScrollableRow.d.ts +6 -13
- package/dist/types/ui/layout/SectionCard.d.ts +6 -8
- package/dist/types/ui/metrics/BarList.d.ts +6 -12
- package/dist/types/ui/metrics/CalendarHeatmap.d.ts +6 -19
- package/dist/types/ui/metrics/ChartTooltip.d.ts +6 -6
- package/dist/types/ui/metrics/ColumnChart.d.ts +6 -25
- package/dist/types/ui/metrics/LineChart.d.ts +6 -20
- package/dist/types/ui/metrics/MetricCard.d.ts +6 -17
- package/dist/types/ui/metrics/TableToggle.d.ts +6 -6
- package/dist/types/ui/metrics/chartParts.d.ts +15 -30
- package/dist/types/ui/navigation/BackButton.d.ts +7 -9
- package/dist/types/ui/navigation/ColorSchemeToggle.d.ts +6 -9
- package/dist/types/ui/navigation/FontSizeToggle.d.ts +6 -8
- package/dist/types/ui/navigation/LanguageToggle.d.ts +6 -8
- package/dist/types/ui/navigation/PageBreadcrumbs.d.ts +6 -7
- package/dist/types/ui/navigation/RouteTabs.d.ts +7 -12
- package/dist/types/ui/timeline/GroupedTimeline.d.ts +6 -23
- package/dist/types/upload/AttachmentsInput.d.ts +11 -1
- package/dist/types/upload/FileDropzone.d.ts +6 -21
- package/dist/types/upload/FileInput.d.ts +7 -24
- package/dist/types/upload/FileUploadInput.d.ts +7 -31
- package/dist/types/upload/ImageInput.d.ts +7 -25
- package/dist/types/upload/TemplateDownloadButton.d.ts +6 -18
- package/dist/ui/actions/ActionsMenu.js +14 -5
- package/dist/ui/actions/ActionsMenu.js.map +1 -1
- package/dist/ui/actions/CopyButton.js +8 -3
- package/dist/ui/actions/CopyButton.js.map +1 -1
- package/dist/ui/actions/DownloadMenu.js +3 -2
- package/dist/ui/actions/DownloadMenu.js.map +1 -1
- package/dist/ui/actions/SecretField.js +8 -3
- package/dist/ui/actions/SecretField.js.map +1 -1
- package/dist/ui/actions/SelectionActions.js +3 -2
- package/dist/ui/actions/SelectionActions.js.map +1 -1
- package/dist/ui/cards/RecordCard.js +10 -5
- package/dist/ui/cards/RecordCard.js.map +1 -1
- package/dist/ui/cards/RecordCardSkeleton.js +8 -3
- package/dist/ui/cards/RecordCardSkeleton.js.map +1 -1
- package/dist/ui/countdown/Countdown.js +3 -2
- package/dist/ui/countdown/Countdown.js.map +1 -1
- package/dist/ui/documents/PdfViewerDialog.js +3 -2
- package/dist/ui/documents/PdfViewerDialog.js.map +1 -1
- package/dist/ui/filters/DateRangeFilter.js +3 -2
- package/dist/ui/filters/DateRangeFilter.js.map +1 -1
- package/dist/ui/filters/FilterDrawer.js +3 -2
- package/dist/ui/filters/FilterDrawer.js.map +1 -1
- package/dist/ui/filters/PeriodRangeSelector.js +3 -2
- package/dist/ui/filters/PeriodRangeSelector.js.map +1 -1
- package/dist/ui/filters/QuickFilters.js +3 -2
- package/dist/ui/filters/QuickFilters.js.map +1 -1
- package/dist/ui/help/HelpButton.js +8 -3
- package/dist/ui/help/HelpButton.js.map +1 -1
- package/dist/ui/help/HelpCarouselDialog.js +3 -2
- package/dist/ui/help/HelpCarouselDialog.js.map +1 -1
- package/dist/ui/labels/MarkedText.js +31 -0
- package/dist/ui/labels/MarkedText.js.map +1 -0
- package/dist/ui/labels/Tag.js +3 -2
- package/dist/ui/labels/Tag.js.map +1 -1
- package/dist/ui/labels/markedTextPieces.js +64 -0
- package/dist/ui/labels/markedTextPieces.js.map +1 -0
- package/dist/ui/layout/CollapsibleSection.js +3 -2
- package/dist/ui/layout/CollapsibleSection.js.map +1 -1
- package/dist/ui/layout/ContentDialog.js +3 -2
- package/dist/ui/layout/ContentDialog.js.map +1 -1
- package/dist/ui/layout/DefinitionList.js +3 -2
- package/dist/ui/layout/DefinitionList.js.map +1 -1
- package/dist/ui/layout/DetailHeader.js +3 -2
- package/dist/ui/layout/DetailHeader.js.map +1 -1
- package/dist/ui/layout/ErrorPage.js +3 -2
- package/dist/ui/layout/ErrorPage.js.map +1 -1
- package/dist/ui/layout/FullPageLoader.js +3 -2
- package/dist/ui/layout/FullPageLoader.js.map +1 -1
- package/dist/ui/layout/GlassPanel.js +3 -2
- package/dist/ui/layout/GlassPanel.js.map +1 -1
- package/dist/ui/layout/PageHeader.js +3 -2
- package/dist/ui/layout/PageHeader.js.map +1 -1
- package/dist/ui/layout/PageHeading.js +3 -2
- package/dist/ui/layout/PageHeading.js.map +1 -1
- package/dist/ui/layout/ProfileHeader.js +3 -2
- package/dist/ui/layout/ProfileHeader.js.map +1 -1
- package/dist/ui/layout/ScrollableRow.js +3 -2
- package/dist/ui/layout/ScrollableRow.js.map +1 -1
- package/dist/ui/layout/SectionCard.js +3 -2
- package/dist/ui/layout/SectionCard.js.map +1 -1
- package/dist/ui/metrics/BarList.js +3 -2
- package/dist/ui/metrics/BarList.js.map +1 -1
- package/dist/ui/metrics/CalendarHeatmap.js +3 -2
- package/dist/ui/metrics/CalendarHeatmap.js.map +1 -1
- package/dist/ui/metrics/ChartTooltip.js +3 -2
- package/dist/ui/metrics/ChartTooltip.js.map +1 -1
- package/dist/ui/metrics/ColumnChart.js +3 -2
- package/dist/ui/metrics/ColumnChart.js.map +1 -1
- package/dist/ui/metrics/LineChart.js +3 -2
- package/dist/ui/metrics/LineChart.js.map +1 -1
- package/dist/ui/metrics/MetricCard.js +3 -2
- package/dist/ui/metrics/MetricCard.js.map +1 -1
- package/dist/ui/metrics/TableToggle.js +3 -2
- package/dist/ui/metrics/TableToggle.js.map +1 -1
- package/dist/ui/metrics/chartParts.js +7 -4
- package/dist/ui/metrics/chartParts.js.map +1 -1
- package/dist/ui/navigation/BackButton.js +7 -2
- package/dist/ui/navigation/BackButton.js.map +1 -1
- package/dist/ui/navigation/ColorSchemeToggle.js +3 -2
- package/dist/ui/navigation/ColorSchemeToggle.js.map +1 -1
- package/dist/ui/navigation/FontSizeToggle.js +3 -2
- package/dist/ui/navigation/FontSizeToggle.js.map +1 -1
- package/dist/ui/navigation/LanguageToggle.js +3 -2
- package/dist/ui/navigation/LanguageToggle.js.map +1 -1
- package/dist/ui/navigation/PageBreadcrumbs.js +3 -2
- package/dist/ui/navigation/PageBreadcrumbs.js.map +1 -1
- package/dist/ui/navigation/RouteTabs.js +8 -4
- package/dist/ui/navigation/RouteTabs.js.map +1 -1
- package/dist/ui/timeline/GroupedTimeline.js +3 -2
- package/dist/ui/timeline/GroupedTimeline.js.map +1 -1
- package/dist/upload/AttachmentsInput.js +9 -6
- package/dist/upload/AttachmentsInput.js.map +1 -1
- package/dist/upload/FileChip.js +2 -1
- package/dist/upload/FileChip.js.map +1 -1
- package/dist/upload/FileDropzone.js +5 -4
- package/dist/upload/FileDropzone.js.map +1 -1
- package/dist/upload/FileInput.js +9 -6
- package/dist/upload/FileInput.js.map +1 -1
- package/dist/upload/FileUploadInput.js +9 -6
- package/dist/upload/FileUploadInput.js.map +1 -1
- package/dist/upload/ImageInput.js +8 -5
- package/dist/upload/ImageInput.js.map +1 -1
- package/dist/upload/TemplateDownloadButton.js +3 -2
- package/dist/upload/TemplateDownloadButton.js.map +1 -1
- package/package.json +1 -1
- package/cli/templates/project/src/app/combineTexts.js.tpl +0 -31
|
@@ -5,41 +5,47 @@ import { useToolkitTranslation } from "../toolkit/useToolkitTranslation.js";
|
|
|
5
5
|
import Autocomplete from "@mui/material/Autocomplete";
|
|
6
6
|
import Checkbox from "@mui/material/Checkbox";
|
|
7
7
|
import TextField from "@mui/material/TextField";
|
|
8
|
-
import {
|
|
8
|
+
import { useValidationText } from "../forms/useValidationText.js";
|
|
9
9
|
import FieldFrame from "./FieldFrame.js";
|
|
10
10
|
import { selectAllStateOf } from "./selectAllState.js";
|
|
11
11
|
import { warnUnknownProps } from "../toolkit/unknownProps.js";
|
|
12
|
+
import { piecePart, withPieceSlots, usePieceProps } from "../theme/themePieces.js";
|
|
13
|
+
const THEME_NAME = "CalcitaCatalogInput";
|
|
14
|
+
const CatalogInputField = piecePart(THEME_NAME, "Field", Autocomplete);
|
|
12
15
|
const MAX_VISIBLE_OPTIONS = 100;
|
|
13
16
|
const SELECT_ALL_CODE = "__calcita_select_all__";
|
|
14
17
|
function plainTextOf(text) {
|
|
15
18
|
return String(text ?? "").normalize("NFD").replace(new RegExp("\\p{Diacritic}", "gu"), "").toLowerCase();
|
|
16
19
|
}
|
|
17
|
-
function
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
20
|
+
function CatalogInputContent(props) {
|
|
21
|
+
const {
|
|
22
|
+
name,
|
|
23
|
+
label,
|
|
24
|
+
value,
|
|
25
|
+
onChange,
|
|
26
|
+
onBlur,
|
|
27
|
+
options = [],
|
|
28
|
+
formatLabel,
|
|
29
|
+
error,
|
|
30
|
+
helperText = " ",
|
|
31
|
+
loading = false,
|
|
32
|
+
disabled = false,
|
|
33
|
+
inputRef,
|
|
34
|
+
placeholder,
|
|
35
|
+
multiple = false,
|
|
36
|
+
selectAll = false,
|
|
37
|
+
checkboxes = false,
|
|
38
|
+
limitTags,
|
|
39
|
+
messages,
|
|
40
|
+
texts,
|
|
41
|
+
...unknownProps
|
|
42
|
+
} = usePieceProps(props, THEME_NAME);
|
|
43
|
+
warnUnknownProps(THEME_NAME, unknownProps);
|
|
39
44
|
const { t } = useToolkitTranslation("common", texts);
|
|
45
|
+
const validationTextOf = useValidationText({ name, label, messages });
|
|
40
46
|
const generatedId = useId();
|
|
41
47
|
const inputId = `${name ?? "catalog"}-${generatedId}`;
|
|
42
|
-
const errorText =
|
|
48
|
+
const errorText = validationTextOf(error);
|
|
43
49
|
const selectedOption = multiple ? (Array.isArray(value) ? value : []).map((code) => options.find((option) => option.code === code)).filter(Boolean) : options.find((option) => option.code === value) ?? null;
|
|
44
50
|
const optionLabelOf = (option) => formatLabel ? formatLabel(option) : `${option.label} (${option.code})`;
|
|
45
51
|
const [searchText, setSearchText] = useState("");
|
|
@@ -58,7 +64,7 @@ function CatalogInput({
|
|
|
58
64
|
})();
|
|
59
65
|
const showsSelectAll = multiple && selectAll;
|
|
60
66
|
return /* @__PURE__ */ jsx(FieldFrame, { inputId, label, children: /* @__PURE__ */ jsx(
|
|
61
|
-
|
|
67
|
+
CatalogInputField,
|
|
62
68
|
{
|
|
63
69
|
id: inputId,
|
|
64
70
|
multiple,
|
|
@@ -130,6 +136,7 @@ function CatalogInput({
|
|
|
130
136
|
}
|
|
131
137
|
) });
|
|
132
138
|
}
|
|
139
|
+
const CatalogInput = withPieceSlots(CatalogInputContent, THEME_NAME);
|
|
133
140
|
export {
|
|
134
141
|
CatalogInput as default
|
|
135
142
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CatalogInput.js","sources":["../../src/inputs/CatalogInput.jsx"],"sourcesContent":["// Campo suelto que elige una clave de un catálogo ([{code, label}]): actividad económica, giro, país, banco… Se busca por la\n// clave o por el texto, sin acentos ni mayúsculas, y al formulario llega solo la clave. Con `multiple` elige varias: cada una\n// queda como chip con su ×, la lista no se cierra al elegir y al formulario llega la lista de claves en el orden elegido.\n// Con varias, además (cada una se pide):\n// selectAll «Elegir todos» arriba de la lista, con su casilla a medias si hay algunos; con algo escrito en el buscador,\n// «Elegir los N que coinciden» (todos los que coinciden, no solo los que se ven)\n// checkboxes una casilla en cada opción (se ve de un vistazo qué está elegido)\n// limitTags cuántos chips se ven con el campo sin foco; los demás, «+3 más»\n//\n// Por qué no un <select>: con catálogos de cientos de opciones no se puede usar; y con texto libre la gente inventa claves.\n// Se enseñan a lo más MAX_VISIBLE_OPTIONS coincidencias: con miles, la lista completa congela la pantalla.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useId, useState} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport Autocomplete from '@mui/material/Autocomplete';\nimport Checkbox from '@mui/material/Checkbox';\nimport TextField from '@mui/material/TextField';\n\nimport {translateValidationMessage} from '@/forms/validationMessages.js';\nimport FieldFrame from '@/inputs/FieldFrame.jsx';\nimport {selectAllStateOf} from '@/inputs/selectAllState.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\nconst MAX_VISIBLE_OPTIONS = 100;\n// la opción de «Elegir todos»: no es del catálogo y nunca llega al formulario\nconst SELECT_ALL_CODE = '__calcita_select_all__';\n\n/**\n * Un texto sin acentos ni mayúsculas, para que «credito» encuentre «Crédito».\n *\n * @param {unknown} text\n * @returns {string}\n */\nfunction plainTextOf(text) {\n return String(text ?? '').normalize('NFD').replace(/\\p{Diacritic}/gu, '').toLowerCase();\n}\n\n/**\n * @param {object} props\n * @param {string} [props.name]\n * @param {string} props.label ya traducido\n * @param {string|string[]|null|undefined} props.value la clave elegida ('' = ninguna); con multiple, la lista ([] = ninguna)\n * @param {(code: any) => void} props.onChange recibe la clave ('' al borrar); con multiple, la lista de claves\n * @param {() => void} [props.onBlur]\n * @param {Array<{code: string, label: string}>} props.options\n * @param {(option: {code: string, label: string}) => string} [props.formatLabel] por omisión «Nombre (clave)»\n * @param {string|{key: string, values?: object}} [props.error]\n * @param {string} [props.helperText]\n * @param {boolean} [props.loading] el catálogo todavía no llega\n * @param {string} [props.placeholder] la pista con el campo vacío, ya traducida (p. ej. «Busque un estado»): sin ella, en un\n * formulario agrupado el campo vacío solo enseña su botón y no se entiende qué es\n * @param {boolean} [props.disabled]\n * @param {import('react').Ref<HTMLInputElement>} [props.inputRef]\n * @param {boolean} [props.multiple] elegir varias claves\n * @param {boolean} [props.selectAll] con multiple: «Elegir todos» (o los que coinciden con lo buscado) arriba de la lista\n * @param {boolean} [props.checkboxes] con multiple: una casilla en cada opción\n * @param {number} [props.limitTags] con multiple: cuántos chips se ven sin foco; los demás como «+3 más»\n * @param {object} [props.texts] los textos que cambian, con la forma de `common` en los textos del paquete\n */\nexport default function CatalogInput({\n name, label, value, onChange, onBlur, options = [], formatLabel, error, helperText = ' ', loading = false, disabled = false, inputRef, placeholder,\n multiple = false, selectAll = false, checkboxes = false, limitTags, texts,\n ...unknownProps\n}) {\n warnUnknownProps('CatalogInput', unknownProps);\n const {t} = useToolkitTranslation('common', texts);\n const generatedId = useId();\n const inputId = `${name ?? 'catalog'}-${generatedId}`;\n const errorText = translateValidationMessage(t, error);\n // con varias, en el orden en que se eligieron (el de la lista de claves)\n const selectedOption = multiple\n ? (Array.isArray(value) ? value : []).map((code) => options.find((option) => option.code === code)).filter(Boolean)\n : options.find((option) => option.code === value) ?? null;\n const optionLabelOf = (option) => (formatLabel ? formatLabel(option) : `${option.label} (${option.code})`);\n // lo escrito en el buscador: «Elegir todos» abarca lo que coincide con eso\n const [searchText, setSearchText] = useState('');\n const matchesSearch = (option, text) => {\n const wantedText = plainTextOf(text);\n return !wantedText || plainTextOf(optionLabelOf(option)).includes(wantedText);\n };\n const selectedCodes = multiple && Array.isArray(value) ? value : [];\n const matchingCodes = options.filter((option) => matchesSearch(option, searchText)).map((option) => option.code);\n const selectAllState = selectAllStateOf(selectedCodes, matchingCodes);\n const selectAllLabel = (() => {\n if (searchText.trim()) {\n return selectAllState.allChosen ? t('form.unselectMatching', {count: matchingCodes.length}) : t('form.selectMatching', {count: matchingCodes.length});\n }\n return selectAllState.allChosen ? t('form.unselectAll') : t('form.selectAll', {count: matchingCodes.length});\n })();\n const showsSelectAll = multiple && selectAll;\n\n return (\n <FieldFrame inputId={inputId} label={label}>\n <Autocomplete\n id={inputId}\n multiple={multiple}\n disableCloseOnSelect={multiple}\n fullWidth\n size=\"small\"\n options={options}\n value={selectedOption}\n loading={loading}\n disabled={disabled}\n noOptionsText={t('form.noOptions')}\n loadingText={t('form.loadingOptions')}\n getOptionLabel={(option) => (option.code === SELECT_ALL_CODE ? selectAllLabel : optionLabelOf(option))}\n isOptionEqualToValue={(option, currentOption) => option.code === currentOption.code}\n filterOptions={(allOptions, {inputValue}) => {\n const matchingOptions = allOptions.filter((option) => matchesSearch(option, inputValue)).slice(0, MAX_VISIBLE_OPTIONS);\n // «Elegir todos» va arriba solo si hay de dónde elegir\n return showsSelectAll && matchingOptions.length > 0 ? [{code: SELECT_ALL_CODE, label: selectAllLabel}, ...matchingOptions] : matchingOptions;\n }}\n // con «Elegir todos», lo buscado se queda al elegir (MUI lo borraba): así se eligen varios de la misma búsqueda.\n // Al salir del campo sí se borra. Sin «Elegir todos», el campo se porta como siempre\n inputValue={showsSelectAll ? searchText : undefined}\n onInputChange={(event, text, reason) => {\n // MUI 7 avisa con qué borra el texto: al elegir o quitar una opción ('selectOption', 'removeOption', 'reset');\n // al salir del campo es 'blur' y ese sí se borra\n const isResetByChoosing = ['selectOption', 'removeOption', 'reset'].includes(reason) && event?.type !== 'blur';\n if (!(showsSelectAll && isResetByChoosing)) {\n setSearchText(text);\n }\n }}\n onChange={(event, option, reason, details) => {\n if (!multiple) {\n onChange(option?.code ?? '');\n return;\n }\n if (details?.option?.code === SELECT_ALL_CODE) {\n onChange(selectAllState.toggled());\n return;\n }\n onChange(option.filter((chosenOption) => chosenOption.code !== SELECT_ALL_CODE).map((chosenOption) => chosenOption.code));\n }}\n limitTags={multiple ? limitTags : undefined}\n getLimitTagsText={(hiddenCount) => t('form.moreSelected', {count: hiddenCount})}\n renderOption={multiple && (checkboxes || selectAll) ? (optionProps, option, {selected}) => {\n // OJO: key va aparte; dentro de las props React avisa en la consola\n const {key, ...liProps} = optionProps;\n const isSelectAll = option.code === SELECT_ALL_CODE;\n const showsBox = checkboxes || isSelectAll;\n return (\n <li key={key} {...liProps} {...(isSelectAll && {'aria-selected': selectAllState.allChosen})}\n style={isSelectAll ? {fontWeight: 600} : undefined}>\n {showsBox && (\n <Checkbox size=\"small\" tabIndex={-1} disableRipple sx={{p: 0, mr: 1}}\n checked={isSelectAll ? selectAllState.allChosen : selected}\n indeterminate={isSelectAll && selectAllState.someChosen} />\n )}\n {isSelectAll ? selectAllLabel : optionLabelOf(option)}\n </li>\n );\n } : undefined}\n onBlur={onBlur}\n renderInput={(inputProps) => (\n <TextField {...inputProps} name={name} inputRef={inputRef} placeholder={placeholder} error={Boolean(errorText)} helperText={errorText ?? helperText} />\n )}\n />\n </FieldFrame>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CatalogInput.js","sources":["../../src/inputs/CatalogInput.jsx"],"sourcesContent":["// Campo suelto que elige una clave de un catálogo ([{code, label}]): actividad económica, giro, país, banco… Se busca por la\n// clave o por el texto, sin acentos ni mayúsculas, y al formulario llega solo la clave. Con `multiple` elige varias: cada una\n// queda como chip con su ×, la lista no se cierra al elegir y al formulario llega la lista de claves en el orden elegido.\n// Con varias, además (cada una se pide):\n// selectAll «Elegir todos» arriba de la lista, con su casilla a medias si hay algunos; con algo escrito en el buscador,\n// «Elegir los N que coinciden» (todos los que coinciden, no solo los que se ven)\n// checkboxes una casilla en cada opción (se ve de un vistazo qué está elegido)\n// limitTags cuántos chips se ven con el campo sin foco; los demás, «+3 más»\n//\n// Por qué no un <select>: con catálogos de cientos de opciones no se puede usar; y con texto libre la gente inventa claves.\n// Se enseñan a lo más MAX_VISIBLE_OPTIONS coincidencias: con miles, la lista completa congela la pantalla.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaCatalogInput 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 {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport Autocomplete from '@mui/material/Autocomplete';\nimport Checkbox from '@mui/material/Checkbox';\nimport TextField from '@mui/material/TextField';\n\nimport {useValidationText} from '@/forms/useValidationText.js';\nimport FieldFrame from '@/inputs/FieldFrame.jsx';\nimport {selectAllStateOf} from '@/inputs/selectAllState.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.CalcitaCatalogInput)\nconst THEME_NAME = 'CalcitaCatalogInput';\nconst CatalogInputField = piecePart(THEME_NAME, 'Field', Autocomplete);\n\nconst MAX_VISIBLE_OPTIONS = 100;\n// la opción de «Elegir todos»: no es del catálogo y nunca llega al formulario\nconst SELECT_ALL_CODE = '__calcita_select_all__';\n\n/**\n * Un texto sin acentos ni mayúsculas, para que «credito» encuentre «Crédito».\n *\n * @param {unknown} text\n * @returns {string}\n */\nfunction plainTextOf(text) {\n return String(text ?? '').normalize('NFD').replace(/\\p{Diacritic}/gu, '').toLowerCase();\n}\n\n/**\n * @param {object} props\n * @param {string} [props.name]\n * @param {string} props.label ya traducido\n * @param {string|string[]|null|undefined} props.value la clave elegida ('' = ninguna); con multiple, la lista ([] = ninguna)\n * @param {(code: any) => void} props.onChange recibe la clave ('' al borrar); con multiple, la lista de claves\n * @param {() => void} [props.onBlur]\n * @param {Array<{code: string, label: string}>} props.options\n * @param {(option: {code: string, label: string}) => string} [props.formatLabel] por omisión «Nombre (clave)»\n * @param {string|{key: string, values?: object}} [props.error]\n * @param {string} [props.helperText]\n * @param {boolean} [props.loading] el catálogo todavía no llega\n * @param {string} [props.placeholder] la pista con el campo vacío, ya traducida (p. ej. «Busque un estado»): sin ella, en un\n * formulario agrupado el campo vacío solo enseña su botón y no se entiende qué es\n * @param {boolean} [props.disabled]\n * @param {import('react').Ref<HTMLInputElement>} [props.inputRef]\n * @param {boolean} [props.multiple] elegir varias claves\n * @param {boolean} [props.selectAll] con multiple: «Elegir todos» (o los que coinciden con lo buscado) arriba de la lista\n * @param {boolean} [props.checkboxes] con multiple: una casilla en cada opción\n * @param {number} [props.limitTags] con multiple: cuántos chips se ven sin foco; los demás como «+3 más»\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 CatalogInputContent(props) {\n const {\n name, label, value, onChange, onBlur, options = [], formatLabel, error, helperText = ' ', loading = false, disabled = false, inputRef, placeholder,\n multiple = false, selectAll = false, checkboxes = false, limitTags, 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 ?? 'catalog'}-${generatedId}`;\n const errorText = validationTextOf(error);\n // con varias, en el orden en que se eligieron (el de la lista de claves)\n const selectedOption = multiple\n ? (Array.isArray(value) ? value : []).map((code) => options.find((option) => option.code === code)).filter(Boolean)\n : options.find((option) => option.code === value) ?? null;\n const optionLabelOf = (option) => (formatLabel ? formatLabel(option) : `${option.label} (${option.code})`);\n // lo escrito en el buscador: «Elegir todos» abarca lo que coincide con eso\n const [searchText, setSearchText] = useState('');\n const matchesSearch = (option, text) => {\n const wantedText = plainTextOf(text);\n return !wantedText || plainTextOf(optionLabelOf(option)).includes(wantedText);\n };\n const selectedCodes = multiple && Array.isArray(value) ? value : [];\n const matchingCodes = options.filter((option) => matchesSearch(option, searchText)).map((option) => option.code);\n const selectAllState = selectAllStateOf(selectedCodes, matchingCodes);\n const selectAllLabel = (() => {\n if (searchText.trim()) {\n return selectAllState.allChosen ? t('form.unselectMatching', {count: matchingCodes.length}) : t('form.selectMatching', {count: matchingCodes.length});\n }\n return selectAllState.allChosen ? t('form.unselectAll') : t('form.selectAll', {count: matchingCodes.length});\n })();\n const showsSelectAll = multiple && selectAll;\n\n return (\n <FieldFrame inputId={inputId} label={label}>\n <CatalogInputField\n id={inputId}\n multiple={multiple}\n disableCloseOnSelect={multiple}\n fullWidth\n size=\"small\"\n options={options}\n value={selectedOption}\n loading={loading}\n disabled={disabled}\n noOptionsText={t('form.noOptions')}\n loadingText={t('form.loadingOptions')}\n getOptionLabel={(option) => (option.code === SELECT_ALL_CODE ? selectAllLabel : optionLabelOf(option))}\n isOptionEqualToValue={(option, currentOption) => option.code === currentOption.code}\n filterOptions={(allOptions, {inputValue}) => {\n const matchingOptions = allOptions.filter((option) => matchesSearch(option, inputValue)).slice(0, MAX_VISIBLE_OPTIONS);\n // «Elegir todos» va arriba solo si hay de dónde elegir\n return showsSelectAll && matchingOptions.length > 0 ? [{code: SELECT_ALL_CODE, label: selectAllLabel}, ...matchingOptions] : matchingOptions;\n }}\n // con «Elegir todos», lo buscado se queda al elegir (MUI lo borraba): así se eligen varios de la misma búsqueda.\n // Al salir del campo sí se borra. Sin «Elegir todos», el campo se porta como siempre\n inputValue={showsSelectAll ? searchText : undefined}\n onInputChange={(event, text, reason) => {\n // MUI 7 avisa con qué borra el texto: al elegir o quitar una opción ('selectOption', 'removeOption', 'reset');\n // al salir del campo es 'blur' y ese sí se borra\n const isResetByChoosing = ['selectOption', 'removeOption', 'reset'].includes(reason) && event?.type !== 'blur';\n if (!(showsSelectAll && isResetByChoosing)) {\n setSearchText(text);\n }\n }}\n onChange={(event, option, reason, details) => {\n if (!multiple) {\n onChange(option?.code ?? '');\n return;\n }\n if (details?.option?.code === SELECT_ALL_CODE) {\n onChange(selectAllState.toggled());\n return;\n }\n onChange(option.filter((chosenOption) => chosenOption.code !== SELECT_ALL_CODE).map((chosenOption) => chosenOption.code));\n }}\n limitTags={multiple ? limitTags : undefined}\n getLimitTagsText={(hiddenCount) => t('form.moreSelected', {count: hiddenCount})}\n renderOption={multiple && (checkboxes || selectAll) ? (optionProps, option, {selected}) => {\n // OJO: key va aparte; dentro de las props React avisa en la consola\n const {key, ...liProps} = optionProps;\n const isSelectAll = option.code === SELECT_ALL_CODE;\n const showsBox = checkboxes || isSelectAll;\n return (\n <li key={key} {...liProps} {...(isSelectAll && {'aria-selected': selectAllState.allChosen})}\n style={isSelectAll ? {fontWeight: 600} : undefined}>\n {showsBox && (\n <Checkbox size=\"small\" tabIndex={-1} disableRipple sx={{p: 0, mr: 1}}\n checked={isSelectAll ? selectAllState.allChosen : selected}\n indeterminate={isSelectAll && selectAllState.someChosen} />\n )}\n {isSelectAll ? selectAllLabel : optionLabelOf(option)}\n </li>\n );\n } : undefined}\n onBlur={onBlur}\n renderInput={(inputProps) => (\n <TextField {...inputProps} name={name} inputRef={inputRef} placeholder={placeholder} error={Boolean(errorText)} helperText={errorText ?? helperText} />\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 CatalogInput = withPieceSlots(CatalogInputContent, THEME_NAME);\nexport default CatalogInput;\n"],"names":[],"mappings":";;;;;;;;;;;;AA6BA,MAAM,aAAa;AACnB,MAAM,oBAAoB,UAAU,YAAY,SAAS,YAAY;AAErE,MAAM,sBAAsB;AAE5B,MAAM,kBAAkB;AAQxB,SAAS,YAAY,MAAM;AACvB,SAAO,OAAO,QAAQ,EAAE,EAAE,UAAU,KAAK,EAAE,QAAQ,WAAA,kBAAA,IAAA,GAAmB,EAAE,EAAE,YAAA;AAC9E;AA0BA,SAAS,oBAAoB,OAAO;AAChC,QAAM;AAAA,IACF;AAAA,IAAM;AAAA,IAAO;AAAA,IAAO;AAAA,IAAU;AAAA,IAAQ,UAAU,CAAA;AAAA,IAAI;AAAA,IAAa;AAAA,IAAO,aAAa;AAAA,IAAK,UAAU;AAAA,IAAO,WAAW;AAAA,IAAO;AAAA,IAAU;AAAA,IACvI,WAAW;AAAA,IAAO,YAAY;AAAA,IAAO,aAAa;AAAA,IAAO;AAAA,IAAW;AAAA,IAAU;AAAA,IAC9E,GAAG;AAAA,EAAA,IACH,cAAc,OAAO,UAAU;AACnC,mBAAiB,YAAY,YAAY;AACzC,QAAM,EAAC,EAAA,IAAK,sBAAsB,UAAU,KAAK;AAEjD,QAAM,mBAAmB,kBAAkB,EAAC,MAAM,OAAO,UAAS;AAClE,QAAM,cAAc,MAAA;AACpB,QAAM,UAAU,GAAG,QAAQ,SAAS,IAAI,WAAW;AACnD,QAAM,YAAY,iBAAiB,KAAK;AAExC,QAAM,iBAAiB,YAChB,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAA,GAAI,IAAI,CAAC,SAAS,QAAQ,KAAK,CAAC,WAAW,OAAO,SAAS,IAAI,CAAC,EAAE,OAAO,OAAO,IAChH,QAAQ,KAAK,CAAC,WAAW,OAAO,SAAS,KAAK,KAAK;AACzD,QAAM,gBAAgB,CAAC,WAAY,cAAc,YAAY,MAAM,IAAI,GAAG,OAAO,KAAK,KAAK,OAAO,IAAI;AAEtG,QAAM,CAAC,YAAY,aAAa,IAAI,SAAS,EAAE;AAC/C,QAAM,gBAAgB,CAAC,QAAQ,SAAS;AACpC,UAAM,aAAa,YAAY,IAAI;AACnC,WAAO,CAAC,cAAc,YAAY,cAAc,MAAM,CAAC,EAAE,SAAS,UAAU;AAAA,EAChF;AACA,QAAM,gBAAgB,YAAY,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAA;AACjE,QAAM,gBAAgB,QAAQ,OAAO,CAAC,WAAW,cAAc,QAAQ,UAAU,CAAC,EAAE,IAAI,CAAC,WAAW,OAAO,IAAI;AAC/G,QAAM,iBAAiB,iBAAiB,eAAe,aAAa;AACpE,QAAM,kBAAkB,MAAM;AAC1B,QAAI,WAAW,QAAQ;AACnB,aAAO,eAAe,YAAY,EAAE,yBAAyB,EAAC,OAAO,cAAc,OAAA,CAAO,IAAI,EAAE,uBAAuB,EAAC,OAAO,cAAc,QAAO;AAAA,IACxJ;AACA,WAAO,eAAe,YAAY,EAAE,kBAAkB,IAAI,EAAE,kBAAkB,EAAC,OAAO,cAAc,OAAA,CAAO;AAAA,EAC/G,GAAA;AACA,QAAM,iBAAiB,YAAY;AAEnC,SACI,oBAAC,YAAA,EAAW,SAAkB,OAC1B,UAAA;AAAA,IAAC;AAAA,IAAA;AAAA,MACG,IAAI;AAAA,MACJ;AAAA,MACA,sBAAsB;AAAA,MACtB,WAAS;AAAA,MACT,MAAK;AAAA,MACL;AAAA,MACA,OAAO;AAAA,MACP;AAAA,MACA;AAAA,MACA,eAAe,EAAE,gBAAgB;AAAA,MACjC,aAAa,EAAE,qBAAqB;AAAA,MACpC,gBAAgB,CAAC,WAAY,OAAO,SAAS,kBAAkB,iBAAiB,cAAc,MAAM;AAAA,MACpG,sBAAsB,CAAC,QAAQ,kBAAkB,OAAO,SAAS,cAAc;AAAA,MAC/E,eAAe,CAAC,YAAY,EAAC,iBAAgB;AACzC,cAAM,kBAAkB,WAAW,OAAO,CAAC,WAAW,cAAc,QAAQ,UAAU,CAAC,EAAE,MAAM,GAAG,mBAAmB;AAErH,eAAO,kBAAkB,gBAAgB,SAAS,IAAI,CAAC,EAAC,MAAM,iBAAiB,OAAO,eAAA,GAAiB,GAAG,eAAe,IAAI;AAAA,MACjI;AAAA,MAGA,YAAY,iBAAiB,aAAa;AAAA,MAC1C,eAAe,CAAC,OAAO,MAAM,WAAW;AAGpC,cAAM,oBAAoB,CAAC,gBAAgB,gBAAgB,OAAO,EAAE,SAAS,MAAM,KAAK,OAAO,SAAS;AACxG,YAAI,EAAE,kBAAkB,oBAAoB;AACxC,wBAAc,IAAI;AAAA,QACtB;AAAA,MACJ;AAAA,MACA,UAAU,CAAC,OAAO,QAAQ,QAAQ,YAAY;AAC1C,YAAI,CAAC,UAAU;AACX,mBAAS,QAAQ,QAAQ,EAAE;AAC3B;AAAA,QACJ;AACA,YAAI,SAAS,QAAQ,SAAS,iBAAiB;AAC3C,mBAAS,eAAe,SAAS;AACjC;AAAA,QACJ;AACA,iBAAS,OAAO,OAAO,CAAC,iBAAiB,aAAa,SAAS,eAAe,EAAE,IAAI,CAAC,iBAAiB,aAAa,IAAI,CAAC;AAAA,MAC5H;AAAA,MACA,WAAW,WAAW,YAAY;AAAA,MAClC,kBAAkB,CAAC,gBAAgB,EAAE,qBAAqB,EAAC,OAAO,aAAY;AAAA,MAC9E,cAAc,aAAa,cAAc,aAAa,CAAC,aAAa,QAAQ,EAAC,eAAc;AAEvF,cAAM,EAAC,KAAK,GAAG,QAAA,IAAW;AAC1B,cAAM,cAAc,OAAO,SAAS;AACpC,cAAM,WAAW,cAAc;AAC/B,eACI;AAAA,UAAC;AAAA,UAAA;AAAA,YAAc,GAAG;AAAA,YAAU,GAAI,eAAe,EAAC,iBAAiB,eAAe,UAAA;AAAA,YAC5E,OAAO,cAAc,EAAC,YAAY,QAAO;AAAA,YACxC,UAAA;AAAA,cAAA,YACG;AAAA,gBAAC;AAAA,gBAAA;AAAA,kBAAS,MAAK;AAAA,kBAAQ,UAAU;AAAA,kBAAI,eAAa;AAAA,kBAAC,IAAI,EAAC,GAAG,GAAG,IAAI,EAAA;AAAA,kBACxD,SAAS,cAAc,eAAe,YAAY;AAAA,kBAClD,eAAe,eAAe,eAAe;AAAA,gBAAA;AAAA,cAAA;AAAA,cAE1D,cAAc,iBAAiB,cAAc,MAAM;AAAA,YAAA;AAAA,UAAA;AAAA,UAP/C;AAAA,QAAA;AAAA,MAUjB,IAAI;AAAA,MACJ;AAAA,MACA,aAAa,CAAC,eACV,oBAAC,WAAA,EAAW,GAAG,YAAY,MAAY,UAAoB,aAA0B,OAAO,QAAQ,SAAS,GAAG,YAAY,aAAa,WAAA,CAAY;AAAA,IAAA;AAAA,EAAA,GAGjK;AAER;AAGA,MAAM,eAAe,eAAe,qBAAqB,UAAU;"}
|
|
@@ -7,17 +7,17 @@ import FormGroup from "@mui/material/FormGroup";
|
|
|
7
7
|
import FormHelperText from "@mui/material/FormHelperText";
|
|
8
8
|
import Stack from "@mui/material/Stack";
|
|
9
9
|
import Typography from "@mui/material/Typography";
|
|
10
|
-
import {
|
|
10
|
+
import { useValidationText } from "../forms/useValidationText.js";
|
|
11
11
|
import FieldFrame from "./FieldFrame.js";
|
|
12
12
|
import { selectAllStateOf } from "./selectAllState.js";
|
|
13
|
-
import { piecePart, usePieceProps } from "../theme/themePieces.js";
|
|
13
|
+
import { piecePart, withPieceSlots, usePieceProps } from "../theme/themePieces.js";
|
|
14
14
|
import { useToolkitTranslation } from "../toolkit/useToolkitTranslation.js";
|
|
15
15
|
import { warnUnknownProps } from "../toolkit/unknownProps.js";
|
|
16
16
|
const THEME_NAME = "CalcitaCheckboxGroupInput";
|
|
17
17
|
const CheckboxGroupRoot = piecePart(THEME_NAME, "Root", FormGroup);
|
|
18
18
|
const CheckboxGroupSelectAll = piecePart(THEME_NAME, "SelectAll", FormControlLabel);
|
|
19
19
|
const CheckboxGroupOption = piecePart(THEME_NAME, "Option", FormControlLabel);
|
|
20
|
-
function
|
|
20
|
+
function CheckboxGroupInputContent(props) {
|
|
21
21
|
const {
|
|
22
22
|
name,
|
|
23
23
|
label,
|
|
@@ -31,15 +31,17 @@ function CheckboxGroupInput(props) {
|
|
|
31
31
|
helperText = " ",
|
|
32
32
|
disabled = false,
|
|
33
33
|
sx,
|
|
34
|
+
messages,
|
|
34
35
|
texts,
|
|
35
36
|
...unknownProps
|
|
36
37
|
} = usePieceProps(props, THEME_NAME);
|
|
37
38
|
warnUnknownProps(THEME_NAME, unknownProps);
|
|
38
39
|
const { t } = useToolkitTranslation("common", texts);
|
|
40
|
+
const validationTextOf = useValidationText({ name, label, messages });
|
|
39
41
|
const generatedId = useId();
|
|
40
42
|
const groupId = `${name ?? "checkbox-group"}-${generatedId}`;
|
|
41
43
|
const labelId = `${groupId}-label`;
|
|
42
|
-
const errorText =
|
|
44
|
+
const errorText = validationTextOf(error);
|
|
43
45
|
const checkedValues = Array.isArray(value) ? value : [];
|
|
44
46
|
const selectAllState = selectAllStateOf(checkedValues, options.filter((option) => !option.disabled).map((option) => option.value));
|
|
45
47
|
const inOptionsOrder = (values) => options.map((option) => option.value).filter((optionValue) => values.includes(optionValue));
|
|
@@ -49,7 +51,7 @@ function CheckboxGroupInput(props) {
|
|
|
49
51
|
};
|
|
50
52
|
const toggle = (optionValue) => change(checkedValues.includes(optionValue) ? checkedValues.filter((checkedValue) => checkedValue !== optionValue) : [...checkedValues, optionValue]);
|
|
51
53
|
return /* @__PURE__ */ jsx(FieldFrame, { inputId: `${groupId}-first`, labelId, label, children: /* @__PURE__ */ jsxs("div", { children: [
|
|
52
|
-
/* @__PURE__ */ jsxs(CheckboxGroupRoot, { row, role: "group", "aria-labelledby": labelId, ownSx: { columnGap: 2,
|
|
54
|
+
/* @__PURE__ */ jsxs(CheckboxGroupRoot, { row, role: "group", "aria-labelledby": labelId, ownSx: { columnGap: 2 }, sx, children: [
|
|
53
55
|
selectAll && /* @__PURE__ */ jsx(
|
|
54
56
|
CheckboxGroupSelectAll,
|
|
55
57
|
{
|
|
@@ -94,6 +96,7 @@ function CheckboxGroupInput(props) {
|
|
|
94
96
|
/* @__PURE__ */ jsx(FormHelperText, { error: Boolean(errorText), children: errorText ?? helperText })
|
|
95
97
|
] }) });
|
|
96
98
|
}
|
|
99
|
+
const CheckboxGroupInput = withPieceSlots(CheckboxGroupInputContent, THEME_NAME);
|
|
97
100
|
export {
|
|
98
101
|
CheckboxGroupInput as default
|
|
99
102
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CheckboxGroupInput.js","sources":["../../src/inputs/CheckboxGroupInput.jsx"],"sourcesContent":["// El grupo de casillas: pocas opciones a la vista (los canales de aviso, los días que abre una sucursal), cada una con su\n// casilla y, si hace falta, una explicación abajo; al formulario llega la lista de las marcadas. Con pocas opciones es más\n// rápido que abrir una lista; con muchas, SelectInput o CatalogInput con `multiple`.\n//\n// selectAll pone «Elegir todos» arriba, con su casilla a medias si hay algunas marcadas. row las acomoda en renglón (en el\n// teléfono bajan solas). Dentro de una FormSection se vuelve renglón, como los demás campos.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaCheckboxGroupInput con defaultProps y\n// styleOverrides de sus partes: root, selectAll, option.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useId} from 'react';\nimport Checkbox from '@mui/material/Checkbox';\nimport FormControlLabel from '@mui/material/FormControlLabel';\nimport FormGroup from '@mui/material/FormGroup';\nimport FormHelperText from '@mui/material/FormHelperText';\nimport Stack from '@mui/material/Stack';\nimport Typography from '@mui/material/Typography';\n\nimport {
|
|
1
|
+
{"version":3,"file":"CheckboxGroupInput.js","sources":["../../src/inputs/CheckboxGroupInput.jsx"],"sourcesContent":["// El grupo de casillas: pocas opciones a la vista (los canales de aviso, los días que abre una sucursal), cada una con su\n// casilla y, si hace falta, una explicación abajo; al formulario llega la lista de las marcadas. Con pocas opciones es más\n// rápido que abrir una lista; con muchas, SelectInput o CatalogInput con `multiple`.\n//\n// selectAll pone «Elegir todos» arriba, con su casilla a medias si hay algunas marcadas. row las acomoda en renglón (en el\n// teléfono bajan solas). Dentro de una FormSection se vuelve renglón, como los demás campos.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaCheckboxGroupInput con defaultProps y\n// styleOverrides de sus partes: root, selectAll, option.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useId} from 'react';\nimport Checkbox from '@mui/material/Checkbox';\nimport FormControlLabel from '@mui/material/FormControlLabel';\nimport FormGroup from '@mui/material/FormGroup';\nimport FormHelperText from '@mui/material/FormHelperText';\nimport Stack from '@mui/material/Stack';\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 {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaCheckboxGroupInput)\nconst THEME_NAME = 'CalcitaCheckboxGroupInput';\nconst CheckboxGroupRoot = piecePart(THEME_NAME, 'Root', FormGroup);\nconst CheckboxGroupSelectAll = piecePart(THEME_NAME, 'SelectAll', FormControlLabel);\nconst CheckboxGroupOption = piecePart(THEME_NAME, 'Option', FormControlLabel);\n\n/**\n * @param {object} props\n * @param {string} [props.name]\n * @param {import('react').ReactNode} props.label ya traducida\n * @param {Array<string|number>|null|undefined} props.value las marcadas ([] = ninguna)\n * @param {(values: Array<string|number>) => void} props.onChange la lista nueva, en el orden de las opciones\n * @param {() => void} [props.onBlur]\n * @param {Array<{value: string|number, label: string, description?: string, disabled?: boolean}>} props.options\n * @param {boolean} [props.row] en renglón en lugar de una debajo de otra\n * @param {boolean} [props.selectAll] «Elegir todos» arriba\n * @param {string|{key: string, values?: object}} [props.error]\n * @param {string} [props.helperText] ya traducida\n * @param {boolean} [props.disabled]\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 */\nfunction CheckboxGroupInputContent(props) {\n const {\n name, label, value, onChange, onBlur, options, row = false, selectAll = false, error, helperText = ' ', disabled = false, sx, messages, texts,\n ...unknownProps\n } = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const {t} = useToolkitTranslation('common', texts);\n // el texto del error: el de la pantalla (messages), el del proyecto para este campo o el de Calcita\n const validationTextOf = useValidationText({name, label, messages});\n const generatedId = useId();\n const groupId = `${name ?? 'checkbox-group'}-${generatedId}`;\n const labelId = `${groupId}-label`;\n const errorText = validationTextOf(error);\n const checkedValues = Array.isArray(value) ? value : [];\n const selectAllState = selectAllStateOf(checkedValues, options.filter((option) => !option.disabled).map((option) => option.value));\n // la lista en el orden de las opciones, no en el que se fueron marcando (así viaja siempre igual)\n const inOptionsOrder = (values) => options.map((option) => option.value).filter((optionValue) => values.includes(optionValue));\n const change = (values) => {\n onChange(inOptionsOrder(values));\n onBlur?.();\n };\n const toggle = (optionValue) => change(checkedValues.includes(optionValue)\n ? checkedValues.filter((checkedValue) => checkedValue !== optionValue)\n : [...checkedValues, optionValue]);\n\n return (\n <FieldFrame inputId={`${groupId}-first`} labelId={labelId} label={label}>\n <div>\n <CheckboxGroupRoot row={row} role=\"group\" aria-labelledby={labelId} ownSx={{columnGap: 2}} sx={sx}>\n {selectAll && (\n <CheckboxGroupSelectAll\n disabled={disabled}\n label={selectAllState.allChosen ? t('form.unselectAll') : t('form.selectAll', {count: options.length})}\n control={(\n <Checkbox size=\"small\" checked={selectAllState.allChosen} indeterminate={selectAllState.someChosen}\n onChange={() => change(selectAllState.toggled())} />\n )}\n ownSx={{'& .MuiFormControlLabel-label': {fontWeight: 600}}}\n />\n )}\n {options.map((option, index) => (\n <CheckboxGroupOption\n key={option.value}\n disabled={disabled || option.disabled}\n ownSx={{alignItems: option.description ? 'flex-start' : 'center'}}\n control={(\n <Checkbox size=\"small\" name={name} id={index === 0 ? `${groupId}-first` : undefined}\n checked={checkedValues.includes(option.value)} onChange={() => toggle(option.value)}\n sx={option.description ? {mt: -0.5} : undefined} />\n )}\n label={option.description ? (\n <Stack component=\"span\">\n <Typography component=\"span\" variant=\"body2\">{option.label}</Typography>\n <Typography component=\"span\" variant=\"caption\" color=\"text.secondary\">{option.description}</Typography>\n </Stack>\n ) : option.label}\n />\n ))}\n </CheckboxGroupRoot>\n <FormHelperText error={Boolean(errorText)}>{errorText ?? helperText}</FormHelperText>\n </div>\n </FieldFrame>\n );\n}\n\n// lo que se exporta: la pieza con slots y slotProps para cambiar sus partes (themePieces.js)\nconst CheckboxGroupInput = withPieceSlots(CheckboxGroupInputContent, THEME_NAME);\nexport default CheckboxGroupInput;\n"],"names":[],"mappings":"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"}
|
|
@@ -8,10 +8,10 @@ import Button from "@mui/material/Button";
|
|
|
8
8
|
import InputAdornment from "@mui/material/InputAdornment";
|
|
9
9
|
import Stack from "@mui/material/Stack";
|
|
10
10
|
import TextField from "@mui/material/TextField";
|
|
11
|
-
import {
|
|
11
|
+
import { useValidationText } from "../forms/useValidationText.js";
|
|
12
12
|
import { contrastOnWhite, MIN_CONTRAST, readableVariantOf, HEX_COLOR } from "./colorContrast.js";
|
|
13
13
|
import FieldFrame from "./FieldFrame.js";
|
|
14
|
-
import { piecePart, usePieceProps } from "../theme/themePieces.js";
|
|
14
|
+
import { piecePart, withPieceSlots, usePieceProps } from "../theme/themePieces.js";
|
|
15
15
|
import { warnUnknownProps } from "../toolkit/unknownProps.js";
|
|
16
16
|
const THEME_NAME = "CalcitaColorInput";
|
|
17
17
|
const ColorSwatch = piecePart(THEME_NAME, "Swatch", Box);
|
|
@@ -21,7 +21,7 @@ const NEUTRAL_COLOR = "#000000";
|
|
|
21
21
|
const RATIO_DECIMALS = 1;
|
|
22
22
|
const SWATCH_PIXELS = 24;
|
|
23
23
|
const SWATCH_EDGE = Object.freeze({ light: "rgba(15, 23, 42, 0.18)", dark: "rgba(255, 255, 255, 0.24)" });
|
|
24
|
-
function
|
|
24
|
+
function ColorInputContent(props) {
|
|
25
25
|
const {
|
|
26
26
|
name,
|
|
27
27
|
label,
|
|
@@ -33,18 +33,20 @@ function ColorInput(props) {
|
|
|
33
33
|
checkContrast = true,
|
|
34
34
|
disabled = false,
|
|
35
35
|
inputRef,
|
|
36
|
+
messages,
|
|
36
37
|
texts,
|
|
37
38
|
...unknownProps
|
|
38
39
|
} = usePieceProps(props, THEME_NAME);
|
|
39
40
|
warnUnknownProps(THEME_NAME, unknownProps);
|
|
40
41
|
const { t } = useToolkitTranslation("common", texts);
|
|
42
|
+
const validationTextOf = useValidationText({ name, label, messages });
|
|
41
43
|
const generatedId = useId();
|
|
42
44
|
const inputId = `${name ?? "color"}-${generatedId}`;
|
|
43
45
|
const color = value ?? "";
|
|
44
46
|
const isValidColor = HEX_COLOR.test(color);
|
|
45
47
|
const contrastRatio = isValidColor ? contrastOnWhite(color) : null;
|
|
46
48
|
const suggestion = checkContrast && isValidColor && contrastRatio < MIN_CONTRAST ? readableVariantOf(color) : null;
|
|
47
|
-
const errorText =
|
|
49
|
+
const errorText = validationTextOf(error);
|
|
48
50
|
const contrastText = contrastRatio && checkContrast ? t("form.colorContrast", { ratio: contrastRatio.toFixed(RATIO_DECIMALS) }) : helperText;
|
|
49
51
|
const choose = (nextColor) => onChange(nextColor.toUpperCase());
|
|
50
52
|
return /* @__PURE__ */ jsx(FieldFrame, { inputId, label, children: /* @__PURE__ */ jsxs(Stack, { spacing: 1, children: [
|
|
@@ -111,6 +113,7 @@ function ColorInput(props) {
|
|
|
111
113
|
)
|
|
112
114
|
] }) });
|
|
113
115
|
}
|
|
116
|
+
const ColorInput = withPieceSlots(ColorInputContent, THEME_NAME);
|
|
114
117
|
export {
|
|
115
118
|
ColorInput as default
|
|
116
119
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ColorInput.js","sources":["../../src/inputs/ColorInput.jsx"],"sourcesContent":["// Campo de COLOR suelto (viene de «Identidad de documentos» de PLD): la muestra redonda (el «color well» de Apple, que abre\n// el selector del sistema) dentro del campo del texto #RRGGBB, el contraste contra el blanco mientras se elige y, si el color casi no se leería, el mismo tono más oscuro con su botón\n// para usarlo.\n//\n// Para lo que se imprime o se lee sobre blanco (el color de una marca en un comprobante). El color llega al formulario en\n// mayúsculas (#2B3A67); '' = sin color.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaColorInput con defaultProps y\n// styleOverrides de sus partes: swatch, nativeInput.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useId} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport Alert from '@mui/material/Alert';\nimport Box from '@mui/material/Box';\nimport Button from '@mui/material/Button';\nimport InputAdornment from '@mui/material/InputAdornment';\nimport Stack from '@mui/material/Stack';\nimport TextField from '@mui/material/TextField';\n\nimport {translateValidationMessage} from '@/forms/validationMessages.js';\nimport {HEX_COLOR, MIN_CONTRAST, contrastOnWhite, readableVariantOf} from '@/inputs/colorContrast.js';\nimport FieldFrame from '@/inputs/FieldFrame.jsx';\nimport {piecePart, usePieceProps} from '@/theme/themePieces.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaColorInput)\nconst THEME_NAME = 'CalcitaColorInput';\nconst ColorSwatch = piecePart(THEME_NAME, 'Swatch', Box);\nconst ColorNativeInput = piecePart(THEME_NAME, 'NativeInput', Box);\n\n// #RRGGBB\nconst COLOR_LENGTH = 7;\n// el color que enseña el selector del navegador mientras el texto no es un color\nconst NEUTRAL_COLOR = '#000000';\nconst RATIO_DECIMALS = 1;\n// la muestra redonda (px) y su orilla fina en claro y en oscuro\nconst SWATCH_PIXELS = 24;\nconst SWATCH_EDGE = Object.freeze({light: 'rgba(15, 23, 42, 0.18)', dark: 'rgba(255, 255, 255, 0.24)'});\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 '#RRGGBB' o ''\n * @param {(color: string) => void} props.onChange recibe el color en mayúsculas\n * @param {() => void} [props.onBlur]\n * @param {string|{key: string, values?: object}} [props.error]\n * @param {string} [props.helperText] cuando no hay color\n * @param {boolean} [props.checkContrast] avisa si casi no se lee sobre blanco (por omisión sí)\n * @param {boolean} [props.disabled]\n * @param {import('react').Ref<HTMLInputElement>} [props.inputRef]\n * @param {object} [props.texts] los textos que cambian, con la forma de `common` en los textos del paquete\n */\nexport default function ColorInput(props) {\n // lo que el proyecto puso en su tema (components.CalcitaColorInput.defaultProps) y encima lo que llega aquí\n const {\n name, label, value, onChange, onBlur, error, helperText = ' ', checkContrast = true, disabled = false, inputRef, texts,\n ...unknownProps\n} = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const {t} = useToolkitTranslation('common', texts);\n const generatedId = useId();\n const inputId = `${name ?? 'color'}-${generatedId}`;\n const color = value ?? '';\n const isValidColor = HEX_COLOR.test(color);\n const contrastRatio = isValidColor ? contrastOnWhite(color) : null;\n const suggestion = checkContrast && isValidColor && contrastRatio < MIN_CONTRAST ? readableVariantOf(color) : null;\n const errorText = translateValidationMessage(t, error);\n const contrastText = contrastRatio && checkContrast ? t('form.colorContrast', {ratio: contrastRatio.toFixed(RATIO_DECIMALS)}) : helperText;\n const choose = (nextColor) => onChange(nextColor.toUpperCase());\n\n return (\n <FieldFrame inputId={inputId} label={label}>\n <Stack spacing={1}>\n {/* un solo campo: la muestra redonda al inicio (el «color well» de Apple) y el código al lado. Picar la muestra\n abre el selector de colores del sistema (un input type=color invisible encima de ella). Antes eran dos cajas\n sueltas (el selector del navegador en la suya y el código en otra) y se veían como piezas sueltas */}\n <TextField\n name={name}\n id={inputId}\n inputRef={inputRef}\n size=\"small\"\n fullWidth\n value={color}\n disabled={disabled}\n placeholder=\"#RRGGBB\"\n onChange={(event) => choose(event.target.value)}\n onBlur={onBlur}\n error={Boolean(errorText)}\n helperText={errorText ?? contrastText}\n slotProps={{\n input: {\n startAdornment: (\n <InputAdornment position=\"start\">\n <ColorSwatch component=\"label\"\n ownSx={(theme) => ({\n position: 'relative', display: 'block', flexShrink: 0, width: SWATCH_PIXELS, height: SWATCH_PIXELS,\n borderRadius: '50%', cursor: disabled ? 'default' : 'pointer',\n backgroundColor: isValidColor ? color : 'transparent',\n // la orilla de la muestra: que se vea aunque el color sea el del fondo (blanco en claro, negro en oscuro)\n boxShadow: `inset 0 0 0 1px ${SWATCH_EDGE.light}`,\n ...theme.applyStyles('dark', {boxShadow: `inset 0 0 0 1px ${SWATCH_EDGE.dark}`}),\n })}>\n <ColorNativeInput component=\"input\" type=\"color\" value={isValidColor ? color : NEUTRAL_COLOR} disabled={disabled}\n aria-label={t('form.pickColor')} onChange={(event) => choose(event.target.value)}\n ownSx={{position: 'absolute', inset: 0, width: '100%', height: '100%', opacity: 0, border: 0, p: 0, cursor: 'inherit'}} />\n </ColorSwatch>\n </InputAdornment>\n ),\n },\n htmlInput: {maxLength: COLOR_LENGTH, spellCheck: false, style: {fontVariantNumeric: 'tabular-nums'}},\n }}\n />\n {suggestion && (\n <Alert severity=\"warning\"\n action={<Button color=\"inherit\" size=\"small\" onClick={() => choose(suggestion)}>{t('form.colorUseSuggestion', {color: suggestion})}</Button>}>\n {t('form.colorLowContrast', {suggestion})}\n </Alert>\n )}\n </Stack>\n </FieldFrame>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ColorInput.js","sources":["../../src/inputs/ColorInput.jsx"],"sourcesContent":["// Campo de COLOR suelto (viene de «Identidad de documentos» de PLD): la muestra redonda (el «color well» de Apple, que abre\n// el selector del sistema) dentro del campo del texto #RRGGBB, el contraste contra el blanco mientras se elige y, si el color casi no se leería, el mismo tono más oscuro con su botón\n// para usarlo.\n//\n// Para lo que se imprime o se lee sobre blanco (el color de una marca en un comprobante). El color llega al formulario en\n// mayúsculas (#2B3A67); '' = sin color.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaColorInput con defaultProps y\n// styleOverrides de sus partes: swatch, nativeInput.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useId} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport Alert from '@mui/material/Alert';\nimport Box from '@mui/material/Box';\nimport Button from '@mui/material/Button';\nimport InputAdornment from '@mui/material/InputAdornment';\nimport Stack from '@mui/material/Stack';\nimport TextField from '@mui/material/TextField';\n\nimport {useValidationText} from '@/forms/useValidationText.js';\nimport {HEX_COLOR, MIN_CONTRAST, contrastOnWhite, readableVariantOf} from '@/inputs/colorContrast.js';\nimport FieldFrame from '@/inputs/FieldFrame.jsx';\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.CalcitaColorInput)\nconst THEME_NAME = 'CalcitaColorInput';\nconst ColorSwatch = piecePart(THEME_NAME, 'Swatch', Box);\nconst ColorNativeInput = piecePart(THEME_NAME, 'NativeInput', Box);\n\n// #RRGGBB\nconst COLOR_LENGTH = 7;\n// el color que enseña el selector del navegador mientras el texto no es un color\nconst NEUTRAL_COLOR = '#000000';\nconst RATIO_DECIMALS = 1;\n// la muestra redonda (px) y su orilla fina en claro y en oscuro\nconst SWATCH_PIXELS = 24;\nconst SWATCH_EDGE = Object.freeze({light: 'rgba(15, 23, 42, 0.18)', dark: 'rgba(255, 255, 255, 0.24)'});\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 '#RRGGBB' o ''\n * @param {(color: string) => void} props.onChange recibe el color en mayúsculas\n * @param {() => void} [props.onBlur]\n * @param {string|{key: string, values?: object}} [props.error]\n * @param {string} [props.helperText] cuando no hay color\n * @param {boolean} [props.checkContrast] avisa si casi no se lee sobre blanco (por omisión sí)\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] los textos que cambian, con la forma de `common` en los textos del paquete\n */\nfunction ColorInputContent(props) {\n // lo que el proyecto puso en su tema (components.CalcitaColorInput.defaultProps) y encima lo que llega aquí\n const {\n name, label, value, onChange, onBlur, error, helperText = ' ', checkContrast = true, disabled = false, inputRef, 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 ?? 'color'}-${generatedId}`;\n const color = value ?? '';\n const isValidColor = HEX_COLOR.test(color);\n const contrastRatio = isValidColor ? contrastOnWhite(color) : null;\n const suggestion = checkContrast && isValidColor && contrastRatio < MIN_CONTRAST ? readableVariantOf(color) : null;\n const errorText = validationTextOf(error);\n const contrastText = contrastRatio && checkContrast ? t('form.colorContrast', {ratio: contrastRatio.toFixed(RATIO_DECIMALS)}) : helperText;\n const choose = (nextColor) => onChange(nextColor.toUpperCase());\n\n return (\n <FieldFrame inputId={inputId} label={label}>\n <Stack spacing={1}>\n {/* un solo campo: la muestra redonda al inicio (el «color well» de Apple) y el código al lado. Picar la muestra\n abre el selector de colores del sistema (un input type=color invisible encima de ella). Antes eran dos cajas\n sueltas (el selector del navegador en la suya y el código en otra) y se veían como piezas sueltas */}\n <TextField\n name={name}\n id={inputId}\n inputRef={inputRef}\n size=\"small\"\n fullWidth\n value={color}\n disabled={disabled}\n placeholder=\"#RRGGBB\"\n onChange={(event) => choose(event.target.value)}\n onBlur={onBlur}\n error={Boolean(errorText)}\n helperText={errorText ?? contrastText}\n slotProps={{\n input: {\n startAdornment: (\n <InputAdornment position=\"start\">\n <ColorSwatch component=\"label\"\n ownSx={(theme) => ({\n position: 'relative', display: 'block', flexShrink: 0, width: SWATCH_PIXELS, height: SWATCH_PIXELS,\n borderRadius: '50%', cursor: disabled ? 'default' : 'pointer',\n backgroundColor: isValidColor ? color : 'transparent',\n // la orilla de la muestra: que se vea aunque el color sea el del fondo (blanco en claro, negro en oscuro)\n boxShadow: `inset 0 0 0 1px ${SWATCH_EDGE.light}`,\n ...theme.applyStyles('dark', {boxShadow: `inset 0 0 0 1px ${SWATCH_EDGE.dark}`}),\n })}>\n <ColorNativeInput component=\"input\" type=\"color\" value={isValidColor ? color : NEUTRAL_COLOR} disabled={disabled}\n aria-label={t('form.pickColor')} onChange={(event) => choose(event.target.value)}\n ownSx={{position: 'absolute', inset: 0, width: '100%', height: '100%', opacity: 0, border: 0, p: 0, cursor: 'inherit'}} />\n </ColorSwatch>\n </InputAdornment>\n ),\n },\n htmlInput: {maxLength: COLOR_LENGTH, spellCheck: false, style: {fontVariantNumeric: 'tabular-nums'}},\n }}\n />\n {suggestion && (\n <Alert severity=\"warning\"\n action={<Button color=\"inherit\" size=\"small\" onClick={() => choose(suggestion)}>{t('form.colorUseSuggestion', {color: suggestion})}</Button>}>\n {t('form.colorLowContrast', {suggestion})}\n </Alert>\n )}\n </Stack>\n </FieldFrame>\n );\n}\n\n// lo que se exporta: la pieza con slots y slotProps para cambiar sus partes (themePieces.js)\nconst ColorInput = withPieceSlots(ColorInputContent, THEME_NAME);\nexport default ColorInput;\n"],"names":[],"mappings":"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"}
|
|
@@ -4,18 +4,24 @@ 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 { piecePart, withPieceSlots, usePieceProps } from "../theme/themePieces.js";
|
|
7
8
|
import { warnUnknownProps } from "../toolkit/unknownProps.js";
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
const THEME_NAME = "CalcitaFieldFrame";
|
|
10
|
+
const FieldFrameRoot = piecePart(THEME_NAME, "Root", Stack);
|
|
11
|
+
const FieldFrameLabel = piecePart(THEME_NAME, "Label", Typography);
|
|
12
|
+
function FieldFrameContent(props) {
|
|
13
|
+
const { inputId, labelId, label, children, ...unknownProps } = usePieceProps(props, THEME_NAME);
|
|
14
|
+
warnUnknownProps(THEME_NAME, unknownProps);
|
|
10
15
|
const isRow = useIsInsideFormSection();
|
|
11
16
|
if (isRow) {
|
|
12
17
|
return /* @__PURE__ */ jsx(FormRow, { label, htmlFor: inputId, labelId, children });
|
|
13
18
|
}
|
|
14
|
-
return /* @__PURE__ */ jsxs(
|
|
15
|
-
/* @__PURE__ */ jsx(
|
|
19
|
+
return /* @__PURE__ */ jsxs(FieldFrameRoot, { spacing: 0.75, children: [
|
|
20
|
+
/* @__PURE__ */ jsx(FieldFrameLabel, { component: "label", id: labelId, htmlFor: inputId, variant: "overline", color: "text.secondary", children: label }),
|
|
16
21
|
children
|
|
17
22
|
] });
|
|
18
23
|
}
|
|
24
|
+
const FieldFrame = withPieceSlots(FieldFrameContent, THEME_NAME);
|
|
19
25
|
export {
|
|
20
26
|
FieldFrame as default
|
|
21
27
|
};
|
|
@@ -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// 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 {warnUnknownProps} from '@/toolkit/unknownProps.js';\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 */\
|
|
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\">{label}</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":";;;;;;;;AAoBA,MAAM,aAAa;AACnB,MAAM,iBAAiB,UAAU,YAAY,QAAQ,KAAK;AAC1D,MAAM,kBAAkB,UAAU,YAAY,SAAS,UAAU;AAUjE,SAAS,kBAAkB,OAAO;AAC9B,QAAM,EAAC,SAAS,SAAS,OAAO,UAAU,GAAG,iBAAgB,cAAc,OAAO,UAAU;AAC5F,mBAAiB,YAAY,YAAY;AACzC,QAAM,QAAQ,uBAAA;AAEd,MAAI,OAAO;AACP,+BAAQ,SAAA,EAAQ,OAAc,SAAS,SAAS,SAAmB,UAAS;AAAA,EAChF;AAEA,SACI,qBAAC,gBAAA,EAAe,SAAS,MACrB,UAAA;AAAA,IAAA,oBAAC,iBAAA,EAAgB,WAAU,SAAQ,IAAI,SAAS,SAAS,SAAS,SAAQ,YAAW,OAAM,kBAAkB,UAAA,OAAM;AAAA,IAClH;AAAA,EAAA,GACL;AAER;AAGA,MAAM,aAAa,eAAe,mBAAmB,UAAU;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MaskedInput.js","sources":["../../src/inputs/MaskedInput.jsx"],"sourcesContent":["// Campo de texto con máscara: un teléfono, una CLABE, un RFC, un folio fiscal. Mientras se escribe se ven los separadores\n// («555 123 4567») y al formulario llega el valor LIMPIO («5551234567»), que es lo que valida y guarda el servidor. Pegar un\n// número con otros separadores también sirve: se queda con los dígitos o letras que caben.\n//\n// Es TextInput por dentro (la etiqueta arriba, la ⓧ para borrar, el error y la ayuda, el renglón en una FormSection), con la\n// máscara de inputMasks.js: mask es el nombre de una de INPUT_MASK ('phone', 'clabe', 'rfc', 'curp', 'uuid'…) o un patrón\n// propio ('AAA-999'). Escribir en medio no manda el cursor al final.\n//\n// Con formik: FormMaskedField (/forms); con react-hook-form, el del mismo nombre (/react-hook-form).\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useLayoutEffect, useRef} from 'react';\n\nimport {caretPositionOf, maskPatternOf, maskedTextOf, rawValueOf} from '@/inputs/inputMasks.js';\nimport TextInput from '@/inputs/TextInput.jsx';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\n// el teclado del teléfono según lo que cabe: solo dígitos abre el numérico\nconst NUMERIC_ONLY = /^[^AXH]*$/;\n\n/**\n * @param {object} props las de TextInput (label, helperText, error, placeholder, disabled, slotProps…), y:\n * @param {string} props.mask 'phone' | 'postalCode' | 'clabe' | 'card' | 'rfc' | 'curp' | 'uuid' (INPUT_MASK) o un patrón propio\n * con 9 (dígito), A (letra), X (letra o dígito) y H (hexadecimal)\n * @param {string|null|undefined} props.value el valor limpio, sin separadores\n * @param {(value: string) => void} props.onChange recibe el valor limpio\n * @param {string} props.label ya traducido\n * @param {string} [props.name]\n * @param {() => void} [props.onBlur]\n * @param {string|{key: string, values?: object}} [props.error]\n * @param {string} [props.helperText]\n * @param {string} [props.placeholder] ya traducido; por omisión ninguno (un ejemplo con la forma ayuda: «555 123 4567»)\n * @param {import('react').Ref<HTMLInputElement>} [props.inputRef]\n * @param {boolean} [props.disabled]\n * @param {object} [props.slotProps]\n * @param {object} [props.texts]\n */\nexport default function MaskedInput({mask, value, onChange, inputRef, slotProps, ...textInputProps}) {\n const {unknownProps, knownProps} = splitTextInputProps(textInputProps);\n warnUnknownProps('MaskedInput', unknownProps);\n const pattern = maskPatternOf(mask);\n const ownInputRef = useRef(null);\n // dónde dejar el cursor cuando el campo ya tenga sus separadores (se pone después de pintar)\n const pendingCaret = useRef(null);\n\n useLayoutEffect(() => {\n const input = ownInputRef.current;\n if (pendingCaret.current === null || !input || input !== input.ownerDocument.activeElement) {\n return;\n }\n input.setSelectionRange(pendingCaret.current, pendingCaret.current);\n pendingCaret.current = null;\n });\n\n const keepInputRef = (input) => {\n ownInputRef.current = input;\n if (typeof inputRef === 'function') {\n inputRef(input);\n } else if (inputRef) {\n inputRef.current = input;\n }\n };\n\n const change = (typedText) => {\n const caretPosition = ownInputRef.current?.selectionStart ?? typedText.length;\n pendingCaret.current = caretPositionOf(typedText, caretPosition, pattern);\n onChange(rawValueOf(typedText, pattern));\n };\n\n return (\n <TextInput\n {...knownProps}\n value={maskedTextOf(value, pattern)}\n onChange={change}\n inputRef={keepInputRef}\n slotProps={{\n ...slotProps,\n // OJO: sin maxLength. El navegador cortaría lo pegado ANTES de limpiarlo: «(555) 123-4567» trae 14 caracteres para\n // un patrón de 12 y se perderían los últimos dígitos. El largo lo cuida la máscara\n htmlInput: {\n inputMode: NUMERIC_ONLY.test(pattern) ? 'numeric' : 'text', autoCapitalize: 'characters', spellCheck: false,\n ...slotProps?.htmlInput,\n },\n }}\n />\n );\n}\n\n// lo que TextInput entiende; lo demás se avisa en la consola mientras se programa. uppercase y normalizeOnBlur no van: la\n// máscara ya decide cómo se escribe\nconst TEXT_INPUT_PROPS = new Set([\n 'name', 'label', 'onBlur', 'error', 'helperText', 'placeholder', 'startAdornment', 'endAdornment', 'clearable', 'autoComplete', 'autoFocus',\n 'disabled', 'required', 'fullWidth', 'id', 'sx', 'texts',\n]);\n\n/**\n * @param {object} props\n * @returns {{knownProps: object, unknownProps: object}}\n */\nfunction splitTextInputProps(props) {\n const knownProps = {};\n const unknownProps = {};\n for (const [key, propValue] of Object.entries(props)) {\n (TEXT_INPUT_PROPS.has(key) ? knownProps : unknownProps)[key] = propValue;\n }\n return {knownProps, unknownProps};\n}\n"],"names":[],"mappings":";;;;;;AAkBA,MAAM,eAAe;
|
|
1
|
+
{"version":3,"file":"MaskedInput.js","sources":["../../src/inputs/MaskedInput.jsx"],"sourcesContent":["// Campo de texto con máscara: un teléfono, una CLABE, un RFC, un folio fiscal. Mientras se escribe se ven los separadores\n// («555 123 4567») y al formulario llega el valor LIMPIO («5551234567»), que es lo que valida y guarda el servidor. Pegar un\n// número con otros separadores también sirve: se queda con los dígitos o letras que caben.\n//\n// Es TextInput por dentro (la etiqueta arriba, la ⓧ para borrar, el error y la ayuda, el renglón en una FormSection), con la\n// máscara de inputMasks.js: mask es el nombre de una de INPUT_MASK ('phone', 'clabe', 'rfc', 'curp', 'uuid'…) o un patrón\n// propio ('AAA-999'). Escribir en medio no manda el cursor al final.\n//\n// Con formik: FormMaskedField (/forms); con react-hook-form, el del mismo nombre (/react-hook-form).\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useLayoutEffect, useRef} from 'react';\n\nimport {caretPositionOf, maskPatternOf, maskedTextOf, rawValueOf} from '@/inputs/inputMasks.js';\nimport TextInput from '@/inputs/TextInput.jsx';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\n// el teclado del teléfono según lo que cabe: solo dígitos abre el numérico\nconst NUMERIC_ONLY = /^[^AXH]*$/;\n\n/**\n * @param {object} props las de TextInput (label, helperText, error, placeholder, disabled, slotProps…), y:\n * @param {string} props.mask 'phone' | 'postalCode' | 'clabe' | 'card' | 'rfc' | 'curp' | 'uuid' (INPUT_MASK) o un patrón propio\n * con 9 (dígito), A (letra), X (letra o dígito) y H (hexadecimal)\n * @param {string|null|undefined} props.value el valor limpio, sin separadores\n * @param {(value: string) => void} props.onChange recibe el valor limpio\n * @param {string} props.label ya traducido\n * @param {string} [props.name]\n * @param {() => void} [props.onBlur]\n * @param {string|{key: string, values?: object}} [props.error]\n * @param {string} [props.helperText]\n * @param {string} [props.placeholder] ya traducido; por omisión ninguno (un ejemplo con la forma ayuda: «555 123 4567»)\n * @param {import('react').Ref<HTMLInputElement>} [props.inputRef]\n * @param {boolean} [props.disabled]\n * @param {Record<string, string|object>} [props.messages] el texto del error de este campo, por regla de validación: gana al de\n * Calcita y al de los textos\n * @param {object} [props.slotProps]\n * @param {object} [props.texts]\n */\nexport default function MaskedInput({mask, value, onChange, inputRef, slotProps, ...textInputProps}) {\n const {unknownProps, knownProps} = splitTextInputProps(textInputProps);\n warnUnknownProps('MaskedInput', unknownProps);\n const pattern = maskPatternOf(mask);\n const ownInputRef = useRef(null);\n // dónde dejar el cursor cuando el campo ya tenga sus separadores (se pone después de pintar)\n const pendingCaret = useRef(null);\n\n useLayoutEffect(() => {\n const input = ownInputRef.current;\n if (pendingCaret.current === null || !input || input !== input.ownerDocument.activeElement) {\n return;\n }\n input.setSelectionRange(pendingCaret.current, pendingCaret.current);\n pendingCaret.current = null;\n });\n\n const keepInputRef = (input) => {\n ownInputRef.current = input;\n if (typeof inputRef === 'function') {\n inputRef(input);\n } else if (inputRef) {\n inputRef.current = input;\n }\n };\n\n const change = (typedText) => {\n const caretPosition = ownInputRef.current?.selectionStart ?? typedText.length;\n pendingCaret.current = caretPositionOf(typedText, caretPosition, pattern);\n onChange(rawValueOf(typedText, pattern));\n };\n\n return (\n <TextInput\n {...knownProps}\n value={maskedTextOf(value, pattern)}\n onChange={change}\n inputRef={keepInputRef}\n slotProps={{\n ...slotProps,\n // OJO: sin maxLength. El navegador cortaría lo pegado ANTES de limpiarlo: «(555) 123-4567» trae 14 caracteres para\n // un patrón de 12 y se perderían los últimos dígitos. El largo lo cuida la máscara\n htmlInput: {\n inputMode: NUMERIC_ONLY.test(pattern) ? 'numeric' : 'text', autoCapitalize: 'characters', spellCheck: false,\n ...slotProps?.htmlInput,\n },\n }}\n />\n );\n}\n\n// lo que TextInput entiende; lo demás se avisa en la consola mientras se programa. uppercase y normalizeOnBlur no van: la\n// máscara ya decide cómo se escribe\nconst TEXT_INPUT_PROPS = new Set([\n 'name', 'label', 'onBlur', 'error', 'helperText', 'placeholder', 'startAdornment', 'endAdornment', 'clearable', 'autoComplete', 'autoFocus',\n 'disabled', 'required', 'fullWidth', 'id', 'sx', 'messages', 'texts',\n]);\n\n/**\n * @param {object} props\n * @returns {{knownProps: object, unknownProps: object}}\n */\nfunction splitTextInputProps(props) {\n const knownProps = {};\n const unknownProps = {};\n for (const [key, propValue] of Object.entries(props)) {\n (TEXT_INPUT_PROPS.has(key) ? knownProps : unknownProps)[key] = propValue;\n }\n return {knownProps, unknownProps};\n}\n"],"names":[],"mappings":";;;;;;AAkBA,MAAM,eAAe;AAqBrB,SAAwB,YAAY,EAAC,MAAM,OAAO,UAAU,UAAU,WAAW,GAAG,kBAAiB;AACjG,QAAM,EAAC,cAAc,eAAc,oBAAoB,cAAc;AACrE,mBAAiB,eAAe,YAAY;AAC5C,QAAM,UAAU,cAAc,IAAI;AAClC,QAAM,cAAc,OAAO,IAAI;AAE/B,QAAM,eAAe,OAAO,IAAI;AAEhC,kBAAgB,MAAM;AAClB,UAAM,QAAQ,YAAY;AAC1B,QAAI,aAAa,YAAY,QAAQ,CAAC,SAAS,UAAU,MAAM,cAAc,eAAe;AACxF;AAAA,IACJ;AACA,UAAM,kBAAkB,aAAa,SAAS,aAAa,OAAO;AAClE,iBAAa,UAAU;AAAA,EAC3B,CAAC;AAED,QAAM,eAAe,CAAC,UAAU;AAC5B,gBAAY,UAAU;AACtB,QAAI,OAAO,aAAa,YAAY;AAChC,eAAS,KAAK;AAAA,IAClB,WAAW,UAAU;AACjB,eAAS,UAAU;AAAA,IACvB;AAAA,EACJ;AAEA,QAAM,SAAS,CAAC,cAAc;AAC1B,UAAM,gBAAgB,YAAY,SAAS,kBAAkB,UAAU;AACvE,iBAAa,UAAU,gBAAgB,WAAW,eAAe,OAAO;AACxE,aAAS,WAAW,WAAW,OAAO,CAAC;AAAA,EAC3C;AAEA,SACI;AAAA,IAAC;AAAA,IAAA;AAAA,MACI,GAAG;AAAA,MACJ,OAAO,aAAa,OAAO,OAAO;AAAA,MAClC,UAAU;AAAA,MACV,UAAU;AAAA,MACV,WAAW;AAAA,QACP,GAAG;AAAA;AAAA;AAAA,QAGH,WAAW;AAAA,UACP,WAAW,aAAa,KAAK,OAAO,IAAI,YAAY;AAAA,UAAQ,gBAAgB;AAAA,UAAc,YAAY;AAAA,UACtG,GAAG,WAAW;AAAA,QAAA;AAAA,MAClB;AAAA,IACJ;AAAA,EAAA;AAGZ;AAIA,MAAM,uCAAuB,IAAI;AAAA,EAC7B;AAAA,EAAQ;AAAA,EAAS;AAAA,EAAU;AAAA,EAAS;AAAA,EAAc;AAAA,EAAe;AAAA,EAAkB;AAAA,EAAgB;AAAA,EAAa;AAAA,EAAgB;AAAA,EAChI;AAAA,EAAY;AAAA,EAAY;AAAA,EAAa;AAAA,EAAM;AAAA,EAAM;AAAA,EAAY;AACjE,CAAC;AAMD,SAAS,oBAAoB,OAAO;AAChC,QAAM,aAAa,CAAA;AACnB,QAAM,eAAe,CAAA;AACrB,aAAW,CAAC,KAAK,SAAS,KAAK,OAAO,QAAQ,KAAK,GAAG;AAClD,KAAC,iBAAiB,IAAI,GAAG,IAAI,aAAa,cAAc,GAAG,IAAI;AAAA,EACnE;AACA,SAAO,EAAC,YAAY,aAAA;AACxB;"}
|
|
@@ -1,42 +1,46 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
3
|
import { useId, useState } from "react";
|
|
4
|
-
import { useToolkitTranslation } from "../toolkit/useToolkitTranslation.js";
|
|
5
4
|
import InputAdornment from "@mui/material/InputAdornment";
|
|
6
5
|
import TextField from "@mui/material/TextField";
|
|
7
|
-
import {
|
|
6
|
+
import { useValidationText } from "../forms/useValidationText.js";
|
|
8
7
|
import FieldFrame from "./FieldFrame.js";
|
|
9
8
|
import { groupThousands, completeDecimals, cleanNumberText } from "./numberText.js";
|
|
10
9
|
import { warnUnknownProps } from "../toolkit/unknownProps.js";
|
|
10
|
+
import { piecePart, withPieceSlots, usePieceProps } from "../theme/themePieces.js";
|
|
11
|
+
const THEME_NAME = "CalcitaNumberInput";
|
|
12
|
+
const NumberInputField = piecePart(THEME_NAME, "Field", TextField);
|
|
11
13
|
const SEPARATORS_ROOM = 6;
|
|
12
|
-
function
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
14
|
+
function NumberInputContent(props) {
|
|
15
|
+
const {
|
|
16
|
+
name,
|
|
17
|
+
label,
|
|
18
|
+
value,
|
|
19
|
+
onChange,
|
|
20
|
+
onBlur,
|
|
21
|
+
error,
|
|
22
|
+
helperText = " ",
|
|
23
|
+
decimals = 2,
|
|
24
|
+
allowNegative = false,
|
|
25
|
+
prefix,
|
|
26
|
+
suffix,
|
|
27
|
+
maxDigits = 17,
|
|
28
|
+
disabled = false,
|
|
29
|
+
inputRef,
|
|
30
|
+
messages,
|
|
31
|
+
texts: unusedTexts,
|
|
32
|
+
...unknownProps
|
|
33
|
+
} = usePieceProps(props, THEME_NAME);
|
|
34
|
+
warnUnknownProps(THEME_NAME, unknownProps);
|
|
35
|
+
const validationTextOf = useValidationText({ name, label, messages });
|
|
32
36
|
const generatedId = useId();
|
|
33
37
|
const inputId = `${name ?? "number"}-${generatedId}`;
|
|
34
38
|
const [isTyping, setIsTyping] = useState(false);
|
|
35
|
-
const errorText =
|
|
39
|
+
const errorText = validationTextOf(error);
|
|
36
40
|
const valueText = value === null || value === void 0 ? "" : String(value);
|
|
37
41
|
const shownText = groupThousands(isTyping ? valueText : completeDecimals(valueText, decimals));
|
|
38
42
|
return /* @__PURE__ */ jsx(FieldFrame, { inputId, label, children: /* @__PURE__ */ jsx(
|
|
39
|
-
|
|
43
|
+
NumberInputField,
|
|
40
44
|
{
|
|
41
45
|
name,
|
|
42
46
|
id: inputId,
|
|
@@ -78,6 +82,7 @@ function NumberInput({
|
|
|
78
82
|
}
|
|
79
83
|
) });
|
|
80
84
|
}
|
|
85
|
+
const NumberInput = withPieceSlots(NumberInputContent, THEME_NAME);
|
|
81
86
|
export {
|
|
82
87
|
NumberInput as default
|
|
83
88
|
};
|
|
@@ -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// Genérico del paquete: function (regla de la casa).\nimport {useId, useState} from 'react';\nimport
|
|
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;AAsBxB,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;AAAA,IAAU;AAAA,IAAU,OAAO;AAAA,IAC7C,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,OAC1B,UAAA;AAAA,IAAC;AAAA,IAAA;AAAA,MACG;AAAA,MACA,IAAI;AAAA,MACJ;AAAA,MACA,OAAO;AAAA,MACP;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;"}
|