@stackcraftmx/calcita 0.32.1 → 0.47.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (338) hide show
  1. package/README.md +263 -2
  2. package/cli/packageVersions.js +12 -3
  3. package/cli/projectFiles.js +5 -2
  4. package/cli/templates/module/__Name__Page.test.jsx.tpl +24 -0
  5. package/cli/templates/project/README.md.tpl +14 -1
  6. package/cli/templates/project/src/app/appRouter.jsx.tpl +5 -2
  7. package/cli/templates/project/src/core/accountConnector.js.tpl +4 -2
  8. package/cli/templates/project/src/modules/auth/SignInPage.test.jsx.tpl +20 -0
  9. package/cli/templates/project/src/modules/home/HomePage.test.jsx.tpl +16 -0
  10. package/cli/templates/project/src/testing/browserGaps.js.tpl +70 -0
  11. package/cli/templates/project/src/testing/openScreen.jsx.tpl +58 -0
  12. package/cli/templates/project/src/testing/setup.js.tpl +25 -0
  13. package/cli/templates/project/vite.config.js.tpl +12 -0
  14. package/dist/DataTable/GoToPage.js +69 -13
  15. package/dist/DataTable/GoToPage.js.map +1 -1
  16. package/dist/DataTable/ServerTable.js +2 -0
  17. package/dist/DataTable/ServerTable.js.map +1 -1
  18. package/dist/DataTable/ServerTableFilterDrawer.js +52 -0
  19. package/dist/DataTable/ServerTableFilterDrawer.js.map +1 -0
  20. package/dist/DataTable/filterContext.js.map +1 -1
  21. package/dist/DataTable/goToPageOptions.js +17 -0
  22. package/dist/DataTable/goToPageOptions.js.map +1 -0
  23. package/dist/date-pickers.js +28 -2
  24. package/dist/date-pickers.js.map +1 -1
  25. package/dist/datePickers/DateInput.js +15 -34
  26. package/dist/datePickers/DateInput.js.map +1 -1
  27. package/dist/datePickers/DateTimeInput.js +75 -0
  28. package/dist/datePickers/DateTimeInput.js.map +1 -0
  29. package/dist/datePickers/FormDateTimeField.js +12 -0
  30. package/dist/datePickers/FormDateTimeField.js.map +1 -0
  31. package/dist/datePickers/FormMonthField.js +12 -0
  32. package/dist/datePickers/FormMonthField.js.map +1 -0
  33. package/dist/datePickers/FormTimeField.js +12 -0
  34. package/dist/datePickers/FormTimeField.js.map +1 -0
  35. package/dist/datePickers/MonthInput.js +58 -0
  36. package/dist/datePickers/MonthInput.js.map +1 -0
  37. package/dist/datePickers/PickerFrame.js +27 -0
  38. package/dist/datePickers/PickerFrame.js.map +1 -0
  39. package/dist/datePickers/TimeInput.js +55 -0
  40. package/dist/datePickers/TimeInput.js.map +1 -0
  41. package/dist/datePickers/dateTimeRules.js +24 -0
  42. package/dist/datePickers/dateTimeRules.js.map +1 -0
  43. package/dist/datePickers/pickerValues.js +106 -0
  44. package/dist/datePickers/pickerValues.js.map +1 -0
  45. package/dist/datePickers/useCurrentMinute.js +28 -0
  46. package/dist/datePickers/useCurrentMinute.js.map +1 -0
  47. package/dist/datePickers/usePickerField.js +22 -0
  48. package/dist/datePickers/usePickerField.js.map +1 -0
  49. package/dist/dictionaries/dictionaries.js +3 -1
  50. package/dist/dictionaries/dictionaries.js.map +1 -1
  51. package/dist/forms/FormAttachmentsField.js +12 -0
  52. package/dist/forms/FormAttachmentsField.js.map +1 -0
  53. package/dist/forms/FormCheckboxGroupField.js +12 -0
  54. package/dist/forms/FormCheckboxGroupField.js.map +1 -0
  55. package/dist/forms/FormFileField.js +12 -0
  56. package/dist/forms/FormFileField.js.map +1 -0
  57. package/dist/forms/FormFileUploadField.js +12 -0
  58. package/dist/forms/FormFileUploadField.js.map +1 -0
  59. package/dist/forms/FormImageField.js +12 -0
  60. package/dist/forms/FormImageField.js.map +1 -0
  61. package/dist/forms/FormMaskedField.js +12 -0
  62. package/dist/forms/FormMaskedField.js.map +1 -0
  63. package/dist/forms/FormRepeatingFields.js +54 -0
  64. package/dist/forms/FormRepeatingFields.js.map +1 -0
  65. package/dist/forms/FormTransferListField.js +12 -0
  66. package/dist/forms/FormTransferListField.js.map +1 -0
  67. package/dist/forms/applyServerProblem.js +10 -3
  68. package/dist/forms/applyServerProblem.js.map +1 -1
  69. package/dist/forms/listRules.js +111 -0
  70. package/dist/forms/listRules.js.map +1 -0
  71. package/dist/forms/serverProblemFields.js +17 -0
  72. package/dist/forms/serverProblemFields.js.map +1 -1
  73. package/dist/forms.js +51 -29
  74. package/dist/forms.js.map +1 -1
  75. package/dist/hookForm/FormAttachmentsField.js +12 -0
  76. package/dist/hookForm/FormAttachmentsField.js.map +1 -0
  77. package/dist/hookForm/FormCheckboxGroupField.js +12 -0
  78. package/dist/hookForm/FormCheckboxGroupField.js.map +1 -0
  79. package/dist/hookForm/FormFileField.js +12 -0
  80. package/dist/hookForm/FormFileField.js.map +1 -0
  81. package/dist/hookForm/FormFileUploadField.js +12 -0
  82. package/dist/hookForm/FormFileUploadField.js.map +1 -0
  83. package/dist/hookForm/FormImageField.js +12 -0
  84. package/dist/hookForm/FormImageField.js.map +1 -0
  85. package/dist/hookForm/FormMaskedField.js +12 -0
  86. package/dist/hookForm/FormMaskedField.js.map +1 -0
  87. package/dist/hookForm/FormRepeatingFields.js +44 -0
  88. package/dist/hookForm/FormRepeatingFields.js.map +1 -0
  89. package/dist/hookForm/FormTransferListField.js +12 -0
  90. package/dist/hookForm/FormTransferListField.js.map +1 -0
  91. package/dist/hookForm/applyServerProblem.js +8 -3
  92. package/dist/hookForm/applyServerProblem.js.map +1 -1
  93. package/dist/index.js +188 -100
  94. package/dist/index.js.map +1 -1
  95. package/dist/inputs/CatalogInput.js +74 -7
  96. package/dist/inputs/CatalogInput.js.map +1 -1
  97. package/dist/inputs/CheckboxGroupInput.js +100 -0
  98. package/dist/inputs/CheckboxGroupInput.js.map +1 -0
  99. package/dist/inputs/MaskedInput.js +86 -0
  100. package/dist/inputs/MaskedInput.js.map +1 -0
  101. package/dist/inputs/NumberInput.js +12 -1
  102. package/dist/inputs/NumberInput.js.map +1 -1
  103. package/dist/inputs/RepeatingFields.js +177 -0
  104. package/dist/inputs/RepeatingFields.js.map +1 -0
  105. package/dist/inputs/SelectInput.js +43 -5
  106. package/dist/inputs/SelectInput.js.map +1 -1
  107. package/dist/inputs/TransferListInput.js +215 -0
  108. package/dist/inputs/TransferListInput.js.map +1 -0
  109. package/dist/inputs/inputMasks.js +84 -0
  110. package/dist/inputs/inputMasks.js.map +1 -0
  111. package/dist/inputs/selectAllState.js +20 -0
  112. package/dist/inputs/selectAllState.js.map +1 -0
  113. package/dist/inputs/useRowKeys.js +26 -0
  114. package/dist/inputs/useRowKeys.js.map +1 -0
  115. package/dist/legal/CookieNotice.js +79 -0
  116. package/dist/legal/CookieNotice.js.map +1 -0
  117. package/dist/legal/TermsAcceptanceDialog.js +129 -0
  118. package/dist/legal/TermsAcceptanceDialog.js.map +1 -0
  119. package/dist/legal/consentStorage.js +18 -0
  120. package/dist/legal/consentStorage.js.map +1 -0
  121. package/dist/legal/useCookieConsent.js +17 -0
  122. package/dist/legal/useCookieConsent.js.map +1 -0
  123. package/dist/locales/en.js +233 -1
  124. package/dist/locales/en.js.map +1 -1
  125. package/dist/locales/es.js +233 -1
  126. package/dist/locales/es.js.map +1 -1
  127. package/dist/mexico/CfdiSummary.js +81 -0
  128. package/dist/mexico/CfdiSummary.js.map +1 -0
  129. package/dist/mexico/EfirmaFields.js +167 -0
  130. package/dist/mexico/EfirmaFields.js.map +1 -0
  131. package/dist/mexico/certificateReader.js +79 -0
  132. package/dist/mexico/certificateReader.js.map +1 -0
  133. package/dist/mexico/cfdiReader.js +81 -0
  134. package/dist/mexico/cfdiReader.js.map +1 -0
  135. package/dist/mexico/derReader.js +81 -0
  136. package/dist/mexico/derReader.js.map +1 -0
  137. package/dist/mexico/efirmaKey.js +51 -0
  138. package/dist/mexico/efirmaKey.js.map +1 -0
  139. package/dist/mexico/mexicoIdentifiers.js +86 -0
  140. package/dist/mexico/mexicoIdentifiers.js.map +1 -0
  141. package/dist/mexico/mexicoRules.js +16 -0
  142. package/dist/mexico/mexicoRules.js.map +1 -0
  143. package/dist/mexico.js +32 -0
  144. package/dist/mexico.js.map +1 -0
  145. package/dist/react-hook-form.js +40 -17
  146. package/dist/react-hook-form.js.map +1 -1
  147. package/dist/rich-text.js +19 -0
  148. package/dist/rich-text.js.map +1 -0
  149. package/dist/richText/FormRichTextField.js +12 -0
  150. package/dist/richText/FormRichTextField.js.map +1 -0
  151. package/dist/richText/RichTextEditor.js +108 -0
  152. package/dist/richText/RichTextEditor.js.map +1 -0
  153. package/dist/richText/RichTextLinkPopover.js +57 -0
  154. package/dist/richText/RichTextLinkPopover.js.map +1 -0
  155. package/dist/richText/RichTextToolbar.js +189 -0
  156. package/dist/richText/RichTextToolbar.js.map +1 -0
  157. package/dist/richText/RichTextView.js +26 -0
  158. package/dist/richText/RichTextView.js.map +1 -0
  159. package/dist/richText/richTextContentSx.js +42 -0
  160. package/dist/richText/richTextContentSx.js.map +1 -0
  161. package/dist/richText/richTextExtensions.js +44 -0
  162. package/dist/richText/richTextExtensions.js.map +1 -0
  163. package/dist/richText/richTextRules.js +46 -0
  164. package/dist/richText/richTextRules.js.map +1 -0
  165. package/dist/security/SessionsCard.js +11 -7
  166. package/dist/security/SessionsCard.js.map +1 -1
  167. package/dist/table.js +2 -0
  168. package/dist/table.js.map +1 -1
  169. package/dist/theme/components/glassComponents.js +27 -0
  170. package/dist/theme/components/glassComponents.js.map +1 -1
  171. package/dist/theme/components/minimalComponents.js +12 -0
  172. package/dist/theme/components/minimalComponents.js.map +1 -1
  173. package/dist/types/DataTable/ServerTableFilterDrawer.d.ts +21 -0
  174. package/dist/types/DataTable/filterContext.d.ts +2 -1
  175. package/dist/types/DataTable/goToPageOptions.d.ts +15 -0
  176. package/dist/types/DataTable/index.d.ts +1 -0
  177. package/dist/types/date-pickers.d.ts +10 -1
  178. package/dist/types/datePickers/DateInput.d.ts +6 -4
  179. package/dist/types/datePickers/DateTimeInput.d.ts +50 -0
  180. package/dist/types/datePickers/FormDateTimeField.d.ts +7 -0
  181. package/dist/types/datePickers/FormMonthField.d.ts +7 -0
  182. package/dist/types/datePickers/FormTimeField.d.ts +7 -0
  183. package/dist/types/datePickers/MonthInput.d.ts +39 -0
  184. package/dist/types/datePickers/PickerFrame.d.ts +22 -0
  185. package/dist/types/datePickers/TimeInput.d.ts +38 -0
  186. package/dist/types/datePickers/dateTimeRules.d.ts +31 -0
  187. package/dist/types/datePickers/pickerValues.d.ts +114 -0
  188. package/dist/types/datePickers/useCurrentMinute.d.ts +5 -0
  189. package/dist/types/datePickers/usePickerField.d.ts +24 -0
  190. package/dist/types/dictionaries/dictionaries.d.ts +6 -3
  191. package/dist/types/forms/FormAttachmentsField.d.ts +7 -0
  192. package/dist/types/forms/FormCheckboxGroupField.d.ts +7 -0
  193. package/dist/types/forms/FormFileField.d.ts +7 -0
  194. package/dist/types/forms/FormFileUploadField.d.ts +7 -0
  195. package/dist/types/forms/FormImageField.d.ts +7 -0
  196. package/dist/types/forms/FormMaskedField.d.ts +7 -0
  197. package/dist/types/forms/FormRepeatingFields.d.ts +25 -0
  198. package/dist/types/forms/FormTransferListField.d.ts +7 -0
  199. package/dist/types/forms/index.d.ts +10 -1
  200. package/dist/types/forms/listRules.d.ts +48 -0
  201. package/dist/types/forms/serverProblemFields.d.ts +13 -0
  202. package/dist/types/forms.d.ts +1 -1
  203. package/dist/types/hookForm/FormAttachmentsField.d.ts +8 -0
  204. package/dist/types/hookForm/FormCheckboxGroupField.d.ts +8 -0
  205. package/dist/types/hookForm/FormFileField.d.ts +8 -0
  206. package/dist/types/hookForm/FormFileUploadField.d.ts +8 -0
  207. package/dist/types/hookForm/FormImageField.d.ts +8 -0
  208. package/dist/types/hookForm/FormMaskedField.d.ts +9 -0
  209. package/dist/types/hookForm/FormRepeatingFields.d.ts +27 -0
  210. package/dist/types/hookForm/FormTransferListField.d.ts +8 -0
  211. package/dist/types/index.d.ts +25 -3
  212. package/dist/types/inputs/CatalogInput.d.ts +7 -1
  213. package/dist/types/inputs/CheckboxGroupInput.d.ts +39 -0
  214. package/dist/types/inputs/MaskedInput.d.ts +35 -0
  215. package/dist/types/inputs/RepeatingFields.d.ts +52 -0
  216. package/dist/types/inputs/SelectInput.d.ts +6 -1
  217. package/dist/types/inputs/TransferListInput.d.ts +43 -0
  218. package/dist/types/inputs/inputMasks.d.ts +52 -0
  219. package/dist/types/inputs/selectAllState.d.ts +11 -0
  220. package/dist/types/inputs/useRowKeys.d.ts +11 -0
  221. package/dist/types/legal/CookieNotice.d.ts +30 -0
  222. package/dist/types/legal/TermsAcceptanceDialog.d.ts +32 -0
  223. package/dist/types/legal/consentStorage.d.ts +16 -0
  224. package/dist/types/legal/useCookieConsent.d.ts +13 -0
  225. package/dist/types/locales/en.d.ts +232 -0
  226. package/dist/types/locales/es.d.ts +232 -0
  227. package/dist/types/mexico/CfdiSummary.d.ts +17 -0
  228. package/dist/types/mexico/EfirmaFields.d.ts +38 -0
  229. package/dist/types/mexico/certificateReader.d.ts +37 -0
  230. package/dist/types/mexico/cfdiReader.d.ts +95 -0
  231. package/dist/types/mexico/derReader.d.ts +54 -0
  232. package/dist/types/mexico/efirmaKey.d.ts +26 -0
  233. package/dist/types/mexico/mexicoIdentifiers.d.ts +61 -0
  234. package/dist/types/mexico/mexicoRules.d.ts +29 -0
  235. package/dist/types/mexico.d.ts +11 -0
  236. package/dist/types/react-hook-form.d.ts +10 -1
  237. package/dist/types/rich-text.d.ts +5 -0
  238. package/dist/types/richText/FormRichTextField.d.ts +7 -0
  239. package/dist/types/richText/RichTextEditor.d.ts +42 -0
  240. package/dist/types/richText/RichTextLinkPopover.d.ts +17 -0
  241. package/dist/types/richText/RichTextToolbar.d.ts +15 -0
  242. package/dist/types/richText/RichTextView.d.ts +13 -0
  243. package/dist/types/richText/richTextContentSx.d.ts +7 -0
  244. package/dist/types/richText/richTextExtensions.d.ts +15 -0
  245. package/dist/types/richText/richTextRules.d.ts +41 -0
  246. package/dist/types/table.d.ts +1 -1
  247. package/dist/types/ui/actions/ActionsMenu.d.ts +18 -3
  248. package/dist/types/ui/actions/CopyButton.d.ts +7 -4
  249. package/dist/types/ui/actions/tableRowText.d.ts +6 -0
  250. package/dist/types/ui/cards/RecordCard.d.ts +65 -0
  251. package/dist/types/ui/cards/RecordCardSkeleton.d.ts +17 -0
  252. package/dist/types/ui/countdown/Countdown.d.ts +31 -0
  253. package/dist/types/ui/countdown/countdownParts.d.ts +39 -0
  254. package/dist/types/ui/countdown/useCountdown.d.ts +14 -0
  255. package/dist/types/ui/documents/PdfViewerDialog.d.ts +21 -0
  256. package/dist/types/ui/filters/FilterDrawer.d.ts +34 -0
  257. package/dist/types/ui/filters/filterDrawerFields.d.ts +139 -0
  258. package/dist/types/ui/help/HelpButton.d.ts +15 -0
  259. package/dist/types/ui/help/HelpCarouselDialog.d.ts +35 -0
  260. package/dist/types/ui/help/useHelpCarousel.d.ts +21 -0
  261. package/dist/types/ui/index.d.ts +10 -0
  262. package/dist/types/ui/labels/IconBadge.d.ts +34 -0
  263. package/dist/types/ui/navigation/RouteTabs.d.ts +44 -0
  264. package/dist/types/ui/navigation/routeTabMatching.d.ts +27 -0
  265. package/dist/types/upload/AttachmentsInput.d.ts +62 -0
  266. package/dist/types/upload/FileChip.d.ts +18 -0
  267. package/dist/types/upload/FileDropzone.d.ts +3 -0
  268. package/dist/types/upload/FileInput.d.ts +48 -0
  269. package/dist/types/upload/FileUploadInput.d.ts +80 -0
  270. package/dist/types/upload/ImageInput.d.ts +53 -0
  271. package/dist/types/upload/TemplateDownloadButton.d.ts +39 -0
  272. package/dist/types/upload/fileAppearance.d.ts +8 -0
  273. package/dist/types/upload/fileRules.d.ts +38 -0
  274. package/dist/types/upload/transferProgress.d.ts +18 -0
  275. package/dist/types/upload/uploadMachine.d.ts +6 -2
  276. package/dist/types/upload/uploadWithProgress.d.ts +6 -2
  277. package/dist/types/upload/useFilePicker.d.ts +39 -0
  278. package/dist/ui/actions/ActionsMenu.js +98 -15
  279. package/dist/ui/actions/ActionsMenu.js.map +1 -1
  280. package/dist/ui/actions/CopyButton.js +31 -11
  281. package/dist/ui/actions/CopyButton.js.map +1 -1
  282. package/dist/ui/actions/tableRowText.js +7 -0
  283. package/dist/ui/actions/tableRowText.js.map +1 -0
  284. package/dist/ui/cards/RecordCard.js +145 -0
  285. package/dist/ui/cards/RecordCard.js.map +1 -0
  286. package/dist/ui/cards/RecordCardSkeleton.js +33 -0
  287. package/dist/ui/cards/RecordCardSkeleton.js.map +1 -0
  288. package/dist/ui/countdown/Countdown.js +38 -0
  289. package/dist/ui/countdown/Countdown.js.map +1 -0
  290. package/dist/ui/countdown/countdownParts.js +34 -0
  291. package/dist/ui/countdown/countdownParts.js.map +1 -0
  292. package/dist/ui/countdown/useCountdown.js +35 -0
  293. package/dist/ui/countdown/useCountdown.js.map +1 -0
  294. package/dist/ui/documents/PdfViewerDialog.js +81 -0
  295. package/dist/ui/documents/PdfViewerDialog.js.map +1 -0
  296. package/dist/ui/filters/FilterDrawer.js +211 -0
  297. package/dist/ui/filters/FilterDrawer.js.map +1 -0
  298. package/dist/ui/filters/filterDrawerFields.js +119 -0
  299. package/dist/ui/filters/filterDrawerFields.js.map +1 -0
  300. package/dist/ui/help/HelpButton.js +17 -0
  301. package/dist/ui/help/HelpButton.js.map +1 -0
  302. package/dist/ui/help/HelpCarouselDialog.js +154 -0
  303. package/dist/ui/help/HelpCarouselDialog.js.map +1 -0
  304. package/dist/ui/help/useHelpCarousel.js +25 -0
  305. package/dist/ui/help/useHelpCarousel.js.map +1 -0
  306. package/dist/ui/labels/IconBadge.js +82 -0
  307. package/dist/ui/labels/IconBadge.js.map +1 -0
  308. package/dist/ui/navigation/RouteTabs.js +75 -0
  309. package/dist/ui/navigation/RouteTabs.js.map +1 -0
  310. package/dist/ui/navigation/routeTabMatching.js +23 -0
  311. package/dist/ui/navigation/routeTabMatching.js.map +1 -0
  312. package/dist/upload/AttachmentsInput.js +127 -0
  313. package/dist/upload/AttachmentsInput.js.map +1 -0
  314. package/dist/upload/FileChip.js +27 -0
  315. package/dist/upload/FileChip.js.map +1 -0
  316. package/dist/upload/FileDropzone.js +19 -84
  317. package/dist/upload/FileDropzone.js.map +1 -1
  318. package/dist/upload/FileInput.js +112 -0
  319. package/dist/upload/FileInput.js.map +1 -0
  320. package/dist/upload/FileUploadInput.js +285 -0
  321. package/dist/upload/FileUploadInput.js.map +1 -0
  322. package/dist/upload/ImageInput.js +125 -0
  323. package/dist/upload/ImageInput.js.map +1 -0
  324. package/dist/upload/TemplateDownloadButton.js +67 -0
  325. package/dist/upload/TemplateDownloadButton.js.map +1 -0
  326. package/dist/upload/fileAppearance.js +21 -0
  327. package/dist/upload/fileAppearance.js.map +1 -0
  328. package/dist/upload/fileRules.js +27 -1
  329. package/dist/upload/fileRules.js.map +1 -1
  330. package/dist/upload/transferProgress.js +23 -0
  331. package/dist/upload/transferProgress.js.map +1 -0
  332. package/dist/upload/uploadMachine.js +8 -8
  333. package/dist/upload/uploadMachine.js.map +1 -1
  334. package/dist/upload/uploadWithProgress.js +1 -1
  335. package/dist/upload/uploadWithProgress.js.map +1 -1
  336. package/dist/upload/useFilePicker.js +101 -0
  337. package/dist/upload/useFilePicker.js.map +1 -0
  338. package/package.json +44 -1
@@ -0,0 +1,100 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useId } from "react";
4
+ import Checkbox from "@mui/material/Checkbox";
5
+ import FormControlLabel from "@mui/material/FormControlLabel";
6
+ import FormGroup from "@mui/material/FormGroup";
7
+ import FormHelperText from "@mui/material/FormHelperText";
8
+ import Stack from "@mui/material/Stack";
9
+ import Typography from "@mui/material/Typography";
10
+ import { translateValidationMessage } from "../forms/validationMessages.js";
11
+ import FieldFrame from "./FieldFrame.js";
12
+ import { selectAllStateOf } from "./selectAllState.js";
13
+ import { piecePart, usePieceProps } from "../theme/themePieces.js";
14
+ import { useToolkitTranslation } from "../toolkit/useToolkitTranslation.js";
15
+ import { warnUnknownProps } from "../toolkit/unknownProps.js";
16
+ const THEME_NAME = "CalcitaCheckboxGroupInput";
17
+ const CheckboxGroupRoot = piecePart(THEME_NAME, "Root", FormGroup);
18
+ const CheckboxGroupSelectAll = piecePart(THEME_NAME, "SelectAll", FormControlLabel);
19
+ const CheckboxGroupOption = piecePart(THEME_NAME, "Option", FormControlLabel);
20
+ function CheckboxGroupInput(props) {
21
+ const {
22
+ name,
23
+ label,
24
+ value,
25
+ onChange,
26
+ onBlur,
27
+ options,
28
+ row = false,
29
+ selectAll = false,
30
+ error,
31
+ helperText = " ",
32
+ disabled = false,
33
+ sx,
34
+ texts,
35
+ ...unknownProps
36
+ } = usePieceProps(props, THEME_NAME);
37
+ warnUnknownProps(THEME_NAME, unknownProps);
38
+ const { t } = useToolkitTranslation("common", texts);
39
+ const generatedId = useId();
40
+ const groupId = `${name ?? "checkbox-group"}-${generatedId}`;
41
+ const labelId = `${groupId}-label`;
42
+ const errorText = translateValidationMessage(t, error);
43
+ const checkedValues = Array.isArray(value) ? value : [];
44
+ const selectAllState = selectAllStateOf(checkedValues, options.filter((option) => !option.disabled).map((option) => option.value));
45
+ const inOptionsOrder = (values) => options.map((option) => option.value).filter((optionValue) => values.includes(optionValue));
46
+ const change = (values) => {
47
+ onChange(inOptionsOrder(values));
48
+ onBlur?.();
49
+ };
50
+ const toggle = (optionValue) => change(checkedValues.includes(optionValue) ? checkedValues.filter((checkedValue) => checkedValue !== optionValue) : [...checkedValues, optionValue]);
51
+ 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, ...sx }, children: [
53
+ selectAll && /* @__PURE__ */ jsx(
54
+ CheckboxGroupSelectAll,
55
+ {
56
+ disabled,
57
+ label: selectAllState.allChosen ? t("form.unselectAll") : t("form.selectAll", { count: options.length }),
58
+ control: /* @__PURE__ */ jsx(
59
+ Checkbox,
60
+ {
61
+ size: "small",
62
+ checked: selectAllState.allChosen,
63
+ indeterminate: selectAllState.someChosen,
64
+ onChange: () => change(selectAllState.toggled())
65
+ }
66
+ ),
67
+ ownSx: { "& .MuiFormControlLabel-label": { fontWeight: 600 } }
68
+ }
69
+ ),
70
+ options.map((option, index) => /* @__PURE__ */ jsx(
71
+ CheckboxGroupOption,
72
+ {
73
+ disabled: disabled || option.disabled,
74
+ ownSx: { alignItems: option.description ? "flex-start" : "center" },
75
+ control: /* @__PURE__ */ jsx(
76
+ Checkbox,
77
+ {
78
+ size: "small",
79
+ name,
80
+ id: index === 0 ? `${groupId}-first` : void 0,
81
+ checked: checkedValues.includes(option.value),
82
+ onChange: () => toggle(option.value),
83
+ sx: option.description ? { mt: -0.5 } : void 0
84
+ }
85
+ ),
86
+ label: option.description ? /* @__PURE__ */ jsxs(Stack, { component: "span", children: [
87
+ /* @__PURE__ */ jsx(Typography, { component: "span", variant: "body2", children: option.label }),
88
+ /* @__PURE__ */ jsx(Typography, { component: "span", variant: "caption", color: "text.secondary", children: option.description })
89
+ ] }) : option.label
90
+ },
91
+ option.value
92
+ ))
93
+ ] }),
94
+ /* @__PURE__ */ jsx(FormHelperText, { error: Boolean(errorText), children: errorText ?? helperText })
95
+ ] }) });
96
+ }
97
+ export {
98
+ CheckboxGroupInput as default
99
+ };
100
+ //# sourceMappingURL=CheckboxGroupInput.js.map
@@ -0,0 +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 {translateValidationMessage} from '@/forms/validationMessages.js';\nimport FieldFrame from '@/inputs/FieldFrame.jsx';\nimport {selectAllStateOf} from '@/inputs/selectAllState.js';\nimport {piecePart, usePieceProps} 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 {object} [props.texts] los textos que cambian, con la forma de `common` en los textos del paquete\n */\nexport default function CheckboxGroupInput(props) {\n const {\n name, label, value, onChange, onBlur, options, row = false, selectAll = false, error, helperText = ' ', disabled = false, sx, 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 groupId = `${name ?? 'checkbox-group'}-${generatedId}`;\n const labelId = `${groupId}-label`;\n const errorText = translateValidationMessage(t, 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}}>\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"],"names":[],"mappings":"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"}
@@ -0,0 +1,86 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { useRef, useLayoutEffect } from "react";
4
+ import { maskPatternOf, maskedTextOf, caretPositionOf, rawValueOf } from "./inputMasks.js";
5
+ import TextInput from "./TextInput.js";
6
+ import { warnUnknownProps } from "../toolkit/unknownProps.js";
7
+ const NUMERIC_ONLY = /^[^AXH]*$/;
8
+ function MaskedInput({ mask, value, onChange, inputRef, slotProps, ...textInputProps }) {
9
+ const { unknownProps, knownProps } = splitTextInputProps(textInputProps);
10
+ warnUnknownProps("MaskedInput", unknownProps);
11
+ const pattern = maskPatternOf(mask);
12
+ const ownInputRef = useRef(null);
13
+ const pendingCaret = useRef(null);
14
+ useLayoutEffect(() => {
15
+ const input = ownInputRef.current;
16
+ if (pendingCaret.current === null || !input || input !== input.ownerDocument.activeElement) {
17
+ return;
18
+ }
19
+ input.setSelectionRange(pendingCaret.current, pendingCaret.current);
20
+ pendingCaret.current = null;
21
+ });
22
+ const keepInputRef = (input) => {
23
+ ownInputRef.current = input;
24
+ if (typeof inputRef === "function") {
25
+ inputRef(input);
26
+ } else if (inputRef) {
27
+ inputRef.current = input;
28
+ }
29
+ };
30
+ const change = (typedText) => {
31
+ const caretPosition = ownInputRef.current?.selectionStart ?? typedText.length;
32
+ pendingCaret.current = caretPositionOf(typedText, caretPosition, pattern);
33
+ onChange(rawValueOf(typedText, pattern));
34
+ };
35
+ return /* @__PURE__ */ jsx(
36
+ TextInput,
37
+ {
38
+ ...knownProps,
39
+ value: maskedTextOf(value, pattern),
40
+ onChange: change,
41
+ inputRef: keepInputRef,
42
+ slotProps: {
43
+ ...slotProps,
44
+ // OJO: sin maxLength. El navegador cortaría lo pegado ANTES de limpiarlo: «(555) 123-4567» trae 14 caracteres para
45
+ // un patrón de 12 y se perderían los últimos dígitos. El largo lo cuida la máscara
46
+ htmlInput: {
47
+ inputMode: NUMERIC_ONLY.test(pattern) ? "numeric" : "text",
48
+ autoCapitalize: "characters",
49
+ spellCheck: false,
50
+ ...slotProps?.htmlInput
51
+ }
52
+ }
53
+ }
54
+ );
55
+ }
56
+ const TEXT_INPUT_PROPS = /* @__PURE__ */ new Set([
57
+ "name",
58
+ "label",
59
+ "onBlur",
60
+ "error",
61
+ "helperText",
62
+ "placeholder",
63
+ "startAdornment",
64
+ "endAdornment",
65
+ "clearable",
66
+ "autoComplete",
67
+ "autoFocus",
68
+ "disabled",
69
+ "required",
70
+ "fullWidth",
71
+ "id",
72
+ "sx",
73
+ "texts"
74
+ ]);
75
+ function splitTextInputProps(props) {
76
+ const knownProps = {};
77
+ const unknownProps = {};
78
+ for (const [key, propValue] of Object.entries(props)) {
79
+ (TEXT_INPUT_PROPS.has(key) ? knownProps : unknownProps)[key] = propValue;
80
+ }
81
+ return { knownProps, unknownProps };
82
+ }
83
+ export {
84
+ MaskedInput as default
85
+ };
86
+ //# sourceMappingURL=MaskedInput.js.map
@@ -0,0 +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;AAmBrB,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;AACrD,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;"}
@@ -47,7 +47,18 @@ function NumberInput({
47
47
  fullWidth: true,
48
48
  error: Boolean(errorText),
49
49
  helperText: errorText ?? helperText,
50
- onFocus: () => setIsTyping(true),
50
+ onFocus: (event) => {
51
+ const input = event.target;
52
+ const wasAllSelected = input.value.length > 0 && input.selectionStart === 0 && input.selectionEnd === input.value.length;
53
+ setIsTyping(true);
54
+ if (wasAllSelected) {
55
+ requestAnimationFrame(() => {
56
+ if (document.activeElement === input) {
57
+ input.select();
58
+ }
59
+ });
60
+ }
61
+ },
51
62
  onChange: (event) => onChange(cleanNumberText(event.target.value, decimals, allowNegative)),
52
63
  onBlur: () => {
53
64
  setIsTyping(false);
@@ -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 {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport InputAdornment from '@mui/material/InputAdornment';\nimport TextField from '@mui/material/TextField';\n\nimport {translateValidationMessage} from '@/forms/validationMessages.js';\nimport FieldFrame from '@/inputs/FieldFrame.jsx';\nimport {cleanNumberText, completeDecimals, groupThousands} from '@/inputs/numberText.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\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 {object} [props.texts] los textos que cambian, con la forma de `common` en los textos del paquete\n */\nexport default function NumberInput({\n name, label, value, onChange, onBlur, error, helperText = ' ', decimals = 2, allowNegative = false, prefix, suffix, maxDigits = 17,\n disabled = false, inputRef, texts,\n ...unknownProps\n}) {\n warnUnknownProps('NumberInput', unknownProps);\n const {t} = useToolkitTranslation('common', texts);\n const generatedId = useId();\n const inputId = `${name ?? 'number'}-${generatedId}`;\n const [isTyping, setIsTyping] = useState(false);\n const errorText = translateValidationMessage(t, 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 <TextField\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={() => setIsTyping(true)}\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"],"names":[],"mappings":";;;;;;;;;;AAoBA,MAAM,kBAAkB;AAoBxB,SAAwB,YAAY;AAAA,EAChC;AAAA,EAAM;AAAA,EAAO;AAAA,EAAO;AAAA,EAAU;AAAA,EAAQ;AAAA,EAAO,aAAa;AAAA,EAAK,WAAW;AAAA,EAAG,gBAAgB;AAAA,EAAO;AAAA,EAAQ;AAAA,EAAQ,YAAY;AAAA,EAChI,WAAW;AAAA,EAAO;AAAA,EAAU;AAAA,EAC5B,GAAG;AACP,GAAG;AACC,mBAAiB,eAAe,YAAY;AAC5C,QAAM,EAAC,EAAA,IAAK,sBAAsB,UAAU,KAAK;AACjD,QAAM,cAAc,MAAA;AACpB,QAAM,UAAU,GAAG,QAAQ,QAAQ,IAAI,WAAW;AAClD,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,KAAK;AAC9C,QAAM,YAAY,2BAA2B,GAAG,KAAK;AACrD,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,MAAM,YAAY,IAAI;AAAA,MAC/B,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;"}
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 {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport InputAdornment from '@mui/material/InputAdornment';\nimport TextField from '@mui/material/TextField';\n\nimport {translateValidationMessage} from '@/forms/validationMessages.js';\nimport FieldFrame from '@/inputs/FieldFrame.jsx';\nimport {cleanNumberText, completeDecimals, groupThousands} from '@/inputs/numberText.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\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 {object} [props.texts] los textos que cambian, con la forma de `common` en los textos del paquete\n */\nexport default function NumberInput({\n name, label, value, onChange, onBlur, error, helperText = ' ', decimals = 2, allowNegative = false, prefix, suffix, maxDigits = 17,\n disabled = false, inputRef, texts,\n ...unknownProps\n}) {\n warnUnknownProps('NumberInput', unknownProps);\n const {t} = useToolkitTranslation('common', texts);\n const generatedId = useId();\n const inputId = `${name ?? 'number'}-${generatedId}`;\n const [isTyping, setIsTyping] = useState(false);\n const errorText = translateValidationMessage(t, 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 <TextField\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"],"names":[],"mappings":";;;;;;;;;;AAoBA,MAAM,kBAAkB;AAoBxB,SAAwB,YAAY;AAAA,EAChC;AAAA,EAAM;AAAA,EAAO;AAAA,EAAO;AAAA,EAAU;AAAA,EAAQ;AAAA,EAAO,aAAa;AAAA,EAAK,WAAW;AAAA,EAAG,gBAAgB;AAAA,EAAO;AAAA,EAAQ;AAAA,EAAQ,YAAY;AAAA,EAChI,WAAW;AAAA,EAAO;AAAA,EAAU;AAAA,EAC5B,GAAG;AACP,GAAG;AACC,mBAAiB,eAAe,YAAY;AAC5C,QAAM,EAAC,EAAA,IAAK,sBAAsB,UAAU,KAAK;AACjD,QAAM,cAAc,MAAA;AACpB,QAAM,UAAU,GAAG,QAAQ,QAAQ,IAAI,WAAW;AAClD,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,KAAK;AAC9C,QAAM,YAAY,2BAA2B,GAAG,KAAK;AACrD,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;"}
@@ -0,0 +1,177 @@
1
+ "use client";
2
+ import { jsxs, jsx } from "react/jsx-runtime";
3
+ import { useState, useId } from "react";
4
+ import Box from "@mui/material/Box";
5
+ import Button from "@mui/material/Button";
6
+ import Dialog from "@mui/material/Dialog";
7
+ import DialogActions from "@mui/material/DialogActions";
8
+ import DialogContent from "@mui/material/DialogContent";
9
+ import DialogTitle from "@mui/material/DialogTitle";
10
+ import FormHelperText from "@mui/material/FormHelperText";
11
+ import Paper from "@mui/material/Paper";
12
+ import Stack from "@mui/material/Stack";
13
+ import Typography from "@mui/material/Typography";
14
+ import AddRoundedIcon from "@mui/icons-material/AddRounded";
15
+ import DeleteOutlineRoundedIcon from "@mui/icons-material/DeleteOutlineRounded";
16
+ import RestartAltRoundedIcon from "@mui/icons-material/RestartAltRounded";
17
+ import ConfirmDialog from "../feedback/ConfirmDialog.js";
18
+ import { translateValidationMessage } from "../forms/validationMessages.js";
19
+ import { FormSectionContext } from "./formLayoutContext.js";
20
+ import { piecePart, usePieceProps } from "../theme/themePieces.js";
21
+ import { useToolkitTranslation } from "../toolkit/useToolkitTranslation.js";
22
+ import { warnUnknownProps } from "../toolkit/unknownProps.js";
23
+ const DEFAULT_COLUMNS = Object.freeze({ xs: 1, sm: 2, md: 3 });
24
+ const THEME_NAME = "CalcitaRepeatingFields";
25
+ const RepeatingRoot = piecePart(THEME_NAME, "Root", Stack);
26
+ const RepeatingRow = piecePart(THEME_NAME, "Row", Paper);
27
+ const RepeatingRowHeader = piecePart(THEME_NAME, "RowHeader", Stack);
28
+ const RepeatingRowContent = piecePart(THEME_NAME, "RowContent", Box);
29
+ const RepeatingAddButton = piecePart(THEME_NAME, "AddButton", Button);
30
+ const RepeatingHelper = piecePart(THEME_NAME, "Helper", FormHelperText);
31
+ function gridColumnsOf(columns) {
32
+ const templateOf = (count) => `repeat(${count}, minmax(0, 1fr))`;
33
+ if (typeof columns === "number") {
34
+ return templateOf(columns);
35
+ }
36
+ return Object.fromEntries(Object.entries(columns).map(([breakpoint, count]) => [breakpoint, templateOf(count)]));
37
+ }
38
+ function RepeatingFields(props) {
39
+ const {
40
+ label,
41
+ rowKeys,
42
+ renderRow,
43
+ rowLabelOf,
44
+ rowActionsOf,
45
+ columns = DEFAULT_COLUMNS,
46
+ minRows = 1,
47
+ maxRows = null,
48
+ onAdd,
49
+ onRemove,
50
+ onClear,
51
+ removeBlockedReasonOf,
52
+ confirmRemoveOf,
53
+ rowHasErrorOf,
54
+ error,
55
+ helperText,
56
+ footer,
57
+ addLabel,
58
+ disabled = false,
59
+ sx,
60
+ texts,
61
+ ...unknownProps
62
+ } = usePieceProps(props, THEME_NAME);
63
+ warnUnknownProps(THEME_NAME, unknownProps);
64
+ const { t } = useToolkitTranslation("common", texts);
65
+ const [pendingRemoval, setPendingRemoval] = useState(null);
66
+ const blockedTitleId = `${useId()}-blocked-title`;
67
+ const rowCount = rowKeys.length;
68
+ const isAtMinimum = rowCount <= minRows;
69
+ const isAtMaximum = maxRows != null && rowCount >= maxRows;
70
+ const errorText = translateValidationMessage(t, error);
71
+ const titleOf = (index) => rowLabelOf?.(index) ?? t("repeating.row", { number: index + 1 });
72
+ const requestRemove = (index) => {
73
+ const blockedReason = removeBlockedReasonOf?.(index) ?? null;
74
+ if (blockedReason) {
75
+ setPendingRemoval({ index, kind: "blocked", reason: blockedReason });
76
+ return;
77
+ }
78
+ if (confirmRemoveOf?.(index)) {
79
+ setPendingRemoval({ index, kind: "confirm" });
80
+ return;
81
+ }
82
+ removeOrClear(index);
83
+ };
84
+ const removeOrClear = (index) => {
85
+ if (isAtMinimum) {
86
+ onClear(index);
87
+ } else {
88
+ onRemove(index);
89
+ }
90
+ };
91
+ return /* @__PURE__ */ jsxs(RepeatingRoot, { spacing: 1.5, ownSx: { minWidth: 0, ...sx }, children: [
92
+ label && /* @__PURE__ */ jsx(Typography, { variant: "subtitle2", component: "h3", children: label }),
93
+ rowKeys.map((rowKey, index) => {
94
+ const removeText = isAtMinimum ? t("repeating.clear", { row: titleOf(index) }) : t("repeating.remove", { row: titleOf(index) });
95
+ return /* @__PURE__ */ jsxs(
96
+ RepeatingRow,
97
+ {
98
+ variant: "outlined",
99
+ role: "group",
100
+ "aria-label": titleOf(index),
101
+ ownSx: { p: { xs: 1.5, sm: 2 }, ...rowHasErrorOf?.(index) && { borderColor: "error.main" } },
102
+ children: [
103
+ /* @__PURE__ */ jsxs(RepeatingRowHeader, { direction: "row", ownSx: { alignItems: "center", gap: 1, mb: 1.5 }, children: [
104
+ /* @__PURE__ */ jsx(Typography, { variant: "subtitle2", sx: { flexGrow: 1, minWidth: 0 }, color: rowHasErrorOf?.(index) ? "error" : "text.primary", children: titleOf(index) }),
105
+ rowActionsOf?.(index),
106
+ /* @__PURE__ */ jsx(
107
+ Button,
108
+ {
109
+ size: "small",
110
+ color: "inherit",
111
+ disabled,
112
+ onClick: () => requestRemove(index),
113
+ "aria-label": removeText,
114
+ startIcon: isAtMinimum ? /* @__PURE__ */ jsx(RestartAltRoundedIcon, {}) : /* @__PURE__ */ jsx(DeleteOutlineRoundedIcon, {}),
115
+ children: isAtMinimum ? t("repeating.clearShort") : t("repeating.removeShort")
116
+ }
117
+ )
118
+ ] }),
119
+ /* @__PURE__ */ jsx(FormSectionContext.Provider, { value: false, children: /* @__PURE__ */ jsx(RepeatingRowContent, { ownSx: { display: "grid", gap: 2, gridTemplateColumns: gridColumnsOf(columns), alignItems: "start" }, children: renderRow(index) }) })
120
+ ]
121
+ },
122
+ rowKey
123
+ );
124
+ }),
125
+ /* @__PURE__ */ jsxs(Stack, { direction: "row", sx: { alignItems: "center", gap: 2, flexWrap: "wrap" }, children: [
126
+ /* @__PURE__ */ jsx(
127
+ RepeatingAddButton,
128
+ {
129
+ variant: "outlined",
130
+ startIcon: /* @__PURE__ */ jsx(AddRoundedIcon, {}),
131
+ onClick: onAdd,
132
+ disabled: disabled || isAtMaximum,
133
+ ownSx: { alignSelf: "flex-start" },
134
+ children: addLabel ?? t("repeating.add")
135
+ }
136
+ ),
137
+ maxRows != null && /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "text.secondary", children: t("repeating.maxRows", { count: maxRows }) }),
138
+ footer
139
+ ] }),
140
+ (errorText || helperText) && /* @__PURE__ */ jsx(RepeatingHelper, { error: Boolean(errorText), role: errorText ? "alert" : void 0, ownSx: { mx: 0 }, children: errorText ?? helperText }),
141
+ /* @__PURE__ */ jsx(
142
+ ConfirmDialog,
143
+ {
144
+ open: pendingRemoval?.kind === "confirm",
145
+ title: t("repeating.confirmTitle", { row: pendingRemoval ? titleOf(pendingRemoval.index) : "" }),
146
+ description: t("repeating.confirmText"),
147
+ confirmLabel: t("repeating.removeShort"),
148
+ tone: "error",
149
+ onConfirm: () => {
150
+ removeOrClear(pendingRemoval.index);
151
+ setPendingRemoval(null);
152
+ },
153
+ onClose: () => setPendingRemoval(null),
154
+ texts
155
+ }
156
+ ),
157
+ /* @__PURE__ */ jsxs(
158
+ Dialog,
159
+ {
160
+ open: pendingRemoval?.kind === "blocked",
161
+ onClose: () => setPendingRemoval(null),
162
+ maxWidth: "xs",
163
+ fullWidth: true,
164
+ "aria-labelledby": blockedTitleId,
165
+ children: [
166
+ /* @__PURE__ */ jsx(DialogTitle, { id: blockedTitleId, children: t("repeating.blockedTitle") }),
167
+ /* @__PURE__ */ jsx(DialogContent, { children: /* @__PURE__ */ jsx(Typography, { children: pendingRemoval?.reason }) }),
168
+ /* @__PURE__ */ jsx(DialogActions, { children: /* @__PURE__ */ jsx(Button, { variant: "contained", onClick: () => setPendingRemoval(null), autoFocus: true, children: t("repeating.understood") }) })
169
+ ]
170
+ }
171
+ )
172
+ ] });
173
+ }
174
+ export {
175
+ RepeatingFields as default
176
+ };
177
+ //# sourceMappingURL=RepeatingFields.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RepeatingFields.js","sources":["../../src/inputs/RepeatingFields.jsx"],"sourcesContent":["// Renglones de campos que se repiten (los contactos de un cliente, los precios por volumen, el reparto de un presupuesto):\n// cada renglón con su número y «Quitar», y «Agregar» al final. Es solo la VISTA: los renglones los guarda el formulario\n// (FormRepeatingFields de formik o de react-hook-form los conecta por name).\n//\n// Reglas de la vista:\n// - minRows (por omisión 1): con el mínimo, «Quitar» se vuelve «Limpiar» y deja el renglón vacío (nunca queda la\n// lista sin renglones para escribir);\n// - maxRows: con el máximo, «Agregar» se apaga y dice hasta cuántos;\n// - quitar un renglón lo decide la pantalla: removeBlockedReasonOf(index) da el motivo si NO se puede (sale en una\n// ventana con «Entendido»), y confirmRemoveOf(index) pide confirmar antes (un renglón ya guardado);\n// - el renglón con un error se marca en rojo, para encontrarlo en una lista larga.\n//\n// En escritorio los campos de un renglón van lado a lado (columns); en el teléfono, uno debajo de otro, como tarjetas.\n// Dentro de un renglón los campos van con su etiqueta arriba aunque la lista esté dentro de una FormSection.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaRepeatingFields con defaultProps y\n// styleOverrides de sus partes: root, row, rowHeader, rowContent, addButton, helper.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useId, useState} from 'react';\nimport Box from '@mui/material/Box';\nimport Button from '@mui/material/Button';\nimport Dialog from '@mui/material/Dialog';\nimport DialogActions from '@mui/material/DialogActions';\nimport DialogContent from '@mui/material/DialogContent';\nimport DialogTitle from '@mui/material/DialogTitle';\nimport FormHelperText from '@mui/material/FormHelperText';\nimport Paper from '@mui/material/Paper';\nimport Stack from '@mui/material/Stack';\nimport Typography from '@mui/material/Typography';\nimport AddRoundedIcon from '@mui/icons-material/AddRounded';\nimport DeleteOutlineRoundedIcon from '@mui/icons-material/DeleteOutlineRounded';\nimport RestartAltRoundedIcon from '@mui/icons-material/RestartAltRounded';\n\nimport ConfirmDialog from '@/feedback/ConfirmDialog.jsx';\nimport {translateValidationMessage} from '@/forms/validationMessages.js';\nimport {FormSectionContext} from '@/inputs/formLayoutContext.js';\nimport {piecePart, usePieceProps} from '@/theme/themePieces.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\n// por omisión: uno en el teléfono, dos en tableta, tres en escritorio\nconst DEFAULT_COLUMNS = Object.freeze({xs: 1, sm: 2, md: 3});\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaRepeatingFields)\nconst THEME_NAME = 'CalcitaRepeatingFields';\nconst RepeatingRoot = piecePart(THEME_NAME, 'Root', Stack);\nconst RepeatingRow = piecePart(THEME_NAME, 'Row', Paper);\nconst RepeatingRowHeader = piecePart(THEME_NAME, 'RowHeader', Stack);\nconst RepeatingRowContent = piecePart(THEME_NAME, 'RowContent', Box);\nconst RepeatingAddButton = piecePart(THEME_NAME, 'AddButton', Button);\nconst RepeatingHelper = piecePart(THEME_NAME, 'Helper', FormHelperText);\n\n/**\n * El número de columnas de la rejilla como CSS: {xs: 1, md: 3} → {xs: 'repeat(1, minmax(0, 1fr))', …}.\n *\n * @param {number|Record<string, number>} columns\n * @returns {string|Record<string, string>}\n */\nfunction gridColumnsOf(columns) {\n const templateOf = (count) => `repeat(${count}, minmax(0, 1fr))`;\n if (typeof columns === 'number') {\n return templateOf(columns);\n }\n return Object.fromEntries(Object.entries(columns).map(([breakpoint, count]) => [breakpoint, templateOf(count)]));\n}\n\n/**\n * @param {object} props\n * @param {import('react').ReactNode} [props.label] ya traducido: el título de la lista («Contactos»)\n * @param {string[]} props.rowKeys una llave estable por renglón (no el índice: al quitar uno, los de abajo no deben\n * confundirse con el que se fue)\n * @param {(index: number) => import('react').ReactNode} props.renderRow los campos del renglón\n * @param {(index: number) => string} [props.rowLabelOf] el título de cada renglón; por omisión «Renglón 1», «Renglón 2»…\n * @param {(index: number) => import('react').ReactNode} [props.rowActionsOf] botones suyos en el título del renglón\n * @param {number|Record<string, number>} [props.columns] cuántos campos lado a lado: 2 o {xs: 1, sm: 2, md: 3}\n * @param {number} [props.minRows] cuántos renglones quedan siempre (por omisión 1); con 0 se puede quedar vacía\n * @param {number|null} [props.maxRows] hasta cuántos; sin él, los que quiera\n * @param {() => void} props.onAdd agrega un renglón vacío al final\n * @param {(index: number) => void} props.onRemove quita ese renglón\n * @param {(index: number) => void} props.onClear deja ese renglón vacío (cuando ya no se puede quitar por el mínimo)\n * @param {(index: number) => string|null} [props.removeBlockedReasonOf] si ese renglón NO se puede quitar, el motivo ya\n * traducido (sale en una ventana)\n * @param {(index: number) => boolean} [props.confirmRemoveOf] true si hay que confirmar antes de quitarlo\n * @param {(index: number) => boolean} [props.rowHasErrorOf] true si el renglón tiene un error (se marca en rojo)\n * @param {string|{key: string, values?: object}} [props.error] el error de toda la lista (la suma no alcanza, faltan renglones)\n * @param {import('react').ReactNode} [props.helperText] ya traducida\n * @param {import('react').ReactNode} [props.footer] lo que va junto a «Agregar» (p. ej. «Suman $40,000 de $50,000»)\n * @param {string} [props.addLabel] ya traducido; por omisión «Agregar»\n * @param {boolean} [props.disabled]\n * @param {object} [props.sx]\n * @param {object} [props.texts] los textos que cambian, con la forma de `common` en los textos del paquete\n */\nexport default function RepeatingFields(props) {\n const {\n label, rowKeys, renderRow, rowLabelOf, rowActionsOf, columns = DEFAULT_COLUMNS, minRows = 1, maxRows = null, onAdd, onRemove,\n onClear, removeBlockedReasonOf, confirmRemoveOf, rowHasErrorOf, error, helperText, footer, addLabel, disabled = false, sx, texts,\n ...unknownProps\n } = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const {t} = useToolkitTranslation('common', texts);\n // el renglón que se va a quitar y por qué se pregunta: {index, kind: 'confirm' | 'blocked', reason}\n const [pendingRemoval, setPendingRemoval] = useState(null);\n const blockedTitleId = `${useId()}-blocked-title`;\n const rowCount = rowKeys.length;\n const isAtMinimum = rowCount <= minRows;\n const isAtMaximum = maxRows != null && rowCount >= maxRows;\n const errorText = translateValidationMessage(t, error);\n const titleOf = (index) => rowLabelOf?.(index) ?? t('repeating.row', {number: index + 1});\n\n // quitar (o limpiar, si ya está en el mínimo): primero lo que diga la pantalla\n const requestRemove = (index) => {\n const blockedReason = removeBlockedReasonOf?.(index) ?? null;\n if (blockedReason) {\n setPendingRemoval({index, kind: 'blocked', reason: blockedReason});\n return;\n }\n if (confirmRemoveOf?.(index)) {\n setPendingRemoval({index, kind: 'confirm'});\n return;\n }\n removeOrClear(index);\n };\n const removeOrClear = (index) => {\n if (isAtMinimum) {\n onClear(index);\n } else {\n onRemove(index);\n }\n };\n\n return (\n <RepeatingRoot spacing={1.5} ownSx={{minWidth: 0, ...sx}}>\n {label && <Typography variant=\"subtitle2\" component=\"h3\">{label}</Typography>}\n {rowKeys.map((rowKey, index) => {\n const removeText = isAtMinimum ? t('repeating.clear', {row: titleOf(index)}) : t('repeating.remove', {row: titleOf(index)});\n return (\n <RepeatingRow\n key={rowKey}\n variant=\"outlined\"\n role=\"group\"\n aria-label={titleOf(index)}\n ownSx={{p: {xs: 1.5, sm: 2}, ...(rowHasErrorOf?.(index) && {borderColor: 'error.main'})}}\n >\n <RepeatingRowHeader direction=\"row\" ownSx={{alignItems: 'center', gap: 1, mb: 1.5}}>\n <Typography variant=\"subtitle2\" sx={{flexGrow: 1, minWidth: 0}} color={rowHasErrorOf?.(index) ? 'error' : 'text.primary'}>\n {titleOf(index)}\n </Typography>\n {rowActionsOf?.(index)}\n <Button size=\"small\" color=\"inherit\" disabled={disabled} onClick={() => requestRemove(index)} aria-label={removeText}\n startIcon={isAtMinimum ? <RestartAltRoundedIcon /> : <DeleteOutlineRoundedIcon />}>\n {isAtMinimum ? t('repeating.clearShort') : t('repeating.removeShort')}\n </Button>\n </RepeatingRowHeader>\n {/* los campos del renglón llevan su etiqueta arriba aunque la lista esté dentro de una FormSection */}\n <FormSectionContext.Provider value={false}>\n <RepeatingRowContent ownSx={{display: 'grid', gap: 2, gridTemplateColumns: gridColumnsOf(columns), alignItems: 'start'}}>\n {renderRow(index)}\n </RepeatingRowContent>\n </FormSectionContext.Provider>\n </RepeatingRow>\n );\n })}\n <Stack direction=\"row\" sx={{alignItems: 'center', gap: 2, flexWrap: 'wrap'}}>\n <RepeatingAddButton variant=\"outlined\" startIcon={<AddRoundedIcon />} onClick={onAdd} disabled={disabled || isAtMaximum}\n ownSx={{alignSelf: 'flex-start'}}>\n {addLabel ?? t('repeating.add')}\n </RepeatingAddButton>\n {maxRows != null && (\n <Typography variant=\"caption\" color=\"text.secondary\">{t('repeating.maxRows', {count: maxRows})}</Typography>\n )}\n {footer}\n </Stack>\n {(errorText || helperText) && (\n <RepeatingHelper error={Boolean(errorText)} role={errorText ? 'alert' : undefined} ownSx={{mx: 0}}>\n {errorText ?? helperText}\n </RepeatingHelper>\n )}\n\n <ConfirmDialog\n open={pendingRemoval?.kind === 'confirm'}\n title={t('repeating.confirmTitle', {row: pendingRemoval ? titleOf(pendingRemoval.index) : ''})}\n description={t('repeating.confirmText')}\n confirmLabel={t('repeating.removeShort')}\n tone=\"error\"\n onConfirm={() => {\n removeOrClear(pendingRemoval.index);\n setPendingRemoval(null);\n }}\n onClose={() => setPendingRemoval(null)}\n texts={texts}\n />\n <Dialog open={pendingRemoval?.kind === 'blocked'} onClose={() => setPendingRemoval(null)} maxWidth=\"xs\" fullWidth\n aria-labelledby={blockedTitleId}>\n <DialogTitle id={blockedTitleId}>{t('repeating.blockedTitle')}</DialogTitle>\n <DialogContent><Typography>{pendingRemoval?.reason}</Typography></DialogContent>\n <DialogActions>\n <Button variant=\"contained\" onClick={() => setPendingRemoval(null)} autoFocus>{t('repeating.understood')}</Button>\n </DialogActions>\n </Dialog>\n </RepeatingRoot>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -3,16 +3,20 @@ import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useId } from "react";
4
4
  import { useToolkitTranslation } from "../toolkit/useToolkitTranslation.js";
5
5
  import Box from "@mui/material/Box";
6
+ import Checkbox from "@mui/material/Checkbox";
6
7
  import MenuItem from "@mui/material/MenuItem";
7
8
  import TextField from "@mui/material/TextField";
8
9
  import Typography from "@mui/material/Typography";
9
10
  import { translateValidationMessage } from "../forms/validationMessages.js";
10
11
  import FieldFrame from "./FieldFrame.js";
12
+ import { selectAllStateOf } from "./selectAllState.js";
11
13
  import { piecePart, usePieceProps } from "../theme/themePieces.js";
12
14
  import { warnUnknownProps } from "../toolkit/unknownProps.js";
13
15
  const THEME_NAME = "CalcitaSelectInput";
14
16
  const SelectOption = piecePart(THEME_NAME, "Option", MenuItem);
15
17
  const SelectOptionText = piecePart(THEME_NAME, "OptionText", Box);
18
+ const SELECT_ALL_VALUE = "__calcita_select_all__";
19
+ const WITHOUT_CHECKMARK = Object.freeze({ "&.Mui-selected::after": { content: "none" } });
16
20
  function SelectInput(props) {
17
21
  const {
18
22
  name,
@@ -27,6 +31,8 @@ function SelectInput(props) {
27
31
  disabled = false,
28
32
  inputRef,
29
33
  multiple = false,
34
+ selectAll = false,
35
+ checkboxes = false,
30
36
  texts,
31
37
  ...unknownProps
32
38
  } = usePieceProps(props, THEME_NAME);
@@ -52,9 +58,15 @@ function SelectInput(props) {
52
58
  return options.find((option) => option.value === selectedValue)?.label ?? placeholderText;
53
59
  };
54
60
  const currentValue = multiple ? Array.isArray(value) ? value : [] : value ?? "";
61
+ const selectAllState = selectAllStateOf(currentValue, options.filter((option) => !option.disabled).map((option) => option.value));
55
62
  const handleChange = (event) => {
56
63
  const nextValue = event.target.value;
57
- onChange(multiple && typeof nextValue === "string" ? nextValue.split(",") : nextValue);
64
+ const values = multiple && typeof nextValue === "string" ? nextValue.split(",") : nextValue;
65
+ if (multiple && values.includes(SELECT_ALL_VALUE)) {
66
+ onChange(selectAllState.toggled());
67
+ return;
68
+ }
69
+ onChange(values);
58
70
  };
59
71
  return /* @__PURE__ */ jsx(FieldFrame, { inputId, labelId, label, children: /* @__PURE__ */ jsxs(
60
72
  TextField,
@@ -74,13 +86,39 @@ function SelectInput(props) {
74
86
  slotProps: { select: { multiple, displayEmpty: true, renderValue: renderSelectedValue, labelId } },
75
87
  children: [
76
88
  !multiple && /* @__PURE__ */ jsx(MenuItem, { value: "", disabled: true, children: placeholderText }),
89
+ multiple && selectAll && /* @__PURE__ */ jsxs(SelectOption, { value: SELECT_ALL_VALUE, ownSx: { fontWeight: 600, ...WITHOUT_CHECKMARK }, children: [
90
+ /* @__PURE__ */ jsx(
91
+ Checkbox,
92
+ {
93
+ size: "small",
94
+ tabIndex: -1,
95
+ disableRipple: true,
96
+ sx: { p: 0, mr: 1 },
97
+ checked: selectAllState.allChosen,
98
+ indeterminate: selectAllState.someChosen
99
+ }
100
+ ),
101
+ selectAllState.allChosen ? t("form.unselectAll") : t("form.selectAll", { count: options.length })
102
+ ] }),
77
103
  options.map((option) => (
78
104
  // la opción en renglón y su nombre con su explicación en un bloque: con la opción en columna, la palomita del
79
105
  // elegido (el tema la pinta al final) se bajaba a otro renglón (lo cachó una captura de Tareas)
80
- /* @__PURE__ */ jsx(SelectOption, { value: option.value, ownSx: { whiteSpace: "normal" }, children: /* @__PURE__ */ jsxs(SelectOptionText, { component: "span", ownSx: { display: "flex", flexDirection: "column", minWidth: 0, flexGrow: 1 }, children: [
81
- /* @__PURE__ */ jsx(Typography, { component: "span", variant: "body2", children: option.label }),
82
- option.description && /* @__PURE__ */ jsx(Typography, { component: "span", variant: "caption", color: "text.secondary", children: option.description })
83
- ] }) }, option.value)
106
+ /* @__PURE__ */ jsxs(
107
+ SelectOption,
108
+ {
109
+ value: option.value,
110
+ disabled: option.disabled,
111
+ ownSx: { whiteSpace: "normal", ...multiple && checkboxes && WITHOUT_CHECKMARK },
112
+ children: [
113
+ multiple && checkboxes && /* @__PURE__ */ jsx(Checkbox, { size: "small", tabIndex: -1, disableRipple: true, sx: { p: 0, mr: 1 }, checked: currentValue.includes(option.value) }),
114
+ /* @__PURE__ */ jsxs(SelectOptionText, { component: "span", ownSx: { display: "flex", flexDirection: "column", minWidth: 0, flexGrow: 1 }, children: [
115
+ /* @__PURE__ */ jsx(Typography, { component: "span", variant: "body2", children: option.label }),
116
+ option.description && /* @__PURE__ */ jsx(Typography, { component: "span", variant: "caption", color: "text.secondary", children: option.description })
117
+ ] })
118
+ ]
119
+ },
120
+ option.value
121
+ )
84
122
  ))
85
123
  ]
86
124
  }