@stackcraftmx/calcita 0.32.2 → 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 (336) 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/table.js +2 -0
  166. package/dist/table.js.map +1 -1
  167. package/dist/theme/components/glassComponents.js +27 -0
  168. package/dist/theme/components/glassComponents.js.map +1 -1
  169. package/dist/theme/components/minimalComponents.js +12 -0
  170. package/dist/theme/components/minimalComponents.js.map +1 -1
  171. package/dist/types/DataTable/ServerTableFilterDrawer.d.ts +21 -0
  172. package/dist/types/DataTable/filterContext.d.ts +2 -1
  173. package/dist/types/DataTable/goToPageOptions.d.ts +15 -0
  174. package/dist/types/DataTable/index.d.ts +1 -0
  175. package/dist/types/date-pickers.d.ts +10 -1
  176. package/dist/types/datePickers/DateInput.d.ts +6 -4
  177. package/dist/types/datePickers/DateTimeInput.d.ts +50 -0
  178. package/dist/types/datePickers/FormDateTimeField.d.ts +7 -0
  179. package/dist/types/datePickers/FormMonthField.d.ts +7 -0
  180. package/dist/types/datePickers/FormTimeField.d.ts +7 -0
  181. package/dist/types/datePickers/MonthInput.d.ts +39 -0
  182. package/dist/types/datePickers/PickerFrame.d.ts +22 -0
  183. package/dist/types/datePickers/TimeInput.d.ts +38 -0
  184. package/dist/types/datePickers/dateTimeRules.d.ts +31 -0
  185. package/dist/types/datePickers/pickerValues.d.ts +114 -0
  186. package/dist/types/datePickers/useCurrentMinute.d.ts +5 -0
  187. package/dist/types/datePickers/usePickerField.d.ts +24 -0
  188. package/dist/types/dictionaries/dictionaries.d.ts +6 -3
  189. package/dist/types/forms/FormAttachmentsField.d.ts +7 -0
  190. package/dist/types/forms/FormCheckboxGroupField.d.ts +7 -0
  191. package/dist/types/forms/FormFileField.d.ts +7 -0
  192. package/dist/types/forms/FormFileUploadField.d.ts +7 -0
  193. package/dist/types/forms/FormImageField.d.ts +7 -0
  194. package/dist/types/forms/FormMaskedField.d.ts +7 -0
  195. package/dist/types/forms/FormRepeatingFields.d.ts +25 -0
  196. package/dist/types/forms/FormTransferListField.d.ts +7 -0
  197. package/dist/types/forms/index.d.ts +10 -1
  198. package/dist/types/forms/listRules.d.ts +48 -0
  199. package/dist/types/forms/serverProblemFields.d.ts +13 -0
  200. package/dist/types/forms.d.ts +1 -1
  201. package/dist/types/hookForm/FormAttachmentsField.d.ts +8 -0
  202. package/dist/types/hookForm/FormCheckboxGroupField.d.ts +8 -0
  203. package/dist/types/hookForm/FormFileField.d.ts +8 -0
  204. package/dist/types/hookForm/FormFileUploadField.d.ts +8 -0
  205. package/dist/types/hookForm/FormImageField.d.ts +8 -0
  206. package/dist/types/hookForm/FormMaskedField.d.ts +9 -0
  207. package/dist/types/hookForm/FormRepeatingFields.d.ts +27 -0
  208. package/dist/types/hookForm/FormTransferListField.d.ts +8 -0
  209. package/dist/types/index.d.ts +25 -3
  210. package/dist/types/inputs/CatalogInput.d.ts +7 -1
  211. package/dist/types/inputs/CheckboxGroupInput.d.ts +39 -0
  212. package/dist/types/inputs/MaskedInput.d.ts +35 -0
  213. package/dist/types/inputs/RepeatingFields.d.ts +52 -0
  214. package/dist/types/inputs/SelectInput.d.ts +6 -1
  215. package/dist/types/inputs/TransferListInput.d.ts +43 -0
  216. package/dist/types/inputs/inputMasks.d.ts +52 -0
  217. package/dist/types/inputs/selectAllState.d.ts +11 -0
  218. package/dist/types/inputs/useRowKeys.d.ts +11 -0
  219. package/dist/types/legal/CookieNotice.d.ts +30 -0
  220. package/dist/types/legal/TermsAcceptanceDialog.d.ts +32 -0
  221. package/dist/types/legal/consentStorage.d.ts +16 -0
  222. package/dist/types/legal/useCookieConsent.d.ts +13 -0
  223. package/dist/types/locales/en.d.ts +232 -0
  224. package/dist/types/locales/es.d.ts +232 -0
  225. package/dist/types/mexico/CfdiSummary.d.ts +17 -0
  226. package/dist/types/mexico/EfirmaFields.d.ts +38 -0
  227. package/dist/types/mexico/certificateReader.d.ts +37 -0
  228. package/dist/types/mexico/cfdiReader.d.ts +95 -0
  229. package/dist/types/mexico/derReader.d.ts +54 -0
  230. package/dist/types/mexico/efirmaKey.d.ts +26 -0
  231. package/dist/types/mexico/mexicoIdentifiers.d.ts +61 -0
  232. package/dist/types/mexico/mexicoRules.d.ts +29 -0
  233. package/dist/types/mexico.d.ts +11 -0
  234. package/dist/types/react-hook-form.d.ts +10 -1
  235. package/dist/types/rich-text.d.ts +5 -0
  236. package/dist/types/richText/FormRichTextField.d.ts +7 -0
  237. package/dist/types/richText/RichTextEditor.d.ts +42 -0
  238. package/dist/types/richText/RichTextLinkPopover.d.ts +17 -0
  239. package/dist/types/richText/RichTextToolbar.d.ts +15 -0
  240. package/dist/types/richText/RichTextView.d.ts +13 -0
  241. package/dist/types/richText/richTextContentSx.d.ts +7 -0
  242. package/dist/types/richText/richTextExtensions.d.ts +15 -0
  243. package/dist/types/richText/richTextRules.d.ts +41 -0
  244. package/dist/types/table.d.ts +1 -1
  245. package/dist/types/ui/actions/ActionsMenu.d.ts +18 -3
  246. package/dist/types/ui/actions/CopyButton.d.ts +7 -4
  247. package/dist/types/ui/actions/tableRowText.d.ts +6 -0
  248. package/dist/types/ui/cards/RecordCard.d.ts +65 -0
  249. package/dist/types/ui/cards/RecordCardSkeleton.d.ts +17 -0
  250. package/dist/types/ui/countdown/Countdown.d.ts +31 -0
  251. package/dist/types/ui/countdown/countdownParts.d.ts +39 -0
  252. package/dist/types/ui/countdown/useCountdown.d.ts +14 -0
  253. package/dist/types/ui/documents/PdfViewerDialog.d.ts +21 -0
  254. package/dist/types/ui/filters/FilterDrawer.d.ts +34 -0
  255. package/dist/types/ui/filters/filterDrawerFields.d.ts +139 -0
  256. package/dist/types/ui/help/HelpButton.d.ts +15 -0
  257. package/dist/types/ui/help/HelpCarouselDialog.d.ts +35 -0
  258. package/dist/types/ui/help/useHelpCarousel.d.ts +21 -0
  259. package/dist/types/ui/index.d.ts +10 -0
  260. package/dist/types/ui/labels/IconBadge.d.ts +34 -0
  261. package/dist/types/ui/navigation/RouteTabs.d.ts +44 -0
  262. package/dist/types/ui/navigation/routeTabMatching.d.ts +27 -0
  263. package/dist/types/upload/AttachmentsInput.d.ts +62 -0
  264. package/dist/types/upload/FileChip.d.ts +18 -0
  265. package/dist/types/upload/FileDropzone.d.ts +3 -0
  266. package/dist/types/upload/FileInput.d.ts +48 -0
  267. package/dist/types/upload/FileUploadInput.d.ts +80 -0
  268. package/dist/types/upload/ImageInput.d.ts +53 -0
  269. package/dist/types/upload/TemplateDownloadButton.d.ts +39 -0
  270. package/dist/types/upload/fileAppearance.d.ts +8 -0
  271. package/dist/types/upload/fileRules.d.ts +38 -0
  272. package/dist/types/upload/transferProgress.d.ts +18 -0
  273. package/dist/types/upload/uploadMachine.d.ts +6 -2
  274. package/dist/types/upload/uploadWithProgress.d.ts +6 -2
  275. package/dist/types/upload/useFilePicker.d.ts +39 -0
  276. package/dist/ui/actions/ActionsMenu.js +98 -15
  277. package/dist/ui/actions/ActionsMenu.js.map +1 -1
  278. package/dist/ui/actions/CopyButton.js +31 -11
  279. package/dist/ui/actions/CopyButton.js.map +1 -1
  280. package/dist/ui/actions/tableRowText.js +7 -0
  281. package/dist/ui/actions/tableRowText.js.map +1 -0
  282. package/dist/ui/cards/RecordCard.js +145 -0
  283. package/dist/ui/cards/RecordCard.js.map +1 -0
  284. package/dist/ui/cards/RecordCardSkeleton.js +33 -0
  285. package/dist/ui/cards/RecordCardSkeleton.js.map +1 -0
  286. package/dist/ui/countdown/Countdown.js +38 -0
  287. package/dist/ui/countdown/Countdown.js.map +1 -0
  288. package/dist/ui/countdown/countdownParts.js +34 -0
  289. package/dist/ui/countdown/countdownParts.js.map +1 -0
  290. package/dist/ui/countdown/useCountdown.js +35 -0
  291. package/dist/ui/countdown/useCountdown.js.map +1 -0
  292. package/dist/ui/documents/PdfViewerDialog.js +81 -0
  293. package/dist/ui/documents/PdfViewerDialog.js.map +1 -0
  294. package/dist/ui/filters/FilterDrawer.js +211 -0
  295. package/dist/ui/filters/FilterDrawer.js.map +1 -0
  296. package/dist/ui/filters/filterDrawerFields.js +119 -0
  297. package/dist/ui/filters/filterDrawerFields.js.map +1 -0
  298. package/dist/ui/help/HelpButton.js +17 -0
  299. package/dist/ui/help/HelpButton.js.map +1 -0
  300. package/dist/ui/help/HelpCarouselDialog.js +154 -0
  301. package/dist/ui/help/HelpCarouselDialog.js.map +1 -0
  302. package/dist/ui/help/useHelpCarousel.js +25 -0
  303. package/dist/ui/help/useHelpCarousel.js.map +1 -0
  304. package/dist/ui/labels/IconBadge.js +82 -0
  305. package/dist/ui/labels/IconBadge.js.map +1 -0
  306. package/dist/ui/navigation/RouteTabs.js +75 -0
  307. package/dist/ui/navigation/RouteTabs.js.map +1 -0
  308. package/dist/ui/navigation/routeTabMatching.js +23 -0
  309. package/dist/ui/navigation/routeTabMatching.js.map +1 -0
  310. package/dist/upload/AttachmentsInput.js +127 -0
  311. package/dist/upload/AttachmentsInput.js.map +1 -0
  312. package/dist/upload/FileChip.js +27 -0
  313. package/dist/upload/FileChip.js.map +1 -0
  314. package/dist/upload/FileDropzone.js +19 -84
  315. package/dist/upload/FileDropzone.js.map +1 -1
  316. package/dist/upload/FileInput.js +112 -0
  317. package/dist/upload/FileInput.js.map +1 -0
  318. package/dist/upload/FileUploadInput.js +285 -0
  319. package/dist/upload/FileUploadInput.js.map +1 -0
  320. package/dist/upload/ImageInput.js +125 -0
  321. package/dist/upload/ImageInput.js.map +1 -0
  322. package/dist/upload/TemplateDownloadButton.js +67 -0
  323. package/dist/upload/TemplateDownloadButton.js.map +1 -0
  324. package/dist/upload/fileAppearance.js +21 -0
  325. package/dist/upload/fileAppearance.js.map +1 -0
  326. package/dist/upload/fileRules.js +27 -1
  327. package/dist/upload/fileRules.js.map +1 -1
  328. package/dist/upload/transferProgress.js +23 -0
  329. package/dist/upload/transferProgress.js.map +1 -0
  330. package/dist/upload/uploadMachine.js +8 -8
  331. package/dist/upload/uploadMachine.js.map +1 -1
  332. package/dist/upload/uploadWithProgress.js +1 -1
  333. package/dist/upload/uploadWithProgress.js.map +1 -1
  334. package/dist/upload/useFilePicker.js +101 -0
  335. package/dist/upload/useFilePicker.js.map +1 -0
  336. package/package.json +44 -1
@@ -0,0 +1,80 @@
1
+ /** En qué va el archivo de un renglón (onStatusChange). */
2
+ export declare const FILE_UPLOAD_STATUS: Readonly<{
3
+ pending: "pending";
4
+ uploading: "uploading";
5
+ processing: "processing";
6
+ uploaded: "uploaded";
7
+ failed: "failed";
8
+ }>;
9
+ /**
10
+ * @param {object} props
11
+ * @param {string} [props.name]
12
+ * @param {import('react').ReactNode} [props.label] ya traducida: el nombre del documento; sin ella, el renglón va solo
13
+ * @param {string|null} [props.value] lo que el servidor dio para reconocer el archivo subido ('' o null = ninguno)
14
+ * @param {(value: string) => void} props.onChange recibe el id al terminar de subir, '' al quitarlo o mientras sube otro
15
+ * @param {() => void} [props.onBlur] se llama al elegir o quitar (para que el formulario lo marque como tocado)
16
+ * @param {(file: File, options: {signal: AbortSignal, idempotencyKey: string, onUploadProgress: (transfer: {loaded: number, total: number, rate?: number, estimated?: number}) => void}) => Promise<any>} props.upload
17
+ * sube el archivo (con uploadWithProgress, axios o el cliente del proyecto) y regresa lo que conteste el servidor
18
+ * @param {(result: any) => string} [props.idOf] de la respuesta, lo que se guarda en el formulario; por omisión result.id
19
+ * @param {{name: string, size?: number}|null} [props.uploadedFile] el que ya estaba subido, para enseñar su nombre al editar
20
+ * @param {(value: string, transferOptions: object) => Promise<{blob: Blob, fileName: string}>} [props.download] baja el
21
+ * que ya subió (pasa por el panel de descargas); sin él no sale el ícono
22
+ * @param {string|string[]} [props.accept] 'pdf' | ['pdf', 'image'] | '.xlsx,.xls' | 'image/*'
23
+ * @param {number|string} [props.maxSize] bytes o '10 MB'
24
+ * @param {string} [props.limitsText] ya traducido: qué acepta, en palabras («PDF o foto, hasta 5 MB»); sale al pasar sobre
25
+ * el botón. Por omisión solo el peso máximo
26
+ * @param {string|{key: string, values?: object}} [props.error]
27
+ * @param {string} [props.helperText] ya traducida
28
+ * @param {boolean} [props.disabled]
29
+ * @param {import('react').ReactNode} [props.actions] lo del negocio junto al estado (Aprobado, Falta información)
30
+ * @param {(result: any) => void} [props.onUploaded] terminó de subir (p. ej. para enseñar el folio de la respuesta)
31
+ * @param {(error: unknown) => void} [props.onUploadFailed] no se pudo subir (el motivo ya sale abajo)
32
+ * @param {() => void} [props.onCancel] la persona canceló a media subida
33
+ * @param {() => void} [props.onRemove] la persona quitó el archivo
34
+ * @param {(status: string) => void} [props.onStatusChange] uno de FILE_UPLOAD_STATUS, cada vez que cambia
35
+ * @param {object} [props.sx] ajustes del renglón
36
+ * @param {object} [props.texts] los textos que cambian, con la forma de `common` en los textos del paquete
37
+ */
38
+ export default function FileUploadInput(props: {
39
+ name?: string;
40
+ label?: import('react').ReactNode;
41
+ value?: string | null;
42
+ onChange: (value: string) => void;
43
+ onBlur?: () => void;
44
+ upload: (file: File, options: {
45
+ signal: AbortSignal;
46
+ idempotencyKey: string;
47
+ onUploadProgress: (transfer: {
48
+ loaded: number;
49
+ total: number;
50
+ rate?: number;
51
+ estimated?: number;
52
+ }) => void;
53
+ }) => Promise<any>;
54
+ idOf?: (result: any) => string;
55
+ uploadedFile?: {
56
+ name: string;
57
+ size?: number;
58
+ } | null;
59
+ download?: (value: string, transferOptions: object) => Promise<{
60
+ blob: Blob;
61
+ fileName: string;
62
+ }>;
63
+ accept?: string | string[];
64
+ maxSize?: number | string;
65
+ limitsText?: string;
66
+ error?: string | {
67
+ key: string;
68
+ values?: object;
69
+ };
70
+ helperText?: string;
71
+ disabled?: boolean;
72
+ actions?: import('react').ReactNode;
73
+ onUploaded?: (result: any) => void;
74
+ onUploadFailed?: (error: unknown) => void;
75
+ onCancel?: () => void;
76
+ onRemove?: () => void;
77
+ onStatusChange?: (status: string) => void;
78
+ sx?: object;
79
+ texts?: object;
80
+ }): import("react").JSX.Element;
@@ -0,0 +1,53 @@
1
+ /** La forma de la vista previa: círculo para una persona, recuadro para lo demás. */
2
+ export declare const IMAGE_SHAPE: Readonly<{
3
+ circle: "circle";
4
+ rounded: "rounded";
5
+ }>;
6
+ /**
7
+ * @param {object} props
8
+ * @param {string} [props.name]
9
+ * @param {import('react').ReactNode} [props.label] ya traducida
10
+ * @param {File|string|null} [props.value] la imagen nueva, la dirección de la que ya está guardada o null
11
+ * @param {(file: File|null) => void} props.onChange null al quitarla
12
+ * @param {() => void} [props.onBlur] se llama al elegir o quitar (para que el formulario lo marque como tocado)
13
+ * @param {'circle'|'rounded'} [props.shape] IMAGE_SHAPE; por omisión, círculo
14
+ * @param {number} [props.size] el lado de la vista previa en px; por omisión 96
15
+ * @param {string} [props.alt] ya traducido: qué se ve en la imagen (por omisión, la etiqueta)
16
+ * @param {import('react').ReactNode} [props.placeholder] lo que se ve sin imagen; por omisión, un ícono
17
+ * @param {string|string[]} [props.accept] por omisión, cualquier imagen
18
+ * @param {number|string} [props.maxSize] bytes o '2 MB'
19
+ * @param {string|{key: string, values?: object}} [props.error]
20
+ * @param {string} [props.helperText] ya traducida (p. ej. «JPG o PNG, cuadrada, hasta 2 MB»)
21
+ * @param {string} [props.chooseLabel] ya traducido; por omisión «Elegir imagen» (o «Cambiar la imagen» si ya hay una)
22
+ * @param {string} [props.removeLabel] ya traducido; por omisión «Quitar la imagen»
23
+ * @param {boolean} [props.disabled]
24
+ * @param {boolean} [props.busy] candado mientras se sube
25
+ * @param {(file: File, reason: string) => void} [props.onReject] reason es uno de FILE_REJECTION; sin él, un aviso
26
+ * @param {object} [props.sx]
27
+ * @param {object} [props.texts] los textos que cambian, con la forma de `common` en los textos del paquete
28
+ */
29
+ export default function ImageInput(props: {
30
+ name?: string;
31
+ label?: import('react').ReactNode;
32
+ value?: File | string | null;
33
+ onChange: (file: File | null) => void;
34
+ onBlur?: () => void;
35
+ shape?: 'circle' | 'rounded';
36
+ size?: number;
37
+ alt?: string;
38
+ placeholder?: import('react').ReactNode;
39
+ accept?: string | string[];
40
+ maxSize?: number | string;
41
+ error?: string | {
42
+ key: string;
43
+ values?: object;
44
+ };
45
+ helperText?: string;
46
+ chooseLabel?: string;
47
+ removeLabel?: string;
48
+ disabled?: boolean;
49
+ busy?: boolean;
50
+ onReject?: (file: File, reason: string) => void;
51
+ sx?: object;
52
+ texts?: object;
53
+ }): import("react").JSX.Element;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * @param {object} props
3
+ * @param {string} [props.href] un archivo fijo; se baja con un enlace
4
+ * @param {string} [props.fileName] con href: con qué nombre se guarda (por omisión, el del archivo)
5
+ * @param {(transferOptions: {signal: AbortSignal, onDownloadProgress: (progress: {loaded: number, total?: number|null}) => void}) => Promise<{blob: Blob, fileName: string}>} [props.request]
6
+ * lo pide al API y va al panel de descargas (startDownload)
7
+ * @param {string} [props.format] con request: el tipo de archivo, para su ícono en el panel (xlsx, csv…)
8
+ * @param {string} [props.label] ya traducido; por omisión «Descargar la plantilla»
9
+ * @param {() => void} [props.onDownload] al empezar la descarga (para medir cuántos la bajan, por ejemplo)
10
+ * @param {'text'|'outlined'|'contained'} [props.variant]
11
+ * @param {'small'|'medium'|'large'} [props.size]
12
+ * @param {boolean} [props.disabled]
13
+ * @param {import('react').ReactNode} [props.startIcon] por omisión, la flecha de descarga
14
+ * @param {object} [props.sx]
15
+ * @param {object} [props.texts] los textos que cambian, con la forma de `common`
16
+ */
17
+ export default function TemplateDownloadButton(props: {
18
+ href?: string;
19
+ fileName?: string;
20
+ request?: (transferOptions: {
21
+ signal: AbortSignal;
22
+ onDownloadProgress: (progress: {
23
+ loaded: number;
24
+ total?: number | null;
25
+ }) => void;
26
+ }) => Promise<{
27
+ blob: Blob;
28
+ fileName: string;
29
+ }>;
30
+ format?: string;
31
+ label?: string;
32
+ onDownload?: () => void;
33
+ variant?: 'text' | 'outlined' | 'contained';
34
+ size?: 'small' | 'medium' | 'large';
35
+ disabled?: boolean;
36
+ startIcon?: import('react').ReactNode;
37
+ sx?: object;
38
+ texts?: object;
39
+ }): import("react").JSX.Element;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @param {string} fileName
3
+ * @returns {{Icon: import('react').ElementType, color: string}}
4
+ */
5
+ export declare function fileAppearanceOf(fileName: string): {
6
+ Icon: import('react').ElementType;
7
+ color: string;
8
+ };
@@ -49,3 +49,41 @@ export declare function matchesAccept(file: {
49
49
  * @returns {number|null} null si no hay límite
50
50
  */
51
51
  export declare function parseFileSize(size: number | string | undefined | null): number | null;
52
+ /**
53
+ * Lo que queda al elegir o soltar archivos: cuáles pasan y cuáles no, con su motivo. La usan los cargadores de MUI del
54
+ * paquete (la zona, el compacto, «Adjuntar» y la imagen), así rechazan igual y con los mismos motivos.
55
+ *
56
+ * Un archivo con el MISMO nombre que uno que ya estaba lo reemplaza (se volvió a elegir corregido) y no cuenta doble ni
57
+ * en la cantidad ni en el peso. Se revisa en este orden: tipo, peso del archivo, cantidad y peso de todos juntos.
58
+ *
59
+ * @param {Array<{name: string, type?: string, size?: number}>} pickedFiles lo que se eligió o se soltó
60
+ * @param {object} rules
61
+ * @param {string} [rules.resolvedAccept] lo que regresa resolveAccept
62
+ * @param {number|null} [rules.maxBytes] el peso máximo de cada archivo
63
+ * @param {number} [rules.maxFiles] cuántos como máximo entre los que ya estaban y los nuevos
64
+ * @param {number|null} [rules.maxTotalBytes] cuánto pueden pesar todos juntos
65
+ * @param {Array<{name: string, size?: number}>} [rules.keptFiles] los que ya estaban (un archivo guardado sin peso cuenta 0)
66
+ * @returns {{files: Array<object>, accepted: Array<object>, rejected: Array<{file: object, reason: string}>}} files: los
67
+ * que ya estaban (sin los reemplazados) y después los nuevos que pasaron
68
+ */
69
+ export declare function screenFiles(pickedFiles: Array<{
70
+ name: string;
71
+ type?: string;
72
+ size?: number;
73
+ }>, { resolvedAccept, maxBytes, maxFiles, maxTotalBytes, keptFiles }?: {
74
+ resolvedAccept?: string;
75
+ maxBytes?: number | null;
76
+ maxFiles?: number;
77
+ maxTotalBytes?: number | null;
78
+ keptFiles?: Array<{
79
+ name: string;
80
+ size?: number;
81
+ }>;
82
+ }): {
83
+ files: Array<object>;
84
+ accepted: Array<object>;
85
+ rejected: Array<{
86
+ file: object;
87
+ reason: string;
88
+ }>;
89
+ };
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @param {{loaded: number, total: number, rate?: number, estimated?: number}} transfer lo que avisa la subida (bytes)
3
+ * @param {number} startedAt cuándo empezó la subida, en milisegundos
4
+ * @param {number} [now]
5
+ * @returns {{percent: number, loaded: number, total: number, bytesPerSecond: number|null, remainingSeconds: number|null}}
6
+ */
7
+ export declare function transferProgressOf({ loaded, total, rate, estimated }: {
8
+ loaded: number;
9
+ total: number;
10
+ rate?: number;
11
+ estimated?: number;
12
+ }, startedAt: number, now?: number): {
13
+ percent: number;
14
+ loaded: number;
15
+ total: number;
16
+ bytesPerSecond: number | null;
17
+ remainingSeconds: number | null;
18
+ };
@@ -16,8 +16,12 @@ export declare const uploadMachine: import("xstate").StateMachine<import("xstate
16
16
  *
17
17
  * fromCallback y no fromPromise: hacen falta EVENTOS a medio camino (el avance) y un CLEANUP que aborte la petición.
18
18
  *
19
- * @param {(input: object, onProgress: (percent: number) => void, signal: AbortSignal) => Promise<unknown>} uploadFile
19
+ * @param {(input: object, onProgress: (percent: number, transfer?: {loaded: number, total: number}) => void, signal: AbortSignal) => Promise<unknown>} uploadFile
20
+ * onProgress lleva, si se sabe, cuánto va y cuánto pesa (para la velocidad y lo que falta)
20
21
  */
21
- export declare function buildUploadActors(uploadFile: (input: object, onProgress: (percent: number) => void, signal: AbortSignal) => Promise<unknown>): {
22
+ export declare function buildUploadActors(uploadFile: (input: object, onProgress: (percent: number, transfer?: {
23
+ loaded: number;
24
+ total: number;
25
+ }) => void, signal: AbortSignal) => Promise<unknown>): {
22
26
  upload: import("xstate").CallbackActorLogic<import("xstate").EventObject, import("xstate").NonReducibleUnknown, import("xstate").EventObject>;
23
27
  };
@@ -3,14 +3,18 @@
3
3
  * @param {FormData} formData los archivos y los campos que pida el backend
4
4
  * @param {object} [options]
5
5
  * @param {Record<string, string>} [options.headers] p. ej. Authorization; Accept-Language ya va con el idioma activo
6
- * @param {(percent: number) => void} [options.onProgress] 0 a 100 de lo que ya viajó del cuerpo
6
+ * @param {(percent: number, transfer: {loaded: number, total: number}) => void} [options.onProgress] 0 a 100 de lo que ya
7
+ * viajó del cuerpo, y los bytes (para la velocidad y lo que falta)
7
8
  * @param {AbortSignal} [options.signal]
8
9
  * @param {boolean} [options.withCredentials] manda las cookies a otro dominio (sesión de Sanctum en otro subdominio)
9
10
  * @returns {Promise<Response>}
10
11
  */
11
12
  export declare function uploadWithProgress(url: string, formData: FormData, { headers, onProgress, signal, withCredentials }?: {
12
13
  headers?: Record<string, string>;
13
- onProgress?: (percent: number) => void;
14
+ onProgress?: (percent: number, transfer: {
15
+ loaded: number;
16
+ total: number;
17
+ }) => void;
14
18
  signal?: AbortSignal;
15
19
  withCredentials?: boolean;
16
20
  }): Promise<Response>;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * @param {object} options
3
+ * @param {string|string[]} [options.accept] 'excel' | ['excel', 'pdf'] | '.xlsx,.xls' | 'image/*'
4
+ * @param {number|string} [options.maxSize] el peso máximo de cada archivo: bytes o '10 MB'
5
+ * @param {boolean} [options.multiple] varios a la vez; sin él, solo el primero que se elija o se suelte
6
+ * @param {number} [options.maxFiles] con multiple: cuántos como máximo, contando los que ya estaban
7
+ * @param {number|string} [options.maxTotalSize] con multiple: cuánto pueden pesar todos juntos
8
+ * @param {Array<{name: string, size?: number}>} [options.keptFiles] con multiple: los que ya estaban (cuentan en los topes)
9
+ * @param {boolean} [options.disabled] no se puede elegir ni soltar (también mientras se sube)
10
+ * @param {(picked: any) => void} options.onAccept sin multiple, el archivo; con multiple, la lista completa (los que ya
11
+ * estaban y los nuevos), lista para guardarse
12
+ * @param {(file: File, reason: string) => void} [options.onReject] reason es uno de FILE_REJECTION; sin él, un aviso
13
+ * @param {object} [options.texts] los textos que cambian, con la forma de `common`
14
+ * @returns {{inputProps: object, open: () => void, pickFiles: (fileList: FileList|File[]|null|undefined) => void, isDragging: boolean, dropHandlers: object, resolvedAccept: string|undefined, maxBytes: number|null, maxTotalBytes: number|null}}
15
+ */
16
+ export declare function useFilePicker({ accept, maxSize, multiple, maxFiles, maxTotalSize, keptFiles, disabled, onAccept, onReject, texts, }: {
17
+ accept?: string | string[];
18
+ maxSize?: number | string;
19
+ multiple?: boolean;
20
+ maxFiles?: number;
21
+ maxTotalSize?: number | string;
22
+ keptFiles?: Array<{
23
+ name: string;
24
+ size?: number;
25
+ }>;
26
+ disabled?: boolean;
27
+ onAccept: (picked: any) => void;
28
+ onReject?: (file: File, reason: string) => void;
29
+ texts?: object;
30
+ }): {
31
+ inputProps: object;
32
+ open: () => void;
33
+ pickFiles: (fileList: FileList | File[] | null | undefined) => void;
34
+ isDragging: boolean;
35
+ dropHandlers: object;
36
+ resolvedAccept: string | undefined;
37
+ maxBytes: number | null;
38
+ maxTotalBytes: number | null;
39
+ };
@@ -1,25 +1,58 @@
1
1
  "use client";
2
2
  import { jsxs, Fragment, jsx } from "react/jsx-runtime";
3
- import { useState } from "react";
3
+ import { useState, useRef, useEffect } from "react";
4
+ import Divider from "@mui/material/Divider";
4
5
  import IconButton from "@mui/material/IconButton";
5
6
  import ListItemIcon from "@mui/material/ListItemIcon";
6
7
  import ListItemText from "@mui/material/ListItemText";
8
+ import ListSubheader from "@mui/material/ListSubheader";
7
9
  import Menu from "@mui/material/Menu";
8
10
  import MenuItem from "@mui/material/MenuItem";
9
11
  import Tooltip from "@mui/material/Tooltip";
12
+ import ChevronLeftRoundedIcon from "@mui/icons-material/ChevronLeftRounded";
13
+ import ChevronRightRoundedIcon from "@mui/icons-material/ChevronRightRounded";
10
14
  import MoreHorizRoundedIcon from "@mui/icons-material/MoreHorizRounded";
11
15
  import { piecePart, usePieceProps } from "../../theme/themePieces.js";
16
+ import { useToolkitTranslation } from "../../toolkit/useToolkitTranslation.js";
12
17
  import { warnUnknownProps } from "../../toolkit/unknownProps.js";
18
+ const ACTIONS_MENU_ROW = Object.freeze({ section: "section", divider: "divider" });
13
19
  const THEME_NAME = "CalcitaActionsMenu";
14
20
  const ActionsItem = piecePart(THEME_NAME, "Item", MenuItem);
15
21
  const ActionsItemIcon = piecePart(THEME_NAME, "ItemIcon", ListItemIcon);
22
+ function levelOf(actions, path) {
23
+ const parents = [];
24
+ let items = actions;
25
+ for (const key of path) {
26
+ const parent = items.find((item) => item.key === key);
27
+ if (!parent?.children) {
28
+ break;
29
+ }
30
+ parents.push(parent);
31
+ items = parent.children;
32
+ }
33
+ return { items, parents };
34
+ }
16
35
  function ActionsMenu(props) {
17
- const { label, actions, icon, size = "small", ...unknownProps } = usePieceProps(props, THEME_NAME);
36
+ const { label, actions, icon, size = "small", texts, ...unknownProps } = usePieceProps(props, THEME_NAME);
18
37
  warnUnknownProps(THEME_NAME, unknownProps);
38
+ const { t } = useToolkitTranslation("common", texts);
19
39
  const [anchor, setAnchor] = useState(null);
40
+ const [path, setPath] = useState([]);
41
+ const listRef = useRef(null);
42
+ const { items, parents } = levelOf(actions ?? [], path);
43
+ useEffect(() => {
44
+ if (anchor) {
45
+ listRef.current?.querySelector('[role="menuitem"]:not([aria-disabled="true"])')?.focus();
46
+ }
47
+ }, [path, anchor]);
20
48
  if (!actions?.length) {
21
49
  return null;
22
50
  }
51
+ const close = () => {
52
+ setAnchor(null);
53
+ setPath([]);
54
+ };
55
+ const goBack = () => setPath((current) => current.slice(0, -1));
23
56
  return /* @__PURE__ */ jsxs(Fragment, { children: [
24
57
  /* @__PURE__ */ jsx(Tooltip, { title: label, children: /* @__PURE__ */ jsx(
25
58
  IconButton,
@@ -34,25 +67,75 @@ function ActionsMenu(props) {
34
67
  children: icon ?? /* @__PURE__ */ jsx(MoreHorizRoundedIcon, { fontSize: "small" })
35
68
  }
36
69
  ) }),
37
- /* @__PURE__ */ jsx(Menu, { anchorEl: anchor, open: Boolean(anchor), onClose: () => setAnchor(null), onClick: (event) => event.stopPropagation(), children: actions.map((action) => /* @__PURE__ */ jsxs(
38
- ActionsItem,
70
+ /* @__PURE__ */ jsxs(
71
+ Menu,
39
72
  {
40
- disabled: action.disabled,
41
- onClick: () => {
42
- setAnchor(null);
43
- action.onClick();
44
- },
45
- ownSx: action.color ? { color: `${action.color}.main` } : void 0,
73
+ anchorEl: anchor,
74
+ open: Boolean(anchor),
75
+ onClose: close,
76
+ onClick: (event) => event.stopPropagation(),
77
+ slotProps: { list: { ref: listRef, onKeyDown: (event) => {
78
+ if (event.key === "ArrowLeft" && path.length > 0) {
79
+ event.preventDefault();
80
+ goBack();
81
+ }
82
+ } } },
46
83
  children: [
47
- action.icon && /* @__PURE__ */ jsx(ActionsItemIcon, { ownSx: action.color ? { color: "inherit" } : void 0, children: action.icon }),
48
- /* @__PURE__ */ jsx(ListItemText, { children: action.label })
84
+ parents.length > 0 && [
85
+ /* @__PURE__ */ jsxs(ActionsItem, { onClick: goBack, ownSx: { fontWeight: 600 }, children: [
86
+ /* @__PURE__ */ jsx(ActionsItemIcon, { children: /* @__PURE__ */ jsx(ChevronLeftRoundedIcon, { fontSize: "small" }) }),
87
+ /* @__PURE__ */ jsx(ListItemText, { primary: parents.at(-1).label, secondary: t("actionsMenu.back") })
88
+ ] }, "__back"),
89
+ /* @__PURE__ */ jsx(Divider, {}, "__back-divider")
90
+ ],
91
+ items.map((item) => {
92
+ if (item.type === ACTIONS_MENU_ROW.divider) {
93
+ return /* @__PURE__ */ jsx(Divider, {}, item.key);
94
+ }
95
+ if (item.type === ACTIONS_MENU_ROW.section) {
96
+ return /* @__PURE__ */ jsx(ListSubheader, { sx: { lineHeight: 2.5, bgcolor: "transparent" }, children: item.label }, item.key);
97
+ }
98
+ if (item.render) {
99
+ return /* @__PURE__ */ jsx("li", { children: item.render({ close }) }, item.key);
100
+ }
101
+ const hasChildren = Boolean(item.children?.length);
102
+ return /* @__PURE__ */ jsxs(
103
+ ActionsItem,
104
+ {
105
+ disabled: item.disabled,
106
+ ...item.href && { component: "a", href: item.href, download: item.download },
107
+ "aria-haspopup": hasChildren ? "menu" : void 0,
108
+ onKeyDown: (event) => {
109
+ if (hasChildren && event.key === "ArrowRight") {
110
+ event.preventDefault();
111
+ setPath((current) => [...current, item.key]);
112
+ }
113
+ },
114
+ onClick: () => {
115
+ if (hasChildren) {
116
+ setPath((current) => [...current, item.key]);
117
+ return;
118
+ }
119
+ close();
120
+ item.onClick?.();
121
+ },
122
+ ownSx: item.color ? { color: `${item.color}.main` } : void 0,
123
+ children: [
124
+ item.icon && /* @__PURE__ */ jsx(ActionsItemIcon, { ownSx: item.color ? { color: "inherit" } : void 0, children: item.icon }),
125
+ /* @__PURE__ */ jsx(ListItemText, { primary: item.label, secondary: item.description }),
126
+ hasChildren && /* @__PURE__ */ jsx(ChevronRightRoundedIcon, { fontSize: "small", sx: { ml: 2, color: "text.secondary" } })
127
+ ]
128
+ },
129
+ item.key
130
+ );
131
+ })
49
132
  ]
50
- },
51
- action.key
52
- )) })
133
+ }
134
+ )
53
135
  ] });
54
136
  }
55
137
  export {
138
+ ACTIONS_MENU_ROW,
56
139
  ActionsMenu as default
57
140
  };
58
141
  //# sourceMappingURL=ActionsMenu.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ActionsMenu.js","sources":["../../../src/ui/actions/ActionsMenu.jsx"],"sourcesContent":["// Un botón «⋯» con un menú de acciones: el de cada tarjeta del tablero, el de cada columna, el de cada mensaje de la bandeja.\n// Las acciones las da la pantalla (label ya traducido, ícono, color, apagada); sin acciones no se pinta nada.\n//\n// <ActionsMenu label=\"Acciones de «Junta de cierre»\"\n// actions={[{key: 'delete', label: 'Borrar', icon: <DeleteOutlineIcon fontSize=\"small\" />, color: 'error', onClick: remove}]} />\n//\n// El clic no llega a lo de abajo: no levanta una tarjeta que se arrastra ni abre el renglón donde vive.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaActionsMenu con defaultProps y\n// styleOverrides de sus partes: item, itemIcon.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useState} from 'react';\nimport IconButton from '@mui/material/IconButton';\nimport ListItemIcon from '@mui/material/ListItemIcon';\nimport ListItemText from '@mui/material/ListItemText';\nimport Menu from '@mui/material/Menu';\nimport MenuItem from '@mui/material/MenuItem';\nimport Tooltip from '@mui/material/Tooltip';\nimport MoreHorizRoundedIcon from '@mui/icons-material/MoreHorizRounded';\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.CalcitaActionsMenu)\nconst THEME_NAME = 'CalcitaActionsMenu';\nconst ActionsItem = piecePart(THEME_NAME, 'Item', MenuItem);\nconst ActionsItemIcon = piecePart(THEME_NAME, 'ItemIcon', ListItemIcon);\n\n/**\n * @param {object} props\n * @param {string} props.label el nombre del botón (lector de pantalla y su tooltip), ya traducido\n * @param {Array<{key: string, label: string, icon?: import('react').ReactNode, color?: string, disabled?: boolean, onClick: () => void}>} [props.actions]\n * @param {import('react').ReactNode} [props.icon] otro ícono para el botón (por omisión «⋯»)\n * @param {'small'|'medium'} [props.size]\n */\nexport default function ActionsMenu(props) {\n // lo que el proyecto puso en su tema (components.CalcitaActionsMenu.defaultProps) y encima lo que llega aquí\n const {label, actions, icon, size = 'small', ...unknownProps} = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const [anchor, setAnchor] = useState(null);\n if (!actions?.length) {\n return null;\n }\n return (\n <>\n <Tooltip title={label}>\n <IconButton size={size} aria-label={label} aria-haspopup=\"menu\"\n onClick={(event) => {\n event.stopPropagation();\n setAnchor(event.currentTarget);\n }}>\n {icon ?? <MoreHorizRoundedIcon fontSize=\"small\" />}\n </IconButton>\n </Tooltip>\n <Menu anchorEl={anchor} open={Boolean(anchor)} onClose={() => setAnchor(null)} onClick={(event) => event.stopPropagation()}>\n {actions.map((action) => (\n <ActionsItem key={action.key} disabled={action.disabled}\n onClick={() => {\n setAnchor(null);\n action.onClick();\n }}\n ownSx={action.color ? {color: `${action.color}.main`} : undefined}>\n {action.icon && <ActionsItemIcon ownSx={action.color ? {color: 'inherit'} : undefined}>{action.icon}</ActionsItemIcon>}\n <ListItemText>{action.label}</ListItemText>\n </ActionsItem>\n ))}\n </Menu>\n </>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;;AAwBA,MAAM,aAAa;AACnB,MAAM,cAAc,UAAU,YAAY,QAAQ,QAAQ;AAC1D,MAAM,kBAAkB,UAAU,YAAY,YAAY,YAAY;AAStE,SAAwB,YAAY,OAAO;AAEvC,QAAM,EAAC,OAAO,SAAS,MAAM,OAAO,SAAS,GAAG,aAAA,IAAgB,cAAc,OAAO,UAAU;AAC/F,mBAAiB,YAAY,YAAY;AACzC,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,IAAI;AACzC,MAAI,CAAC,SAAS,QAAQ;AAClB,WAAO;AAAA,EACX;AACA,SACI,qBAAA,UAAA,EACI,UAAA;AAAA,IAAA,oBAAC,SAAA,EAAQ,OAAO,OACZ,UAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QAAW;AAAA,QAAY,cAAY;AAAA,QAAO,iBAAc;AAAA,QAC7C,SAAS,CAAC,UAAU;AAChB,gBAAM,gBAAA;AACN,oBAAU,MAAM,aAAa;AAAA,QACjC;AAAA,QACP,UAAA,QAAQ,oBAAC,sBAAA,EAAqB,UAAS,QAAA,CAAQ;AAAA,MAAA;AAAA,IAAA,GAExD;AAAA,IACA,oBAAC,QAAK,UAAU,QAAQ,MAAM,QAAQ,MAAM,GAAG,SAAS,MAAM,UAAU,IAAI,GAAG,SAAS,CAAC,UAAU,MAAM,mBACpG,UAAA,QAAQ,IAAI,CAAC,WACV;AAAA,MAAC;AAAA,MAAA;AAAA,QAA6B,UAAU,OAAO;AAAA,QACrC,SAAS,MAAM;AACX,oBAAU,IAAI;AACd,iBAAO,QAAA;AAAA,QACX;AAAA,QACA,OAAO,OAAO,QAAQ,EAAC,OAAO,GAAG,OAAO,KAAK,QAAA,IAAW;AAAA,QAC7D,UAAA;AAAA,UAAA,OAAO,QAAQ,oBAAC,iBAAA,EAAgB,OAAO,OAAO,QAAQ,EAAC,OAAO,UAAA,IAAa,QAAY,UAAA,OAAO,MAAK;AAAA,UACpG,oBAAC,cAAA,EAAc,UAAA,OAAO,MAAA,CAAM;AAAA,QAAA;AAAA,MAAA;AAAA,MAPd,OAAO;AAAA,IAAA,CAS5B,EAAA,CACL;AAAA,EAAA,GACJ;AAER;"}
1
+ {"version":3,"file":"ActionsMenu.js","sources":["../../../src/ui/actions/ActionsMenu.jsx"],"sourcesContent":["// Un botón «⋯» con un menú de acciones: el de cada tarjeta del tablero, el de cada columna, el de cada mensaje de la bandeja,\n// el de cada tarjeta de registro. Las acciones las da la pantalla (label ya traducido, ícono, color, apagada); sin acciones\n// no se pinta nada.\n//\n// <ActionsMenu label=\"Acciones de «Junta de cierre»\"\n// actions={[{key: 'delete', label: 'Borrar', icon: <DeleteOutlineIcon fontSize=\"small\" />, color: 'error', onClick: remove}]} />\n//\n// Tan flexible como un menú de sistema (cada renglón es uno de estos):\n// {key, label, icon?, description?, color?, disabled?, onClick} una acción\n// {key, label, href, download?} una liga (bajar un documento, abrir otra página)\n// {key, label, children: [...]} un submenú: se abre DENTRO del mismo menú, con «‹ Regresar»\n// arriba (igual en el teléfono); también con → y ←\n// {type: 'section', key, label} un título de sección\n// {type: 'divider', key} una línea\n// {key, render: ({close}) => …} un renglón propio (abre su ventana y cierra el menú)\n//\n// El clic no llega a lo de abajo: no levanta una tarjeta que se arrastra ni abre el renglón donde vive.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaActionsMenu con defaultProps y\n// styleOverrides de sus partes: item, itemIcon.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useEffect, useRef, useState} from 'react';\nimport Divider from '@mui/material/Divider';\nimport IconButton from '@mui/material/IconButton';\nimport ListItemIcon from '@mui/material/ListItemIcon';\nimport ListItemText from '@mui/material/ListItemText';\nimport ListSubheader from '@mui/material/ListSubheader';\nimport Menu from '@mui/material/Menu';\nimport MenuItem from '@mui/material/MenuItem';\nimport Tooltip from '@mui/material/Tooltip';\nimport ChevronLeftRoundedIcon from '@mui/icons-material/ChevronLeftRounded';\nimport ChevronRightRoundedIcon from '@mui/icons-material/ChevronRightRounded';\nimport MoreHorizRoundedIcon from '@mui/icons-material/MoreHorizRounded';\n\nimport {piecePart, usePieceProps} from '@/theme/themePieces.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\n/** Los renglones que no son acciones. */\nexport const ACTIONS_MENU_ROW = Object.freeze({section: 'section', divider: 'divider'});\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaActionsMenu)\nconst THEME_NAME = 'CalcitaActionsMenu';\nconst ActionsItem = piecePart(THEME_NAME, 'Item', MenuItem);\nconst ActionsItemIcon = piecePart(THEME_NAME, 'ItemIcon', ListItemIcon);\n\n/**\n * Los renglones del nivel abierto y sus títulos, siguiendo las llaves de los submenús abiertos.\n *\n * @param {object[]} actions\n * @param {string[]} path las llaves de los submenús abiertos, del primero al último\n * @returns {{items: object[], parents: object[]}}\n */\nfunction levelOf(actions, path) {\n const parents = [];\n let items = actions;\n for (const key of path) {\n const parent = items.find((item) => item.key === key);\n if (!parent?.children) {\n break;\n }\n parents.push(parent);\n items = parent.children;\n }\n return {items, parents};\n}\n\n/**\n * @param {object} props\n * @param {string} props.label el nombre del botón (lector de pantalla y su tooltip), ya traducido\n * @param {Array<{key: string, type?: string, label?: string, icon?: import('react').ReactNode, description?: string, color?: string, disabled?: boolean, onClick?: () => void, href?: string, download?: string|boolean, children?: object[], render?: (helpers: {close: () => void}) => import('react').ReactNode}>} [props.actions]\n * @param {import('react').ReactNode} [props.icon] otro ícono para el botón (por omisión «⋯»)\n * @param {'small'|'medium'} [props.size]\n * @param {object} [props.texts] los textos que cambian, con la forma de `common` en los textos del paquete\n */\nexport default function ActionsMenu(props) {\n // lo que el proyecto puso en su tema (components.CalcitaActionsMenu.defaultProps) y encima lo que llega aquí\n const {label, actions, icon, size = 'small', texts, ...unknownProps} = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const {t} = useToolkitTranslation('common', texts);\n const [anchor, setAnchor] = useState(null);\n // los submenús abiertos (llaves), del primero al último\n const [path, setPath] = useState([]);\n const listRef = useRef(null);\n const {items, parents} = levelOf(actions ?? [], path);\n\n // al entrar o salir de un submenú, el foco va a su primer renglón (con el teclado se sigue sin el ratón)\n useEffect(() => {\n if (anchor) {\n listRef.current?.querySelector('[role=\"menuitem\"]:not([aria-disabled=\"true\"])')?.focus();\n }\n }, [path, anchor]);\n\n if (!actions?.length) {\n return null;\n }\n const close = () => {\n setAnchor(null);\n setPath([]);\n };\n const goBack = () => setPath((current) => current.slice(0, -1));\n\n return (\n <>\n <Tooltip title={label}>\n <IconButton size={size} aria-label={label} aria-haspopup=\"menu\"\n onClick={(event) => {\n event.stopPropagation();\n setAnchor(event.currentTarget);\n }}>\n {icon ?? <MoreHorizRoundedIcon fontSize=\"small\" />}\n </IconButton>\n </Tooltip>\n <Menu anchorEl={anchor} open={Boolean(anchor)} onClose={close} onClick={(event) => event.stopPropagation()}\n slotProps={{list: {ref: listRef, onKeyDown: (event) => {\n if (event.key === 'ArrowLeft' && path.length > 0) {\n event.preventDefault();\n goBack();\n }\n }}}}>\n {parents.length > 0 && [\n <ActionsItem key=\"__back\" onClick={goBack} ownSx={{fontWeight: 600}}>\n <ActionsItemIcon><ChevronLeftRoundedIcon fontSize=\"small\" /></ActionsItemIcon>\n <ListItemText primary={parents.at(-1).label} secondary={t('actionsMenu.back')} />\n </ActionsItem>,\n <Divider key=\"__back-divider\" />,\n ]}\n {items.map((item) => {\n if (item.type === ACTIONS_MENU_ROW.divider) {\n return <Divider key={item.key} />;\n }\n if (item.type === ACTIONS_MENU_ROW.section) {\n return <ListSubheader key={item.key} sx={{lineHeight: 2.5, bgcolor: 'transparent'}}>{item.label}</ListSubheader>;\n }\n if (item.render) {\n return <li key={item.key}>{item.render({close})}</li>;\n }\n const hasChildren = Boolean(item.children?.length);\n return (\n <ActionsItem\n key={item.key}\n disabled={item.disabled}\n {...(item.href && {component: 'a', href: item.href, download: item.download})}\n aria-haspopup={hasChildren ? 'menu' : undefined}\n onKeyDown={(event) => {\n if (hasChildren && event.key === 'ArrowRight') {\n event.preventDefault();\n setPath((current) => [...current, item.key]);\n }\n }}\n onClick={() => {\n if (hasChildren) {\n setPath((current) => [...current, item.key]);\n return;\n }\n close();\n item.onClick?.();\n }}\n ownSx={item.color ? {color: `${item.color}.main`} : undefined}\n >\n {item.icon && <ActionsItemIcon ownSx={item.color ? {color: 'inherit'} : undefined}>{item.icon}</ActionsItemIcon>}\n <ListItemText primary={item.label} secondary={item.description} />\n {hasChildren && <ChevronRightRoundedIcon fontSize=\"small\" sx={{ml: 2, color: 'text.secondary'}} />}\n </ActionsItem>\n );\n })}\n </Menu>\n </>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -1,28 +1,48 @@
1
1
  "use client";
2
- import { jsx } from "react/jsx-runtime";
3
- import { useState } from "react";
4
- import { useToolkitTranslation } from "../../toolkit/useToolkitTranslation.js";
2
+ import { jsxs, Fragment, jsx } from "react/jsx-runtime";
3
+ import { useState, useRef, useEffect } from "react";
4
+ import Box from "@mui/material/Box";
5
5
  import IconButton from "@mui/material/IconButton";
6
6
  import Tooltip from "@mui/material/Tooltip";
7
7
  import CheckIcon from "@mui/icons-material/CheckRounded";
8
8
  import CopyIcon from "@mui/icons-material/ContentCopyOutlined";
9
+ import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline";
10
+ import { useToolkitTranslation } from "../../toolkit/useToolkitTranslation.js";
9
11
  import { warnUnknownProps } from "../../toolkit/unknownProps.js";
10
- const COPIED_FEEDBACK_MILLISECONDS = 2e3;
11
- function CopyButton({ value, label, size = "small", onCopied, texts, ...unknownProps }) {
12
+ const FEEDBACK_MILLISECONDS = 2e3;
13
+ const COPY_STATE = Object.freeze({ idle: "idle", copied: "copied", failed: "failed" });
14
+ const ICON_BY_STATE = Object.freeze({ [COPY_STATE.idle]: CopyIcon, [COPY_STATE.copied]: CheckIcon, [COPY_STATE.failed]: ErrorOutlineIcon });
15
+ const COLOR_BY_STATE = Object.freeze({ [COPY_STATE.idle]: void 0, [COPY_STATE.copied]: "success.dark", [COPY_STATE.failed]: "error.dark" });
16
+ const VISUALLY_HIDDEN_SX = { position: "absolute", width: "1px", height: "1px", overflow: "hidden", clip: "rect(0 0 0 0)", whiteSpace: "nowrap" };
17
+ function CopyButton({ value, label, size = "small", onCopied, onCopyFailed, texts, ...unknownProps }) {
12
18
  warnUnknownProps("CopyButton", unknownProps);
13
19
  const { t } = useToolkitTranslation("common", texts);
14
- const [isCopied, setIsCopied] = useState(false);
20
+ const [copyState, setCopyState] = useState(COPY_STATE.idle);
21
+ const resetTimer = useRef(null);
15
22
  const buttonLabel = label ?? t("form.copy");
23
+ useEffect(() => () => clearTimeout(resetTimer.current), []);
16
24
  const copy = async () => {
25
+ clearTimeout(resetTimer.current);
17
26
  try {
18
- await navigator.clipboard.writeText(value);
19
- setIsCopied(true);
27
+ await navigator.clipboard.writeText(typeof value === "function" ? value() : value);
28
+ setCopyState(COPY_STATE.copied);
20
29
  onCopied?.();
21
- setTimeout(() => setIsCopied(false), COPIED_FEEDBACK_MILLISECONDS);
22
- } catch {
30
+ } catch (error) {
31
+ setCopyState(COPY_STATE.failed);
32
+ onCopyFailed?.(error);
23
33
  }
34
+ resetTimer.current = setTimeout(() => setCopyState(COPY_STATE.idle), FEEDBACK_MILLISECONDS);
24
35
  };
25
- return /* @__PURE__ */ jsx(Tooltip, { title: isCopied ? t("form.copied") : buttonLabel, children: /* @__PURE__ */ jsx(IconButton, { size, "aria-label": buttonLabel, onClick: copy, color: isCopied ? "success" : "default", children: isCopied ? /* @__PURE__ */ jsx(CheckIcon, { fontSize: "inherit" }) : /* @__PURE__ */ jsx(CopyIcon, { fontSize: "inherit" }) }) });
36
+ const feedbackText = {
37
+ [COPY_STATE.idle]: buttonLabel,
38
+ [COPY_STATE.copied]: t("form.copied"),
39
+ [COPY_STATE.failed]: t("form.copyFailed")
40
+ }[copyState];
41
+ const StateIcon = ICON_BY_STATE[copyState];
42
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
43
+ /* @__PURE__ */ jsx(Tooltip, { title: feedbackText, open: copyState === COPY_STATE.idle ? void 0 : true, placement: "top", children: /* @__PURE__ */ jsx(IconButton, { size, "aria-label": buttonLabel, onClick: copy, sx: { color: COLOR_BY_STATE[copyState] }, children: /* @__PURE__ */ jsx(StateIcon, { fontSize: "inherit" }) }) }),
44
+ /* @__PURE__ */ jsx(Box, { component: "span", role: "status", "aria-live": "polite", sx: VISUALLY_HIDDEN_SX, children: copyState === COPY_STATE.idle ? "" : feedbackText })
45
+ ] });
26
46
  }
27
47
  export {
28
48
  CopyButton as default
@@ -1 +1 @@
1
- {"version":3,"file":"CopyButton.js","sources":["../../../src/ui/actions/CopyButton.jsx"],"sourcesContent":["// Botón para copiar un valor al portapapeles, con la palomita de «copiado» un par de segundos.\n//\n// Sin permiso de portapapeles (una página en http, dentro de un iframe) no truena: el valor sigue a la vista para copiarlo\n// a mano.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useState} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport IconButton from '@mui/material/IconButton';\nimport Tooltip from '@mui/material/Tooltip';\nimport CheckIcon from '@mui/icons-material/CheckRounded';\nimport CopyIcon from '@mui/icons-material/ContentCopyOutlined';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\n// cuánto se queda la palomita de «copiado»\nconst COPIED_FEEDBACK_MILLISECONDS = 2000;\n\n/**\n * @param {object} props\n * @param {string} props.value lo que se copia\n * @param {string} [props.label] ya traducido; por omisión «Copiar»\n * @param {'small'|'medium'} [props.size]\n * @param {() => void} [props.onCopied]\n * @param {object} [props.texts] los textos que cambian, con la forma de `common` en los textos del paquete\n */\nexport default function CopyButton({value, label, size = 'small', onCopied, texts, ...unknownProps}) {\n warnUnknownProps('CopyButton', unknownProps);\n const {t} = useToolkitTranslation('common', texts);\n const [isCopied, setIsCopied] = useState(false);\n const buttonLabel = label ?? t('form.copy');\n\n const copy = async () => {\n try {\n await navigator.clipboard.writeText(value);\n setIsCopied(true);\n onCopied?.();\n setTimeout(() => setIsCopied(false), COPIED_FEEDBACK_MILLISECONDS);\n } catch {\n // sin permiso de portapapeles: el valor está a la vista para copiarlo a mano\n }\n };\n\n return (\n <Tooltip title={isCopied ? t('form.copied') : buttonLabel}>\n <IconButton size={size} aria-label={buttonLabel} onClick={copy} color={isCopied ? 'success' : 'default'}>\n {isCopied ? <CheckIcon fontSize=\"inherit\" /> : <CopyIcon fontSize=\"inherit\" />}\n </IconButton>\n </Tooltip>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;AAeA,MAAM,+BAA+B;AAUrC,SAAwB,WAAW,EAAC,OAAO,OAAO,OAAO,SAAS,UAAU,OAAO,GAAG,gBAAe;AACjG,mBAAiB,cAAc,YAAY;AAC3C,QAAM,EAAC,EAAA,IAAK,sBAAsB,UAAU,KAAK;AACjD,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,KAAK;AAC9C,QAAM,cAAc,SAAS,EAAE,WAAW;AAE1C,QAAM,OAAO,YAAY;AACrB,QAAI;AACA,YAAM,UAAU,UAAU,UAAU,KAAK;AACzC,kBAAY,IAAI;AAChB,iBAAA;AACA,iBAAW,MAAM,YAAY,KAAK,GAAG,4BAA4B;AAAA,IACrE,QAAQ;AAAA,IAER;AAAA,EACJ;AAEA,SACI,oBAAC,SAAA,EAAQ,OAAO,WAAW,EAAE,aAAa,IAAI,aAC1C,UAAA,oBAAC,YAAA,EAAW,MAAY,cAAY,aAAa,SAAS,MAAM,OAAO,WAAW,YAAY,WACzF,UAAA,WAAW,oBAAC,WAAA,EAAU,UAAS,UAAA,CAAU,IAAK,oBAAC,UAAA,EAAS,UAAS,UAAA,CAAU,GAChF,GACJ;AAER;"}
1
+ {"version":3,"file":"CopyButton.js","sources":["../../../src/ui/actions/CopyButton.jsx"],"sourcesContent":["// Botón para copiar al portapapeles: un ícono que pone el texto y lo confirma ahí mismo («Copiado» un par de segundos, con la\n// palomita). Va donde copiar a mano cuesta trabajo: un folio, un texto largo que en pantalla va cortado, un renglón entero\n// (con tableRowTextOf: separado por tabuladores, al pegarlo en Excel cada dato cae en su celda).\n//\n// Si el navegador no deja copiar (una página en http, dentro de un iframe, sin permiso), el botón lo dice en rojo en lugar\n// de fingir que copió, y avisa con onCopyFailed. La confirmación también se anuncia a los lectores de pantalla (el globo no\n// siempre se anuncia).\n//\n// value puede ser una función: se llama al picar (arma el renglón solo si de verdad se copia).\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useEffect, useRef, useState} from 'react';\nimport Box from '@mui/material/Box';\nimport IconButton from '@mui/material/IconButton';\nimport Tooltip from '@mui/material/Tooltip';\nimport CheckIcon from '@mui/icons-material/CheckRounded';\nimport CopyIcon from '@mui/icons-material/ContentCopyOutlined';\nimport ErrorOutlineIcon from '@mui/icons-material/ErrorOutline';\n\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\n// cuánto se queda la confirmación (copiado o no se pudo) antes de regresar al ícono de copiar\nconst FEEDBACK_MILLISECONDS = 2000;\n// en qué va el botón: listo, copiado o no se pudo\nconst COPY_STATE = Object.freeze({idle: 'idle', copied: 'copied', failed: 'failed'});\nconst ICON_BY_STATE = Object.freeze({[COPY_STATE.idle]: CopyIcon, [COPY_STATE.copied]: CheckIcon, [COPY_STATE.failed]: ErrorOutlineIcon});\nconst COLOR_BY_STATE = Object.freeze({[COPY_STATE.idle]: undefined, [COPY_STATE.copied]: 'success.dark', [COPY_STATE.failed]: 'error.dark'});\n// el aviso para lectores de pantalla, escondido a la vista. OJO: '1px' en texto; en MUI un width: 1 es 100% y el texto\n// escondido ensanchaba la página\nconst VISUALLY_HIDDEN_SX = {position: 'absolute', width: '1px', height: '1px', overflow: 'hidden', clip: 'rect(0 0 0 0)', whiteSpace: 'nowrap'};\n\n/**\n * @param {object} props\n * @param {string|(() => string)} props.value lo que se copia; una función se llama al picar\n * @param {string} [props.label] ya traducido: qué copia («Copiar el folio»); por omisión «Copiar». Es el globo y el nombre\n * del botón para un lector de pantalla\n * @param {'small'|'medium'} [props.size]\n * @param {() => void} [props.onCopied]\n * @param {(error: unknown) => void} [props.onCopyFailed] el navegador no dejó copiar (el botón ya lo dice en rojo)\n * @param {object} [props.texts] los textos que cambian, con la forma de `common` en los textos del paquete\n */\nexport default function CopyButton({value, label, size = 'small', onCopied, onCopyFailed, texts, ...unknownProps}) {\n warnUnknownProps('CopyButton', unknownProps);\n const {t} = useToolkitTranslation('common', texts);\n const [copyState, setCopyState] = useState(COPY_STATE.idle);\n const resetTimer = useRef(null);\n const buttonLabel = label ?? t('form.copy');\n\n // al desmontar no se queda un temporizador vivo\n useEffect(() => () => clearTimeout(resetTimer.current), []);\n\n const copy = async () => {\n clearTimeout(resetTimer.current);\n try {\n await navigator.clipboard.writeText(typeof value === 'function' ? value() : value);\n setCopyState(COPY_STATE.copied);\n onCopied?.();\n } catch (error) {\n setCopyState(COPY_STATE.failed);\n onCopyFailed?.(error);\n }\n resetTimer.current = setTimeout(() => setCopyState(COPY_STATE.idle), FEEDBACK_MILLISECONDS);\n };\n\n const feedbackText = {\n [COPY_STATE.idle]: buttonLabel,\n [COPY_STATE.copied]: t('form.copied'),\n [COPY_STATE.failed]: t('form.copyFailed'),\n }[copyState];\n const StateIcon = ICON_BY_STATE[copyState];\n\n return (\n <>\n {/* la confirmación se queda abierta mientras dura, aunque el cursor ya no esté encima; arriba y no a un lado, para no\n tapar el dato de junto */}\n <Tooltip title={feedbackText} open={copyState === COPY_STATE.idle ? undefined : true} placement=\"top\">\n <IconButton size={size} aria-label={buttonLabel} onClick={copy} sx={{color: COLOR_BY_STATE[copyState]}}>\n <StateIcon fontSize=\"inherit\" />\n </IconButton>\n </Tooltip>\n <Box component=\"span\" role=\"status\" aria-live=\"polite\" sx={VISUALLY_HIDDEN_SX}>\n {copyState === COPY_STATE.idle ? '' : feedbackText}\n </Box>\n </>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;AAuBA,MAAM,wBAAwB;AAE9B,MAAM,aAAa,OAAO,OAAO,EAAC,MAAM,QAAQ,QAAQ,UAAU,QAAQ,UAAS;AACnF,MAAM,gBAAgB,OAAO,OAAO,EAAC,CAAC,WAAW,IAAI,GAAG,UAAU,CAAC,WAAW,MAAM,GAAG,WAAW,CAAC,WAAW,MAAM,GAAG,kBAAiB;AACxI,MAAM,iBAAiB,OAAO,OAAO,EAAC,CAAC,WAAW,IAAI,GAAG,QAAW,CAAC,WAAW,MAAM,GAAG,gBAAgB,CAAC,WAAW,MAAM,GAAG,cAAa;AAG3I,MAAM,qBAAqB,EAAC,UAAU,YAAY,OAAO,OAAO,QAAQ,OAAO,UAAU,UAAU,MAAM,iBAAiB,YAAY,SAAA;AAYtI,SAAwB,WAAW,EAAC,OAAO,OAAO,OAAO,SAAS,UAAU,cAAc,OAAO,GAAG,gBAAe;AAC/G,mBAAiB,cAAc,YAAY;AAC3C,QAAM,EAAC,EAAA,IAAK,sBAAsB,UAAU,KAAK;AACjD,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,WAAW,IAAI;AAC1D,QAAM,aAAa,OAAO,IAAI;AAC9B,QAAM,cAAc,SAAS,EAAE,WAAW;AAG1C,YAAU,MAAM,MAAM,aAAa,WAAW,OAAO,GAAG,CAAA,CAAE;AAE1D,QAAM,OAAO,YAAY;AACrB,iBAAa,WAAW,OAAO;AAC/B,QAAI;AACA,YAAM,UAAU,UAAU,UAAU,OAAO,UAAU,aAAa,MAAA,IAAU,KAAK;AACjF,mBAAa,WAAW,MAAM;AAC9B,iBAAA;AAAA,IACJ,SAAS,OAAO;AACZ,mBAAa,WAAW,MAAM;AAC9B,qBAAe,KAAK;AAAA,IACxB;AACA,eAAW,UAAU,WAAW,MAAM,aAAa,WAAW,IAAI,GAAG,qBAAqB;AAAA,EAC9F;AAEA,QAAM,eAAe;AAAA,IACjB,CAAC,WAAW,IAAI,GAAG;AAAA,IACnB,CAAC,WAAW,MAAM,GAAG,EAAE,aAAa;AAAA,IACpC,CAAC,WAAW,MAAM,GAAG,EAAE,iBAAiB;AAAA,EAAA,EAC1C,SAAS;AACX,QAAM,YAAY,cAAc,SAAS;AAEzC,SACI,qBAAA,UAAA,EAGI,UAAA;AAAA,IAAA,oBAAC,SAAA,EAAQ,OAAO,cAAc,MAAM,cAAc,WAAW,OAAO,SAAY,MAAM,WAAU,OAC5F,UAAA,oBAAC,cAAW,MAAY,cAAY,aAAa,SAAS,MAAM,IAAI,EAAC,OAAO,eAAe,SAAS,EAAA,GAChG,UAAA,oBAAC,WAAA,EAAU,UAAS,UAAA,CAAU,GAClC,GACJ;AAAA,IACA,oBAAC,KAAA,EAAI,WAAU,QAAO,MAAK,UAAS,aAAU,UAAS,IAAI,oBACtD,UAAA,cAAc,WAAW,OAAO,KAAK,aAAA,CAC1C;AAAA,EAAA,GACJ;AAER;"}
@@ -0,0 +1,7 @@
1
+ function tableRowTextOf(cells) {
2
+ return cells.map((cell) => (cell === null || cell === void 0 ? "" : String(cell)).replace(/[\t\r\n]+/g, " ")).join(" ");
3
+ }
4
+ export {
5
+ tableRowTextOf
6
+ };
7
+ //# sourceMappingURL=tableRowText.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tableRowText.js","sources":["../../../src/ui/actions/tableRowText.js"],"sourcesContent":["// Un renglón de tabla como texto para el portapapeles: sus celdas separadas por tabulador, como copian Excel y las tablas de\n// datos. Al pegarlo en una hoja cada dato cae en su celda; en un correo se lee como una línea. Va con CopyButton:\n//\n// <CopyButton label=\"Copiar el pedido\" value={() => tableRowTextOf([order.folio, order.customer, formatCurrency(order.total)])} />\n//\n// Puro (sin React): se prueba con node (tableRowText.test.js).\n//\n// Genérico del paquete: function (regla de la casa).\n\n/**\n * @param {Array<string|number|null|undefined>} cells las celdas en el orden de las columnas, ya formateadas\n * @returns {string} las celdas unidas con tabulador; una vacía o nula queda vacía. Un tabulador o un salto de renglón dentro\n * de una celda se cambia por un espacio: partirían el renglón al pegarlo\n */\nexport function tableRowTextOf(cells) {\n return cells\n .map((cell) => (cell === null || cell === undefined ? '' : String(cell)).replace(/[\\t\\r\\n]+/g, ' '))\n .join('\\t');\n}\n"],"names":[],"mappings":"AAcO,SAAS,eAAe,OAAO;AAClC,SAAO,MACF,IAAI,CAAC,UAAU,SAAS,QAAQ,SAAS,SAAY,KAAK,OAAO,IAAI,GAAG,QAAQ,cAAc,GAAG,CAAC,EAClG,KAAK,GAAI;AAClB;"}