@stackcraftmx/calcita 0.47.0 → 0.52.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (501) hide show
  1. package/README.md +192 -13
  2. package/cli/calcita.js +17 -0
  3. package/cli/checkTexts.js +266 -0
  4. package/cli/packageVersions.js +6 -1
  5. package/cli/projectFiles.js +4 -2
  6. package/cli/templates/project/README.md.tpl +21 -1
  7. package/cli/templates/project/eslint.config.js.tpl +27 -0
  8. package/cli/templates/project/src/app/appTexts.js.tpl +2 -1
  9. package/cli/templates/project/src/core/publicPaths.js.tpl +10 -2
  10. package/cli/templates/project/vite.config.js.tpl +8 -1
  11. package/dist/DataTable/AuditTrailDialog.js +3 -2
  12. package/dist/DataTable/AuditTrailDialog.js.map +1 -1
  13. package/dist/DataTable/ColumnFilters/HeaderFilterPopover.js +3 -2
  14. package/dist/DataTable/ColumnFilters/HeaderFilterPopover.js.map +1 -1
  15. package/dist/DataTable/ColumnFilters/MultiSelectColumnFilter.js +3 -2
  16. package/dist/DataTable/ColumnFilters/MultiSelectColumnFilter.js.map +1 -1
  17. package/dist/DataTable/ColumnFilters/SortableFilterHeader.js +3 -2
  18. package/dist/DataTable/ColumnFilters/SortableFilterHeader.js.map +1 -1
  19. package/dist/DataTable/EmptyState.js +3 -2
  20. package/dist/DataTable/EmptyState.js.map +1 -1
  21. package/dist/DataTable/FilterChips.js +3 -2
  22. package/dist/DataTable/FilterChips.js.map +1 -1
  23. package/dist/DataTable/GoToPage.js +3 -2
  24. package/dist/DataTable/GoToPage.js.map +1 -1
  25. package/dist/DataTable/ServerTable.js +95 -28
  26. package/dist/DataTable/ServerTable.js.map +1 -1
  27. package/dist/DataTable/ServerTableFilterDrawer.js +8 -3
  28. package/dist/DataTable/ServerTableFilterDrawer.js.map +1 -1
  29. package/dist/DataTable/StackedCell.js +60 -0
  30. package/dist/DataTable/StackedCell.js.map +1 -0
  31. package/dist/DataTable/TableCardsView.js +88 -0
  32. package/dist/DataTable/TableCardsView.js.map +1 -0
  33. package/dist/DataTable/columnWidths.js +30 -0
  34. package/dist/DataTable/columnWidths.js.map +1 -0
  35. package/dist/DataTable/columns.js +47 -1
  36. package/dist/DataTable/columns.js.map +1 -1
  37. package/dist/DataTable/fixedColumns.js +24 -0
  38. package/dist/DataTable/fixedColumns.js.map +1 -0
  39. package/dist/DataTable/useTableView.js +29 -0
  40. package/dist/DataTable/useTableView.js.map +1 -0
  41. package/dist/access/PermissionMatrix.js +3 -2
  42. package/dist/access/PermissionMatrix.js.map +1 -1
  43. package/dist/account/AccountProvider.js +1 -1
  44. package/dist/account/AccountSecuritySection.js +3 -2
  45. package/dist/account/AccountSecuritySection.js.map +1 -1
  46. package/dist/account/DemoTotpCodeButton.js +3 -2
  47. package/dist/account/DemoTotpCodeButton.js.map +1 -1
  48. package/dist/account/ProfileScreen.js +3 -2
  49. package/dist/account/ProfileScreen.js.map +1 -1
  50. package/dist/account/SignInScreen.js +3 -2
  51. package/dist/account/SignInScreen.js.map +1 -1
  52. package/dist/appShell/AccountMenu.js +3 -2
  53. package/dist/appShell/AccountMenu.js.map +1 -1
  54. package/dist/appShell/AppLayout.js +3 -2
  55. package/dist/appShell/AppLayout.js.map +1 -1
  56. package/dist/appShell/AppearanceSettings.js +3 -2
  57. package/dist/appShell/AppearanceSettings.js.map +1 -1
  58. package/dist/appShell/AuthLayout.js +3 -2
  59. package/dist/appShell/AuthLayout.js.map +1 -1
  60. package/dist/appShell/BarActionButton.js +11 -4
  61. package/dist/appShell/BarActionButton.js.map +1 -1
  62. package/dist/appShell/BarActions.js +3 -2
  63. package/dist/appShell/BarActions.js.map +1 -1
  64. package/dist/appShell/BarCapsule.js +3 -2
  65. package/dist/appShell/BarCapsule.js.map +1 -1
  66. package/dist/appShell/BarSearch.js +3 -2
  67. package/dist/appShell/BarSearch.js.map +1 -1
  68. package/dist/appShell/HeaderTools.js +3 -2
  69. package/dist/appShell/HeaderTools.js.map +1 -1
  70. package/dist/appShell/MobileTabBar.js +3 -2
  71. package/dist/appShell/MobileTabBar.js.map +1 -1
  72. package/dist/appShell/SettingsMenu.js +3 -2
  73. package/dist/appShell/SettingsMenu.js.map +1 -1
  74. package/dist/appShell/SideMenu.js +3 -2
  75. package/dist/appShell/SideMenu.js.map +1 -1
  76. package/dist/appShell/SideMenuEntry.js +3 -2
  77. package/dist/appShell/SideMenuEntry.js.map +1 -1
  78. package/dist/appShell/SubmenuActionsMenu.js +3 -2
  79. package/dist/appShell/SubmenuActionsMenu.js.map +1 -1
  80. package/dist/appShell/TabBarEditor.js +3 -2
  81. package/dist/appShell/TabBarEditor.js.map +1 -1
  82. package/dist/appShell/TopMenu.js +3 -2
  83. package/dist/appShell/TopMenu.js.map +1 -1
  84. package/dist/appShell/Wordmark.js +3 -2
  85. package/dist/appShell/Wordmark.js.map +1 -1
  86. package/dist/auth/PasswordChecklist.js +3 -2
  87. package/dist/auth/PasswordChecklist.js.map +1 -1
  88. package/dist/calendar/EventCalendar.js +3 -2
  89. package/dist/calendar/EventCalendar.js.map +1 -1
  90. package/dist/calendar/EventDetails.js +3 -2
  91. package/dist/calendar/EventDetails.js.map +1 -1
  92. package/dist/datePickers/DateInput.js +28 -22
  93. package/dist/datePickers/DateInput.js.map +1 -1
  94. package/dist/datePickers/DateTimeInput.js +33 -27
  95. package/dist/datePickers/DateTimeInput.js.map +1 -1
  96. package/dist/datePickers/MonthInput.js +27 -21
  97. package/dist/datePickers/MonthInput.js.map +1 -1
  98. package/dist/datePickers/TimeInput.js +26 -20
  99. package/dist/datePickers/TimeInput.js.map +1 -1
  100. package/dist/datePickers/usePickerField.js +5 -4
  101. package/dist/datePickers/usePickerField.js.map +1 -1
  102. package/dist/downloads/DownloadManager.js +3 -2
  103. package/dist/downloads/DownloadManager.js.map +1 -1
  104. package/dist/feedback/ConfirmDialog.js +3 -2
  105. package/dist/feedback/ConfirmDialog.js.map +1 -1
  106. package/dist/feedback/ModalWaiting.js +3 -2
  107. package/dist/feedback/ModalWaiting.js.map +1 -1
  108. package/dist/feedback/NewBuildNotice.js +3 -2
  109. package/dist/feedback/NewBuildNotice.js.map +1 -1
  110. package/dist/filepond/FileDropzone.js +3 -2
  111. package/dist/filepond/FileDropzone.js.map +1 -1
  112. package/dist/forms/FormProblemAlert.js +3 -2
  113. package/dist/forms/FormProblemAlert.js.map +1 -1
  114. package/dist/forms/FormWizard.js +21 -6
  115. package/dist/forms/FormWizard.js.map +1 -1
  116. package/dist/forms/SubmitButton.js +7 -2
  117. package/dist/forms/SubmitButton.js.map +1 -1
  118. package/dist/forms/auth/SignInForm.js +3 -2
  119. package/dist/forms/auth/SignInForm.js.map +1 -1
  120. package/dist/forms/fieldRules.js +30 -0
  121. package/dist/forms/fieldRules.js.map +1 -0
  122. package/dist/forms/security/SecondStepForm.js +3 -2
  123. package/dist/forms/security/SecondStepForm.js.map +1 -1
  124. package/dist/forms/security/TwoStepVerificationCard.js +3 -2
  125. package/dist/forms/security/TwoStepVerificationCard.js.map +1 -1
  126. package/dist/forms/useValidationText.js +18 -0
  127. package/dist/forms/useValidationText.js.map +1 -0
  128. package/dist/forms/validation.js +35 -31
  129. package/dist/forms/validation.js.map +1 -1
  130. package/dist/forms/validationMessages.js +64 -7
  131. package/dist/forms/validationMessages.js.map +1 -1
  132. package/dist/forms.js +3 -0
  133. package/dist/forms.js.map +1 -1
  134. package/dist/hookForm/FormProblemAlert.js +3 -2
  135. package/dist/hookForm/FormProblemAlert.js.map +1 -1
  136. package/dist/hookForm/FormWizard.js +19 -5
  137. package/dist/hookForm/FormWizard.js.map +1 -1
  138. package/dist/hookForm/SubmitButton.js +7 -2
  139. package/dist/hookForm/SubmitButton.js.map +1 -1
  140. package/dist/inbox/InboxLayout.js +3 -2
  141. package/dist/inbox/InboxLayout.js.map +1 -1
  142. package/dist/inbox/MessageList.js +3 -2
  143. package/dist/inbox/MessageList.js.map +1 -1
  144. package/dist/inbox/MessageView.js +3 -2
  145. package/dist/inbox/MessageView.js.map +1 -1
  146. package/dist/index.js +193 -163
  147. package/dist/index.js.map +1 -1
  148. package/dist/inputs/CatalogInput.js +32 -25
  149. package/dist/inputs/CatalogInput.js.map +1 -1
  150. package/dist/inputs/CheckboxGroupInput.js +8 -5
  151. package/dist/inputs/CheckboxGroupInput.js.map +1 -1
  152. package/dist/inputs/ColorInput.js +7 -4
  153. package/dist/inputs/ColorInput.js.map +1 -1
  154. package/dist/inputs/FieldFrame.js +10 -4
  155. package/dist/inputs/FieldFrame.js.map +1 -1
  156. package/dist/inputs/FormSection.js +1 -1
  157. package/dist/inputs/MaskedInput.js +1 -0
  158. package/dist/inputs/MaskedInput.js.map +1 -1
  159. package/dist/inputs/NumberInput.js +29 -24
  160. package/dist/inputs/NumberInput.js.map +1 -1
  161. package/dist/inputs/RepeatingFields.js +8 -5
  162. package/dist/inputs/RepeatingFields.js.map +1 -1
  163. package/dist/inputs/SelectInput.js +8 -5
  164. package/dist/inputs/SelectInput.js.map +1 -1
  165. package/dist/inputs/TextInput.js +5 -3
  166. package/dist/inputs/TextInput.js.map +1 -1
  167. package/dist/inputs/TransferListInput.js +8 -5
  168. package/dist/inputs/TransferListInput.js.map +1 -1
  169. package/dist/inputs/WizardLayout.js +48 -19
  170. package/dist/inputs/WizardLayout.js.map +1 -1
  171. package/dist/inputs/useWizardSteps.js +36 -0
  172. package/dist/inputs/useWizardSteps.js.map +1 -0
  173. package/dist/inputs/wizardSteps.js +32 -0
  174. package/dist/inputs/wizardSteps.js.map +1 -0
  175. package/dist/kanban/KanbanBoard.js +3 -2
  176. package/dist/kanban/KanbanBoard.js.map +1 -1
  177. package/dist/legal/CookieNotice.js +3 -2
  178. package/dist/legal/CookieNotice.js.map +1 -1
  179. package/dist/legal/TermsAcceptanceDialog.js +3 -2
  180. package/dist/legal/TermsAcceptanceDialog.js.map +1 -1
  181. package/dist/locales/en.js +24 -0
  182. package/dist/locales/en.js.map +1 -1
  183. package/dist/locales/es.js +24 -0
  184. package/dist/locales/es.js.map +1 -1
  185. package/dist/maps/MapView.js +3 -2
  186. package/dist/maps/MapView.js.map +1 -1
  187. package/dist/mexico/CfdiSummary.js +3 -2
  188. package/dist/mexico/CfdiSummary.js.map +1 -1
  189. package/dist/mexico/EfirmaFields.js +3 -2
  190. package/dist/mexico/EfirmaFields.js.map +1 -1
  191. package/dist/mexico/mexicoIdentifiers.js +5 -0
  192. package/dist/mexico/mexicoIdentifiers.js.map +1 -1
  193. package/dist/mexico/mexicoRules.js +9 -4
  194. package/dist/mexico/mexicoRules.js.map +1 -1
  195. package/dist/mexico.js +4 -2
  196. package/dist/pwa/InstallAppSetting.js +3 -2
  197. package/dist/pwa/InstallAppSetting.js.map +1 -1
  198. package/dist/pwa/InstallInstructionsDialog.js +3 -2
  199. package/dist/pwa/InstallInstructionsDialog.js.map +1 -1
  200. package/dist/pwa/InstallPromptBanner.js +3 -2
  201. package/dist/pwa/InstallPromptBanner.js.map +1 -1
  202. package/dist/react-hook-form.js +3 -0
  203. package/dist/react-hook-form.js.map +1 -1
  204. package/dist/richText/RichTextEditor.js +7 -4
  205. package/dist/richText/RichTextEditor.js.map +1 -1
  206. package/dist/richText/RichTextView.js +3 -2
  207. package/dist/richText/RichTextView.js.map +1 -1
  208. package/dist/security/ActivityLog.js +3 -2
  209. package/dist/security/ActivityLog.js.map +1 -1
  210. package/dist/security/RecoveryCodesDialog.js +3 -2
  211. package/dist/security/RecoveryCodesDialog.js.map +1 -1
  212. package/dist/security/SessionsCard.js +3 -2
  213. package/dist/security/SessionsCard.js.map +1 -1
  214. package/dist/table.js +21 -2
  215. package/dist/table.js.map +1 -1
  216. package/dist/theme/themePieces.js +70 -4
  217. package/dist/theme/themePieces.js.map +1 -1
  218. package/dist/toolkit/ToolkitProvider.js +8 -1
  219. package/dist/toolkit/ToolkitProvider.js.map +1 -1
  220. package/dist/toolkit/browserNavigation.js +19 -4
  221. package/dist/toolkit/browserNavigation.js.map +1 -1
  222. package/dist/toolkit/combineTexts.js +32 -0
  223. package/dist/toolkit/combineTexts.js.map +1 -0
  224. package/dist/toolkit/hashNavigation.js +17 -3
  225. package/dist/toolkit/hashNavigation.js.map +1 -1
  226. package/dist/toolkit/navigationPaths.js +15 -0
  227. package/dist/toolkit/navigationPaths.js.map +1 -1
  228. package/dist/toolkit/pathIndex.js +66 -0
  229. package/dist/toolkit/pathIndex.js.map +1 -0
  230. package/dist/toolkit/toolkitSettings.js +19 -1
  231. package/dist/toolkit/toolkitSettings.js.map +1 -1
  232. package/dist/types/DataTable/AuditTrailDialog.d.ts +6 -17
  233. package/dist/types/DataTable/ColumnFilters/HeaderFilterPopover.d.ts +6 -5
  234. package/dist/types/DataTable/ColumnFilters/MultiSelectColumnFilter.d.ts +6 -9
  235. package/dist/types/DataTable/ColumnFilters/SortableFilterHeader.d.ts +6 -14
  236. package/dist/types/DataTable/EmptyState.d.ts +6 -9
  237. package/dist/types/DataTable/FilterChips.d.ts +6 -11
  238. package/dist/types/DataTable/GoToPage.d.ts +6 -9
  239. package/dist/types/DataTable/ServerTable.d.ts +16 -80
  240. package/dist/types/DataTable/ServerTableFilterDrawer.d.ts +6 -13
  241. package/dist/types/DataTable/StackedCell.d.ts +12 -0
  242. package/dist/types/DataTable/TableCardsView.d.ts +27 -0
  243. package/dist/types/DataTable/columnWidths.d.ts +21 -0
  244. package/dist/types/DataTable/columns.d.ts +111 -0
  245. package/dist/types/DataTable/fixedColumns.d.ts +29 -0
  246. package/dist/types/DataTable/index.d.ts +6 -1
  247. package/dist/types/DataTable/useTableView.d.ts +22 -0
  248. package/dist/types/access/PermissionMatrix.d.ts +6 -10
  249. package/dist/types/account/AccountSecuritySection.d.ts +6 -7
  250. package/dist/types/account/DemoTotpCodeButton.d.ts +6 -5
  251. package/dist/types/account/ProfileScreen.d.ts +6 -18
  252. package/dist/types/account/SignInScreen.d.ts +6 -13
  253. package/dist/types/appShell/AccountMenu.d.ts +6 -11
  254. package/dist/types/appShell/AppLayout.d.ts +3 -40
  255. package/dist/types/appShell/AppearanceSettings.d.ts +6 -12
  256. package/dist/types/appShell/AuthLayout.d.ts +3 -11
  257. package/dist/types/appShell/BarActionButton.d.ts +7 -10
  258. package/dist/types/appShell/BarActions.d.ts +6 -15
  259. package/dist/types/appShell/BarCapsule.d.ts +6 -9
  260. package/dist/types/appShell/BarSearch.d.ts +6 -15
  261. package/dist/types/appShell/HeaderTools.d.ts +6 -16
  262. package/dist/types/appShell/MobileTabBar.d.ts +6 -13
  263. package/dist/types/appShell/SettingsMenu.d.ts +6 -10
  264. package/dist/types/appShell/SideMenu.d.ts +6 -20
  265. package/dist/types/appShell/SideMenuEntry.d.ts +6 -11
  266. package/dist/types/appShell/SubmenuActionsMenu.d.ts +6 -12
  267. package/dist/types/appShell/TabBarEditor.d.ts +6 -9
  268. package/dist/types/appShell/TopMenu.d.ts +6 -5
  269. package/dist/types/appShell/Wordmark.d.ts +6 -7
  270. package/dist/types/auth/PasswordChecklist.d.ts +6 -9
  271. package/dist/types/calendar/EventCalendar.d.ts +6 -23
  272. package/dist/types/calendar/EventDetails.d.ts +6 -10
  273. package/dist/types/datePickers/DateInput.d.ts +7 -22
  274. package/dist/types/datePickers/DateTimeInput.d.ts +7 -27
  275. package/dist/types/datePickers/MonthInput.d.ts +7 -21
  276. package/dist/types/datePickers/TimeInput.d.ts +7 -21
  277. package/dist/types/datePickers/usePickerField.d.ts +5 -1
  278. package/dist/types/downloads/DownloadManager.d.ts +6 -6
  279. package/dist/types/feedback/ConfirmDialog.d.ts +6 -18
  280. package/dist/types/feedback/ModalWaiting.d.ts +6 -9
  281. package/dist/types/feedback/NewBuildNotice.d.ts +6 -10
  282. package/dist/types/filepond/FileDropzone.d.ts +6 -29
  283. package/dist/types/forms/FormProblemAlert.d.ts +6 -5
  284. package/dist/types/forms/FormWizard.d.ts +12 -1
  285. package/dist/types/forms/SubmitButton.d.ts +6 -6
  286. package/dist/types/forms/auth/SignInForm.d.ts +6 -11
  287. package/dist/types/forms/fieldRules.d.ts +44 -0
  288. package/dist/types/forms/index.d.ts +1 -0
  289. package/dist/types/forms/security/SecondStepForm.d.ts +6 -14
  290. package/dist/types/forms/security/TwoStepVerificationCard.d.ts +6 -21
  291. package/dist/types/forms/useValidationText.d.ts +12 -0
  292. package/dist/types/forms/validation.d.ts +117 -174
  293. package/dist/types/forms/validationMessages.d.ts +54 -6
  294. package/dist/types/forms.d.ts +1 -1
  295. package/dist/types/hookForm/FormProblemAlert.d.ts +6 -5
  296. package/dist/types/hookForm/FormWizard.d.ts +12 -1
  297. package/dist/types/hookForm/SubmitButton.d.ts +6 -13
  298. package/dist/types/inbox/InboxLayout.d.ts +6 -17
  299. package/dist/types/inbox/MessageList.d.ts +6 -18
  300. package/dist/types/inbox/MessageView.d.ts +6 -14
  301. package/dist/types/index.d.ts +10 -2
  302. package/dist/types/inputs/CatalogInput.d.ts +7 -24
  303. package/dist/types/inputs/CheckboxGroupInput.d.ts +7 -18
  304. package/dist/types/inputs/ColorInput.d.ts +7 -16
  305. package/dist/types/inputs/FieldFrame.d.ts +6 -10
  306. package/dist/types/inputs/MaskedInput.d.ts +3 -0
  307. package/dist/types/inputs/NumberInput.d.ts +7 -20
  308. package/dist/types/inputs/RepeatingFields.d.ts +7 -28
  309. package/dist/types/inputs/SelectInput.d.ts +7 -20
  310. package/dist/types/inputs/TextInput.d.ts +3 -0
  311. package/dist/types/inputs/TransferListInput.d.ts +7 -20
  312. package/dist/types/inputs/WizardLayout.d.ts +11 -21
  313. package/dist/types/inputs/useWizardSteps.d.ts +23 -0
  314. package/dist/types/inputs/wizardSteps.d.ts +65 -0
  315. package/dist/types/kanban/KanbanBoard.d.ts +6 -25
  316. package/dist/types/legal/CookieNotice.d.ts +6 -16
  317. package/dist/types/legal/TermsAcceptanceDialog.d.ts +6 -18
  318. package/dist/types/locales/en.d.ts +24 -0
  319. package/dist/types/locales/es.d.ts +24 -0
  320. package/dist/types/maps/MapView.d.ts +6 -17
  321. package/dist/types/mexico/CfdiSummary.d.ts +6 -11
  322. package/dist/types/mexico/EfirmaFields.d.ts +6 -16
  323. package/dist/types/mexico/mexicoIdentifiers.d.ts +7 -0
  324. package/dist/types/mexico/mexicoRules.d.ts +19 -3
  325. package/dist/types/mexico.d.ts +2 -2
  326. package/dist/types/pwa/InstallAppSetting.d.ts +6 -6
  327. package/dist/types/pwa/InstallInstructionsDialog.d.ts +6 -9
  328. package/dist/types/pwa/InstallPromptBanner.d.ts +6 -11
  329. package/dist/types/react-hook-form.d.ts +1 -0
  330. package/dist/types/richText/RichTextEditor.d.ts +7 -22
  331. package/dist/types/richText/RichTextView.d.ts +6 -9
  332. package/dist/types/security/ActivityLog.d.ts +6 -9
  333. package/dist/types/security/RecoveryCodesDialog.d.ts +6 -11
  334. package/dist/types/security/SessionsCard.d.ts +6 -11
  335. package/dist/types/table.d.ts +3 -1
  336. package/dist/types/theme/themePieces.d.ts +55 -3
  337. package/dist/types/toolkit/ToolkitProvider.d.ts +13 -1
  338. package/dist/types/toolkit/browserNavigation.d.ts +7 -3
  339. package/dist/types/toolkit/combineTexts.d.ts +6 -0
  340. package/dist/types/toolkit/hashNavigation.d.ts +4 -2
  341. package/dist/types/toolkit/navigationPaths.d.ts +35 -0
  342. package/dist/types/toolkit/pathIndex.d.ts +27 -0
  343. package/dist/types/toolkit/toolkitSettings.d.ts +7 -4
  344. package/dist/types/ui/actions/ActionsMenu.d.ts +9 -11
  345. package/dist/types/ui/actions/CopyButton.d.ts +6 -12
  346. package/dist/types/ui/actions/DownloadMenu.d.ts +6 -29
  347. package/dist/types/ui/actions/SecretField.d.ts +6 -11
  348. package/dist/types/ui/actions/SelectionActions.d.ts +6 -13
  349. package/dist/types/ui/cards/RecordCard.d.ts +16 -29
  350. package/dist/types/ui/cards/RecordCardSkeleton.d.ts +6 -11
  351. package/dist/types/ui/countdown/Countdown.d.ts +6 -15
  352. package/dist/types/ui/documents/PdfViewerDialog.d.ts +6 -13
  353. package/dist/types/ui/filters/DateRangeFilter.d.ts +6 -10
  354. package/dist/types/ui/filters/FilterDrawer.d.ts +6 -17
  355. package/dist/types/ui/filters/PeriodRangeSelector.d.ts +6 -13
  356. package/dist/types/ui/filters/QuickFilters.d.ts +6 -12
  357. package/dist/types/ui/help/HelpButton.d.ts +6 -10
  358. package/dist/types/ui/help/HelpCarouselDialog.d.ts +6 -14
  359. package/dist/types/ui/index.d.ts +1 -1
  360. package/dist/types/ui/labels/MarkedText.d.ts +10 -0
  361. package/dist/types/ui/labels/Tag.d.ts +6 -12
  362. package/dist/types/ui/labels/markedTextPieces.d.ts +22 -0
  363. package/dist/types/ui/layout/CollapsibleSection.d.ts +6 -12
  364. package/dist/types/ui/layout/ContentDialog.d.ts +6 -10
  365. package/dist/types/ui/layout/DefinitionList.d.ts +6 -9
  366. package/dist/types/ui/layout/DetailHeader.d.ts +6 -10
  367. package/dist/types/ui/layout/ErrorPage.d.ts +6 -17
  368. package/dist/types/ui/layout/FullPageLoader.d.ts +6 -6
  369. package/dist/types/ui/layout/GlassPanel.d.ts +6 -8
  370. package/dist/types/ui/layout/PageHeader.d.ts +6 -10
  371. package/dist/types/ui/layout/PageHeading.d.ts +6 -11
  372. package/dist/types/ui/layout/ProfileHeader.d.ts +6 -11
  373. package/dist/types/ui/layout/ScrollableRow.d.ts +6 -13
  374. package/dist/types/ui/layout/SectionCard.d.ts +6 -8
  375. package/dist/types/ui/metrics/BarList.d.ts +6 -12
  376. package/dist/types/ui/metrics/CalendarHeatmap.d.ts +6 -19
  377. package/dist/types/ui/metrics/ChartTooltip.d.ts +6 -6
  378. package/dist/types/ui/metrics/ColumnChart.d.ts +6 -25
  379. package/dist/types/ui/metrics/LineChart.d.ts +6 -20
  380. package/dist/types/ui/metrics/MetricCard.d.ts +6 -17
  381. package/dist/types/ui/metrics/TableToggle.d.ts +6 -6
  382. package/dist/types/ui/metrics/chartParts.d.ts +15 -30
  383. package/dist/types/ui/navigation/BackButton.d.ts +7 -9
  384. package/dist/types/ui/navigation/ColorSchemeToggle.d.ts +6 -9
  385. package/dist/types/ui/navigation/FontSizeToggle.d.ts +6 -8
  386. package/dist/types/ui/navigation/LanguageToggle.d.ts +6 -8
  387. package/dist/types/ui/navigation/PageBreadcrumbs.d.ts +6 -7
  388. package/dist/types/ui/navigation/RouteTabs.d.ts +7 -12
  389. package/dist/types/ui/timeline/GroupedTimeline.d.ts +6 -23
  390. package/dist/types/upload/AttachmentsInput.d.ts +11 -1
  391. package/dist/types/upload/FileDropzone.d.ts +6 -21
  392. package/dist/types/upload/FileInput.d.ts +7 -24
  393. package/dist/types/upload/FileUploadInput.d.ts +7 -31
  394. package/dist/types/upload/ImageInput.d.ts +7 -25
  395. package/dist/types/upload/TemplateDownloadButton.d.ts +6 -18
  396. package/dist/ui/actions/ActionsMenu.js +14 -5
  397. package/dist/ui/actions/ActionsMenu.js.map +1 -1
  398. package/dist/ui/actions/CopyButton.js +8 -3
  399. package/dist/ui/actions/CopyButton.js.map +1 -1
  400. package/dist/ui/actions/DownloadMenu.js +3 -2
  401. package/dist/ui/actions/DownloadMenu.js.map +1 -1
  402. package/dist/ui/actions/SecretField.js +8 -3
  403. package/dist/ui/actions/SecretField.js.map +1 -1
  404. package/dist/ui/actions/SelectionActions.js +3 -2
  405. package/dist/ui/actions/SelectionActions.js.map +1 -1
  406. package/dist/ui/cards/RecordCard.js +10 -5
  407. package/dist/ui/cards/RecordCard.js.map +1 -1
  408. package/dist/ui/cards/RecordCardSkeleton.js +8 -3
  409. package/dist/ui/cards/RecordCardSkeleton.js.map +1 -1
  410. package/dist/ui/countdown/Countdown.js +3 -2
  411. package/dist/ui/countdown/Countdown.js.map +1 -1
  412. package/dist/ui/documents/PdfViewerDialog.js +3 -2
  413. package/dist/ui/documents/PdfViewerDialog.js.map +1 -1
  414. package/dist/ui/filters/DateRangeFilter.js +3 -2
  415. package/dist/ui/filters/DateRangeFilter.js.map +1 -1
  416. package/dist/ui/filters/FilterDrawer.js +3 -2
  417. package/dist/ui/filters/FilterDrawer.js.map +1 -1
  418. package/dist/ui/filters/PeriodRangeSelector.js +3 -2
  419. package/dist/ui/filters/PeriodRangeSelector.js.map +1 -1
  420. package/dist/ui/filters/QuickFilters.js +3 -2
  421. package/dist/ui/filters/QuickFilters.js.map +1 -1
  422. package/dist/ui/help/HelpButton.js +8 -3
  423. package/dist/ui/help/HelpButton.js.map +1 -1
  424. package/dist/ui/help/HelpCarouselDialog.js +3 -2
  425. package/dist/ui/help/HelpCarouselDialog.js.map +1 -1
  426. package/dist/ui/labels/MarkedText.js +31 -0
  427. package/dist/ui/labels/MarkedText.js.map +1 -0
  428. package/dist/ui/labels/Tag.js +3 -2
  429. package/dist/ui/labels/Tag.js.map +1 -1
  430. package/dist/ui/labels/markedTextPieces.js +64 -0
  431. package/dist/ui/labels/markedTextPieces.js.map +1 -0
  432. package/dist/ui/layout/CollapsibleSection.js +3 -2
  433. package/dist/ui/layout/CollapsibleSection.js.map +1 -1
  434. package/dist/ui/layout/ContentDialog.js +3 -2
  435. package/dist/ui/layout/ContentDialog.js.map +1 -1
  436. package/dist/ui/layout/DefinitionList.js +3 -2
  437. package/dist/ui/layout/DefinitionList.js.map +1 -1
  438. package/dist/ui/layout/DetailHeader.js +3 -2
  439. package/dist/ui/layout/DetailHeader.js.map +1 -1
  440. package/dist/ui/layout/ErrorPage.js +3 -2
  441. package/dist/ui/layout/ErrorPage.js.map +1 -1
  442. package/dist/ui/layout/FullPageLoader.js +3 -2
  443. package/dist/ui/layout/FullPageLoader.js.map +1 -1
  444. package/dist/ui/layout/GlassPanel.js +3 -2
  445. package/dist/ui/layout/GlassPanel.js.map +1 -1
  446. package/dist/ui/layout/PageHeader.js +3 -2
  447. package/dist/ui/layout/PageHeader.js.map +1 -1
  448. package/dist/ui/layout/PageHeading.js +3 -2
  449. package/dist/ui/layout/PageHeading.js.map +1 -1
  450. package/dist/ui/layout/ProfileHeader.js +3 -2
  451. package/dist/ui/layout/ProfileHeader.js.map +1 -1
  452. package/dist/ui/layout/ScrollableRow.js +3 -2
  453. package/dist/ui/layout/ScrollableRow.js.map +1 -1
  454. package/dist/ui/layout/SectionCard.js +3 -2
  455. package/dist/ui/layout/SectionCard.js.map +1 -1
  456. package/dist/ui/metrics/BarList.js +3 -2
  457. package/dist/ui/metrics/BarList.js.map +1 -1
  458. package/dist/ui/metrics/CalendarHeatmap.js +3 -2
  459. package/dist/ui/metrics/CalendarHeatmap.js.map +1 -1
  460. package/dist/ui/metrics/ChartTooltip.js +3 -2
  461. package/dist/ui/metrics/ChartTooltip.js.map +1 -1
  462. package/dist/ui/metrics/ColumnChart.js +3 -2
  463. package/dist/ui/metrics/ColumnChart.js.map +1 -1
  464. package/dist/ui/metrics/LineChart.js +3 -2
  465. package/dist/ui/metrics/LineChart.js.map +1 -1
  466. package/dist/ui/metrics/MetricCard.js +3 -2
  467. package/dist/ui/metrics/MetricCard.js.map +1 -1
  468. package/dist/ui/metrics/TableToggle.js +3 -2
  469. package/dist/ui/metrics/TableToggle.js.map +1 -1
  470. package/dist/ui/metrics/chartParts.js +7 -4
  471. package/dist/ui/metrics/chartParts.js.map +1 -1
  472. package/dist/ui/navigation/BackButton.js +7 -2
  473. package/dist/ui/navigation/BackButton.js.map +1 -1
  474. package/dist/ui/navigation/ColorSchemeToggle.js +3 -2
  475. package/dist/ui/navigation/ColorSchemeToggle.js.map +1 -1
  476. package/dist/ui/navigation/FontSizeToggle.js +3 -2
  477. package/dist/ui/navigation/FontSizeToggle.js.map +1 -1
  478. package/dist/ui/navigation/LanguageToggle.js +3 -2
  479. package/dist/ui/navigation/LanguageToggle.js.map +1 -1
  480. package/dist/ui/navigation/PageBreadcrumbs.js +3 -2
  481. package/dist/ui/navigation/PageBreadcrumbs.js.map +1 -1
  482. package/dist/ui/navigation/RouteTabs.js +8 -4
  483. package/dist/ui/navigation/RouteTabs.js.map +1 -1
  484. package/dist/ui/timeline/GroupedTimeline.js +3 -2
  485. package/dist/ui/timeline/GroupedTimeline.js.map +1 -1
  486. package/dist/upload/AttachmentsInput.js +9 -6
  487. package/dist/upload/AttachmentsInput.js.map +1 -1
  488. package/dist/upload/FileChip.js +2 -1
  489. package/dist/upload/FileChip.js.map +1 -1
  490. package/dist/upload/FileDropzone.js +5 -4
  491. package/dist/upload/FileDropzone.js.map +1 -1
  492. package/dist/upload/FileInput.js +9 -6
  493. package/dist/upload/FileInput.js.map +1 -1
  494. package/dist/upload/FileUploadInput.js +9 -6
  495. package/dist/upload/FileUploadInput.js.map +1 -1
  496. package/dist/upload/ImageInput.js +8 -5
  497. package/dist/upload/ImageInput.js.map +1 -1
  498. package/dist/upload/TemplateDownloadButton.js +3 -2
  499. package/dist/upload/TemplateDownloadButton.js.map +1 -1
  500. package/package.json +1 -1
  501. package/cli/templates/project/src/app/combineTexts.js.tpl +0 -31
@@ -0,0 +1,266 @@
1
+ // calcita check:texts — revisa los textos del proyecto sin abrir la app:
2
+ // 1. que cada t('llave') escrita tal cual en el código exista en todos los idiomas (con su namespace: el de
3
+ // useToolkitTranslation('orders') del archivo, o el que trae la llave, 'common:actions.save');
4
+ // 2. que cada idioma tenga las mismas llaves que los demás (lo que falta en inglés se pintaría en español… o como llave).
5
+ // Sin esto, una llave que no existe se descubre cuando alguien la ve en la pantalla («profile:tabs.documents»).
6
+ //
7
+ // Lo que NO revisa: las llaves armadas con una variable (t(`status.${value}`)) ni las que llevan defaultValue (son opcionales).
8
+ // Para esas está el aviso de Calcita en la consola (onMissingText de ToolkitProvider).
9
+ //
10
+ // Lee los archivos de textos del proyecto (src/**/*Texts.js y src/**/texts.js: cada uno exporta {es: {...}, en: {...}}) con
11
+ // node; el que los junta (con import.meta.glob) se salta. Si uno importa otro archivo, puede ser con ruta relativa o con el
12
+ // alias @/, que aquí apunta a la carpeta del código, como en vite.config.js (trampa pagada: un texts.js que reexportaba con
13
+ // @/ no se leía y sus 400 llaves salían como faltantes). Los namespaces de Calcita (common, table, validation…) salen de su dist/.
14
+ //
15
+ // Un verde falso es peor que un rojo: sin la carpeta, sin ningún archivo de textos o con uno que no se pudo leer, la revisión
16
+ // sale con problema y no compara llaves (con un archivo sin leer, todas las suyas saldrían como faltantes).
17
+ //
18
+ // Solo node, sin dependencias (el CLI corre antes de que el proyecto tenga nada instalado): function (regla de la casa).
19
+ // Pruebas en checkTexts.test.js.
20
+ import {readdir, readFile, stat} from 'node:fs/promises';
21
+ import * as nodeModule from 'node:module';
22
+ import {join, relative} from 'node:path';
23
+ import {pathToFileURL} from 'node:url';
24
+
25
+ const SOURCE_EXTENSIONS = /\.(jsx?|tsx?)$/;
26
+ const TEST_FILE = /\.test\.(jsx?|tsx?)$/;
27
+ const TEXT_FILE = /(?:^|\/)(?:texts|[A-Za-z0-9]+Texts)\.js$/;
28
+ const IGNORED_FOLDERS = new Set(['node_modules', 'dist', 'coverage', '.git']);
29
+ // el namespace de un archivo: useToolkitTranslation('orders') o useToolkitTranslation(['orders', …])
30
+ const NAMESPACE_CALL = /useToolkitTranslation\(\s*\[?\s*(['"])([\w-]+)\1/g;
31
+ // una llave escrita tal cual: t('list.title') o t("common:actions.save"); con lo que sigue, para ver si trae defaultValue
32
+ const KEY_CALL = /\bt\(\s*(['"])([^'"`$\n]+?)\1([^)]{0,200})/g;
33
+ const PLURAL_SUFFIXES = ['', '_zero', '_one', '_two', '_few', '_many', '_other'];
34
+
35
+ // la carpeta del código de la revisión en curso, para el alias @/ de los archivos de textos
36
+ let aliasFolderUrl = null;
37
+ let isAliasRegistered = false;
38
+
39
+ /**
40
+ * Que node entienda @/ al leer los archivos de textos, como Vite. Se registra una sola vez por proceso y lee aliasFolderUrl,
41
+ * la carpeta de la revisión en curso. registerHooks llegó en Node 22.15: con uno anterior, un archivo de textos que importe
42
+ * con @/ no se puede leer y se dice.
43
+ */
44
+ function understandAlias() {
45
+ if (isAliasRegistered || typeof nodeModule.registerHooks !== 'function') {
46
+ return;
47
+ }
48
+ nodeModule.registerHooks({
49
+ resolve(specifier, context, nextResolve) {
50
+ if (aliasFolderUrl && specifier.startsWith('@/')) {
51
+ return nextResolve(new URL(specifier.slice(2), aliasFolderUrl).href, context);
52
+ }
53
+ return nextResolve(specifier, context);
54
+ },
55
+ });
56
+ isAliasRegistered = true;
57
+ }
58
+
59
+ /**
60
+ * Los archivos de una carpeta y sus subcarpetas, sin node_modules ni dist.
61
+ *
62
+ * @param {string} folder
63
+ * @returns {Promise<string[]>}
64
+ */
65
+ async function filesOf(folder) {
66
+ const entries = await readdir(folder, {withFileTypes: true}).catch(() => []);
67
+ const nested = await Promise.all(entries.map(async (entry) => {
68
+ const path = join(folder, entry.name);
69
+ if (entry.isDirectory()) {
70
+ return IGNORED_FOLDERS.has(entry.name) ? [] : filesOf(path);
71
+ }
72
+ return [path];
73
+ }));
74
+ return nested.flat();
75
+ }
76
+
77
+ /**
78
+ * Suma los textos de un archivo a los de todos (por idioma y namespace, objetos por dentro). Aquí no truena con un repetido:
79
+ * lo anota, para decirlo junto con lo demás.
80
+ */
81
+ function addTexts(combined, fileTexts, duplicates, fileName) {
82
+ const mergeInto = (target, added, place) => {
83
+ for (const [key, value] of Object.entries(added)) {
84
+ const isObject = value && typeof value === 'object';
85
+ const keyPath = [...place.path, key];
86
+ if (isObject && target[key] && typeof target[key] === 'object') {
87
+ mergeInto(target[key], value, {...place, path: keyPath});
88
+ } else if (Object.hasOwn(target, key)) {
89
+ duplicates.push(`«${place.namespace}:${keyPath.join('.')}» en ${place.language} (otra vez en ${fileName})`);
90
+ } else {
91
+ target[key] = isObject ? structuredClone(value) : value;
92
+ }
93
+ }
94
+ };
95
+ for (const [language, namespaces] of Object.entries(fileTexts)) {
96
+ combined[language] ??= {};
97
+ for (const [namespace, keys] of Object.entries(namespaces ?? {})) {
98
+ combined[language][namespace] ??= {};
99
+ mergeInto(combined[language][namespace], keys, {language, namespace, path: []});
100
+ }
101
+ }
102
+ }
103
+
104
+ /**
105
+ * ¿Parece un objeto de textos? ({es: {namespace: {...}}, en: {...}}: llaves de idioma con objetos adentro)
106
+ *
107
+ * @param {unknown} value
108
+ * @returns {boolean}
109
+ */
110
+ function looksLikeTexts(value) {
111
+ return Boolean(value) && typeof value === 'object' && Object.keys(value).length > 0
112
+ && Object.entries(value).every(([language, namespaces]) => /^[a-z]{2}(-[A-Z]{2})?$/.test(language) && namespaces && typeof namespaces === 'object');
113
+ }
114
+
115
+ /** Lo que hay en un camino con puntos ('list.title') dentro de un objeto. */
116
+ function valueAt(object, dottedKey) {
117
+ return dottedKey.split('.').reduce((current, step) => (current && typeof current === 'object' ? current[step] : undefined), object);
118
+ }
119
+
120
+ /** ¿Existe la llave (o su plural) en ese namespace? */
121
+ function hasKey(namespaceTexts, key) {
122
+ return PLURAL_SUFFIXES.some((suffix) => typeof valueAt(namespaceTexts, `${key}${suffix}`) === 'string');
123
+ }
124
+
125
+ /** Todas las llaves con texto de un objeto, con puntos (para comparar idiomas); los plurales cuentan como su llave base. */
126
+ function leafKeysOf(object, prefix = '') {
127
+ return Object.entries(object ?? {}).flatMap(([key, value]) => {
128
+ const path = prefix ? `${prefix}.${key}` : key;
129
+ if (value && typeof value === 'object') {
130
+ return leafKeysOf(value, path);
131
+ }
132
+ return [path.replace(/_(zero|one|two|few|many|other)$/, '')];
133
+ });
134
+ }
135
+
136
+ /**
137
+ * Los textos de Calcita (common, table, validation…), del dist/ del paquete que corre este CLI. Sin dist/ (el repo del paquete
138
+ * sin construir), ninguno: las llaves de esos namespaces no se revisan.
139
+ *
140
+ * @returns {Promise<Record<string, object>>}
141
+ */
142
+ async function packageTextsOf() {
143
+ const texts = {};
144
+ for (const language of ['es', 'en']) {
145
+ try {
146
+ const module = await import(new URL(`../dist/locales/${language}.js`, import.meta.url).href);
147
+ texts[language] = module.default ?? {};
148
+ } catch {
149
+ return {};
150
+ }
151
+ }
152
+ return texts;
153
+ }
154
+
155
+ /**
156
+ * @param {object} options
157
+ * @param {string} options.projectRoot la carpeta del proyecto
158
+ * @param {string} [options.sourceFolder] dónde está el código (por omisión src; en Laravel, resources/js): `--source` del CLI
159
+ * @param {Record<string, object>} [options.packageTexts] los textos de Calcita (por omisión, los de su dist/)
160
+ * @returns {Promise<{problems: string[], checkedKeys: number, textFiles: string[], languages: string[], sourceFolder: string}>}
161
+ */
162
+ export async function checkTexts({projectRoot, sourceFolder: askedFolder = 'src', packageTexts}) {
163
+ // 'resources/js/' y 'resources/js' son la misma carpeta (el mensaje la dice sin la diagonal de más)
164
+ const sourceFolder = askedFolder.replace(/[\\/]+$/, '');
165
+ const sourceRoot = join(projectRoot, sourceFolder);
166
+ const nothingChecked = {checkedKeys: 0, textFiles: [], languages: [], sourceFolder};
167
+ const isFolder = await stat(sourceRoot).then((found) => found.isDirectory(), () => false);
168
+ if (!isFolder) {
169
+ return {...nothingChecked, problems: [`No existe la carpeta ${sourceFolder}/ en el proyecto: no hay código que revisar (si está en otra, --source resources/js)`]};
170
+ }
171
+ const files = await filesOf(sourceRoot);
172
+ const problems = [];
173
+ aliasFolderUrl = `${pathToFileURL(sourceRoot).href}/`;
174
+ understandAlias();
175
+
176
+ // 1. los textos del proyecto
177
+ const projectTexts = {};
178
+ const duplicates = [];
179
+ const textFiles = files.filter((file) => TEXT_FILE.test(file.replaceAll('\\', '/')));
180
+ const readTextFiles = [];
181
+ const unreadTextFiles = [];
182
+ const addedTexts = new Set();
183
+ for (const file of textFiles) {
184
+ const fileName = relative(projectRoot, file);
185
+ // el que JUNTA los textos (appTexts.js, con el glob de Vite) no es un archivo de textos: se salta
186
+ if ((await readFile(file, 'utf8')).includes('import.meta.glob')) {
187
+ continue;
188
+ }
189
+ readTextFiles.push(fileName);
190
+ try {
191
+ const module = await import(pathToFileURL(file).href);
192
+ const exported = Object.values(module).filter(looksLikeTexts);
193
+ if (exported.length === 0) {
194
+ problems.push(`${fileName}: no exporta textos con la forma {es: {...}, en: {...}}`);
195
+ }
196
+ // el mismo objeto que otro archivo ya reexportó (texts.js que reexporta documentFileTexts.js) se suma una vez:
197
+ // node da el mismo objeto por archivo, y sumarlo dos veces lo haría pasar por repetido
198
+ exported.filter((fileTexts) => !addedTexts.has(fileTexts)).forEach((fileTexts) => {
199
+ addedTexts.add(fileTexts);
200
+ addTexts(projectTexts, fileTexts, duplicates, fileName);
201
+ });
202
+ } catch (error) {
203
+ unreadTextFiles.push(fileName);
204
+ const reason = error.message.split('\n')[0];
205
+ const hint = isAliasRegistered
206
+ ? 'revise lo que importa: otro archivo va con ruta relativa o con @/'
207
+ : 'si importa con @/, esta revisión necesita Node 22.15 o más';
208
+ problems.push(`${fileName}: no se pudo leer (${reason}); ${hint}`);
209
+ }
210
+ }
211
+ if (unreadTextFiles.length > 0) {
212
+ problems.push(`No revisé las llaves: sin ${unreadTextFiles.length === 1 ? 'ese archivo' : 'esos archivos'} de textos, sus llaves saldrían todas como faltantes`);
213
+ return {...nothingChecked, problems, textFiles: readTextFiles};
214
+ }
215
+ if (readTextFiles.length === 0) {
216
+ return {...nothingChecked, problems: [`No encontré archivos de textos (*Texts.js o texts.js) en ${sourceFolder}/: no hay con qué comparar (si el código está en otra carpeta, --source resources/js)`]};
217
+ }
218
+ problems.push(...duplicates.map((duplicate) => `texto repetido: ${duplicate}`));
219
+
220
+ const languages = Object.keys(projectTexts);
221
+ // 2. que cada idioma tenga las mismas llaves
222
+ for (const language of languages) {
223
+ for (const otherLanguage of languages.filter((other) => other !== language)) {
224
+ for (const namespace of Object.keys(projectTexts[language])) {
225
+ const otherKeys = new Set(leafKeysOf(projectTexts[otherLanguage][namespace]));
226
+ for (const key of new Set(leafKeysOf(projectTexts[language][namespace]))) {
227
+ if (!otherKeys.has(key)) {
228
+ problems.push(`«${namespace}:${key}» está en ${language} y falta en ${otherLanguage}`);
229
+ }
230
+ }
231
+ }
232
+ }
233
+ }
234
+
235
+ // 3. cada t('llave') escrita tal cual
236
+ const calcitaTexts = packageTexts ?? await packageTextsOf();
237
+ const textsOf = (language, namespace) => projectTexts[language]?.[namespace] ?? calcitaTexts[language]?.[namespace];
238
+ let checkedKeys = 0;
239
+ for (const file of files.filter((path) => SOURCE_EXTENSIONS.test(path) && !TEST_FILE.test(path))) {
240
+ const code = await readFile(file, 'utf8');
241
+ const fileNamespaces = [...code.matchAll(NAMESPACE_CALL)].map((match) => match[2]);
242
+ for (const match of code.matchAll(KEY_CALL)) {
243
+ const [, , key, rest] = match;
244
+ // con defaultValue la llave es opcional; una llave con espacios es un texto, no una llave
245
+ if (/defaultValue/.test(rest) || /\s/.test(key)) {
246
+ continue;
247
+ }
248
+ const line = code.slice(0, match.index).split('\n').length;
249
+ const place = `${relative(projectRoot, file)}:${line}`;
250
+ const [explicitNamespace, plainKey] = key.includes(':') ? key.split(':', 2) : [null, key];
251
+ const candidates = explicitNamespace ? [explicitNamespace] : fileNamespaces;
252
+ if (candidates.length === 0) {
253
+ continue;
254
+ }
255
+ checkedKeys += 1;
256
+ for (const language of languages.length > 0 ? languages : ['es']) {
257
+ const found = candidates.some((namespace) => hasKey(textsOf(language, namespace), plainKey));
258
+ if (!found) {
259
+ problems.push(`${place} «${explicitNamespace ? key : `${candidates[0]}:${key}`}» no existe en ${language}`);
260
+ }
261
+ }
262
+ }
263
+ }
264
+
265
+ return {problems, checkedKeys, textFiles: readTextFiles, languages, sourceFolder};
266
+ }
@@ -7,7 +7,7 @@ import {readFile} from 'node:fs/promises';
7
7
  // Lo que el paquete no usa, así que no está en sus devDependencies: i18next (su adaptador recibe la instancia del proyecto;
8
8
  // versión con más de dos semanas publicada al escogerla, pnpm espera un día antes de instalar una recién salida), qrcode
9
9
  // (el QR de la cuenta de demostración; la misma versión que usan los ejemplos) y las pruebas de pantalla del proyecto
10
- // (Vitest con jsdom y Testing Library), en las versiones que ya se probaron juntas con el mismo Vite 7 y React 19
10
+ // (Vitest con jsdom y Testing Library), en las versiones que ya se probaron juntas con el mismo Vite 7 y React 19, y ESLint
11
11
  const VERSIONS_NOT_IN_PACKAGE = Object.freeze({
12
12
  i18next: '26.4.2',
13
13
  qrcode: '1.5.4',
@@ -16,6 +16,11 @@ const VERSIONS_NOT_IN_PACKAGE = Object.freeze({
16
16
  '@testing-library/dom': '10.4.2',
17
17
  '@testing-library/react': '16.3.3',
18
18
  '@testing-library/user-event': '14.6.7',
19
+ // ESLint del proyecto nuevo (pnpm lint): versiones con más de dos semanas publicadas al escogerlas (03/11/2026)
20
+ eslint: '10.13.0',
21
+ '@eslint/js': '10.0.1',
22
+ 'eslint-plugin-react-hooks': '7.1.1',
23
+ globals: '17.13.0',
19
24
  });
20
25
 
21
26
  /**
@@ -15,8 +15,10 @@ const BASE_DEPENDENCIES = Object.freeze([
15
15
  const HOOK_FORM_DEPENDENCIES = Object.freeze(['@hookform/resolvers', 'react-hook-form']);
16
16
  // la cuenta: las llaves de acceso (las pide @stackcraftmx/calcita/account) y el QR de la demostración
17
17
  const ACCOUNT_DEPENDENCIES = Object.freeze(['@simplewebauthn/browser', 'qrcode']);
18
- // las pruebas de pantalla (pnpm test): Vitest con jsdom como navegador y Testing Library para picar como una persona
18
+ // las pruebas de pantalla (pnpm test): Vitest con jsdom como navegador y Testing Library para picar como una persona; y ESLint
19
+ // (pnpm lint), que caza al escribir lo que si no se ve en la pantalla del cliente (una variable que no existe: «t is not defined»)
19
20
  const DEV_DEPENDENCIES = Object.freeze([
21
+ '@eslint/js', 'eslint', 'eslint-plugin-react-hooks', 'globals',
20
22
  '@testing-library/dom', '@testing-library/react', '@testing-library/user-event', '@vitejs/plugin-react', 'jsdom', 'vite', 'vitest',
21
23
  ]);
22
24
 
@@ -43,7 +45,7 @@ export function packageJsonOf({packageName, answers, calcitaPackage}) {
43
45
  version: '0.1.0',
44
46
  private: true,
45
47
  type: 'module',
46
- scripts: {dev: 'vite', build: 'vite build', preview: 'vite preview', test: 'vitest run'},
48
+ scripts: {dev: 'vite', build: 'vite build', preview: 'vite preview', test: 'vitest run', lint: 'eslint .', 'check:texts': 'calcita check:texts'},
47
49
  dependencies: sortedByKey({'@stackcraftmx/calcita': `^${calcitaPackage.version}`, ...versionsOf(dependencyNames, calcitaPackage)}),
48
50
  devDependencies: sortedByKey(versionsOf([...DEV_DEPENDENCIES], calcitaPackage)),
49
51
  engines: {node: '>=22'},
@@ -55,6 +55,17 @@ clic o escribe con `user`. Pasa por lo mismo que la persona (permisos, menú, te
55
55
  módulo de ejemplo, y `make:module` arma la de cada módulo nuevo. Corren con Vitest y jsdom (un navegador de mentira): lo que
56
56
  depende de medir o pintar se revisa en un navegador de verdad.
57
57
 
58
+ ## Revisar antes de subir
59
+
60
+ ```bash
61
+ pnpm lint # ESLint: una variable que no existe, los hooks de React fuera de orden
62
+ pnpm check:texts # que cada t('llave') exista en todos los idiomas y que los idiomas tengan las mismas llaves
63
+ ```
64
+
65
+ Las dos cazan al escribir lo que si no se descubre en la pantalla del cliente («t is not defined», «orders:tabs.documents»).
66
+ Una llave armada con una variable no se puede revisar así: para esas, Calcita avisa en la consola del navegador (y
67
+ `onMissingText` de `ToolkitProvider` se lo dice a su proyecto).
68
+
58
69
  ## Publicar una versión nueva
59
70
 
60
71
  `pnpm build` deja la app en `dist/`, con `version.json` (la versión de esa compilación). La app publicada lo vuelve a leer al
@@ -81,7 +92,16 @@ location /ventas/ {
81
92
  }
82
93
  ```
83
94
 
84
- Con otro prefijo se vuelve a construir.
95
+ Con otro prefijo se vuelve a construir… o, si el prefijo lo decide el servidor al servir la página (Laravel con
96
+ `config('app.url')`, un proxy con su ruta), se construye una sola vez con archivos relativos y la página trae su `<base>`:
97
+
98
+ ```bash
99
+ ASSET_URL=./ pnpm build
100
+ ```
101
+
102
+ ```html
103
+ <base href="/ventas/"> <!-- lo pone el servidor en index.html; el router y publicUrlOf lo leen al abrir -->
104
+ ```
85
105
 
86
106
  ## Crecer la app con el CLI
87
107
 
@@ -0,0 +1,27 @@
1
+ // Las reglas de ESLint del proyecto (pnpm lint): lo que se caza al escribir y no en la pantalla del cliente. La que más
2
+ // ahorra: no-undef, una variable que no existe (un `t` que una función auxiliar se quedó sin recibir truena con «t is not
3
+ // defined» hasta que alguien abre esa pantalla). Y las de los hooks de React (en el orden de siempre, con sus dependencias).
4
+ import js from '@eslint/js';
5
+ import reactHooks from 'eslint-plugin-react-hooks';
6
+ import globals from 'globals';
7
+
8
+ export default [
9
+ {ignores: ['dist/', 'coverage/']},
10
+ js.configs.recommended,
11
+ reactHooks.configs.flat.recommended,
12
+ {
13
+ files: ['**/*.{js,jsx}'],
14
+ languageOptions: {
15
+ ecmaVersion: 'latest',
16
+ sourceType: 'module',
17
+ globals: {...globals.browser},
18
+ parserOptions: {ecmaFeatures: {jsx: true}},
19
+ },
20
+ rules: {
21
+ // un parámetro que no se usa a propósito lleva «unused» en el nombre (unusedEvent)
22
+ 'no-unused-vars': ['error', {argsIgnorePattern: '^unused', varsIgnorePattern: '^unused', caughtErrors: 'none'}],
23
+ },
24
+ },
25
+ // la configuración y las pruebas corren en node
26
+ {files: ['*.config.js', 'src/testing/**', '**/*.test.{js,jsx}'], languageOptions: {globals: {...globals.node}}},
27
+ ];
@@ -4,7 +4,8 @@
4
4
  //
5
5
  // OJO: constante de módulo. ToolkitProvider vuelve a configurar todo cuando cambian sus textos; un objeto nuevo en cada
6
6
  // pintada lo haría en cada pintada.
7
- import {combineTexts} from '@/app/combineTexts.js';
7
+ // combineTexts es del paquete: un texto repetido entre módulos truena y los objetos de adentro (validation.fields) se suman
8
+ import {combineTexts} from '@stackcraftmx/calcita';
8
9
  import {shellTexts} from '@/app/shellTexts.js';
9
10
 
10
11
  const MODULE_TEXT_FILES = import.meta.glob('@/modules/*/*Texts.js', {eager: true});
@@ -5,13 +5,21 @@
5
5
  // OJO: una dirección escrita a mano desde la raíz ('/favicon.svg', '/version.json') funciona en la raíz y se rompe detrás del
6
6
  // proxy: se arma con publicUrlOf.
7
7
  //
8
+ // El prefijo también puede llegar al SERVIR la página, sin volver a construir: si la página trae <base href="/ventas/"> (Laravel
9
+ // lo pone con config('app.url'), un proxy con su ruta), manda ese (documentBasePath). Para eso se construye con ASSET_URL=./
10
+ // (archivos relativos a la página). Sin <base>, todo sigue como arriba.
11
+ //
8
12
  // Funciones puras, no renderizan: function (regla de la casa).
9
13
 
14
+ import {documentBasePath} from '@stackcraftmx/calcita';
15
+
10
16
  // en las pruebas de node no hay import.meta.env (es de Vite): ahí, la raíz
11
17
  const APP_BASE_URL = import.meta.env?.BASE_URL ?? '/';
18
+ // el prefijo que puso el servidor en <base href> ('' en la raíz), o null si la página no trae <base>
19
+ const SERVED_BASE_PATH = documentBasePath();
12
20
 
13
21
  /** El prefijo como lo pide el router: '/ventas' (sin la diagonal del final) o '/'. */
14
- export const ROUTER_BASENAME = APP_BASE_URL.replace(/\/$/, '') || '/';
22
+ export const ROUTER_BASENAME = (SERVED_BASE_PATH ?? APP_BASE_URL.replace(/\/$/, '')) || '/';
15
23
 
16
24
  /**
17
25
  * La dirección de un archivo de public/ con el prefijo: 'favicon.svg' → '/ventas/favicon.svg'.
@@ -20,5 +28,5 @@ export const ROUTER_BASENAME = APP_BASE_URL.replace(/\/$/, '') || '/';
20
28
  * @returns {string}
21
29
  */
22
30
  export function publicUrlOf(path) {
23
- return `${APP_BASE_URL}${path}`;
31
+ return SERVED_BASE_PATH === null ? `${APP_BASE_URL}${path}` : `${SERVED_BASE_PATH}/${path}`;
24
32
  }
@@ -21,8 +21,15 @@ const LARGEST_CHUNK_KILOBYTES = 1000;
21
21
  // misma línea que lleva el .env del servidor. Vite pide sus archivos con el prefijo, se lo pone a las rutas de index.html y lo
22
22
  // deja en import.meta.env.BASE_URL, de donde lo toman el router, las imágenes y version.json (src/core/publicPaths.js). Sin
23
23
  // ella, todo en la raíz. Con otro prefijo hay que volver a construir.
24
+ // ASSET_URL=./ : los archivos relativos a la página, para cuando el prefijo lo pone el servidor al servirla (<base href>):
25
+ // una sola compilación sirve detrás de cualquier prefijo (src/core/publicPaths.js)
26
+ const RELATIVE_BASE = './';
24
27
  const basePathOf = (assetUrl) => {
25
- const prefix = (assetUrl ?? '').trim().replace(/^\/+|\/+$/g, '');
28
+ const trimmed = (assetUrl ?? '').trim();
29
+ if (trimmed === '.' || trimmed === RELATIVE_BASE) {
30
+ return RELATIVE_BASE;
31
+ }
32
+ const prefix = trimmed.replace(/^\/+|\/+$/g, '');
26
33
  return prefix ? `/${prefix}/` : '/';
27
34
  };
28
35
 
@@ -3,11 +3,11 @@ import { jsxs, jsx } from "react/jsx-runtime";
3
3
  import Typography from "@mui/material/Typography";
4
4
  import AuditTrailTable from "./AuditTrailTable.js";
5
5
  import ContentDialog from "../ui/layout/ContentDialog.js";
6
- import { piecePart, usePieceProps } from "../theme/themePieces.js";
6
+ import { piecePart, withPieceSlots, usePieceProps } from "../theme/themePieces.js";
7
7
  import { warnUnknownProps } from "../toolkit/unknownProps.js";
8
8
  const THEME_NAME = "CalcitaAuditTrailDialog";
9
9
  const AuditDescription = piecePart(THEME_NAME, "Description", Typography);
10
- function AuditTrailDialog(props) {
10
+ function AuditTrailDialogContent(props) {
11
11
  const {
12
12
  open,
13
13
  onClose,
@@ -41,6 +41,7 @@ function AuditTrailDialog(props) {
41
41
  )
42
42
  ] });
43
43
  }
44
+ const AuditTrailDialog = withPieceSlots(AuditTrailDialogContent, THEME_NAME);
44
45
  export {
45
46
  AuditTrailDialog as default
46
47
  };
@@ -1 +1 @@
1
- {"version":3,"file":"AuditTrailDialog.js","sources":["../../src/DataTable/AuditTrailDialog.jsx"],"sourcesContent":["// Diálogo de auditoría de un renglón: su historia de eventos, paginada y filtrada EN EL SERVIDOR (AuditTrailTable).\n//\n// Sirve para el historial de cualquier registro (un pedido, una cuenta).\n// Solo abre una vista: no ejecuta nada y por eso no lleva confirmación.\n//\n// Se abre como diálogo de contenido (ContentDialog): sube desde abajo y ocupa la pantalla debajo de la barra superior. Una\n// tabla con su historia se lee mejor a todo lo ancho que en un recuadro al centro, también en el teléfono.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaAuditTrailDialog con defaultProps y\n// styleOverrides de sus partes: description.\n//\n// Genérico del paquete: function (regla de la casa).\nimport Typography from '@mui/material/Typography';\n\nimport AuditTrailTable from '@/DataTable/AuditTrailTable.jsx';\nimport ContentDialog from '@/ui/layout/ContentDialog.jsx';\nimport {piecePart, usePieceProps} from '@/theme/themePieces.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaAuditTrailDialog)\nconst THEME_NAME = 'CalcitaAuditTrailDialog';\nconst AuditDescription = piecePart(THEME_NAME, 'Description', Typography);\n\n/**\n * @param {object} props\n * @param {boolean} props.open\n * @param {() => void} props.onClose\n * @param {string} props.title ya traducido («Historial de Q3 · ago 2026»)\n * @param {import('react').ReactNode} [props.subtitle]\n * @param {string} props.subjectKey\n * @param {(tableRequest: object, signal: AbortSignal) => Promise<{rows: Array, total: number, facets?: object}>} props.fetchPage\n * @param {Record<string, object>} props.eventDictionary\n * @param {Record<string, object>} [props.resultDictionary]\n * @param {Array<object>} [props.extraColumns]\n * @param {import('react').ReactNode} [props.header] va entre el subtítulo y la tabla (p. ej. pestañas que cambian lo que se\n * consulta)\n * @param {(row: any) => string|null} [props.eventSuffixOf] complemento del evento de un renglón (AuditTrailTable)\n */\nexport default function AuditTrailDialog(props) {\n // lo que el proyecto puso en su tema (components.CalcitaAuditTrailDialog.defaultProps) y encima lo que llega aquí\n const {\n open, onClose, title, subtitle, subjectKey, fetchPage, eventDictionary, resultDictionary, extraColumns = [], header = null, eventSuffixOf = null,\n ...unknownProps\n} = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n return (\n <ContentDialog open={open} onClose={onClose} title={title}>\n {subtitle && (\n <AuditDescription variant=\"body2\" color=\"text.secondary\" ownSx={{mb: 2}}>{subtitle}</AuditDescription>\n )}\n {header}\n <AuditTrailTable\n subjectKey={open ? subjectKey : ''}\n fetchPage={fetchPage}\n eventDictionary={eventDictionary}\n resultDictionary={resultDictionary}\n extraColumns={extraColumns}\n eventSuffixOf={eventSuffixOf}\n ariaLabel={title}\n maxGridHeight={null}\n />\n </ContentDialog>\n );\n}\n"],"names":[],"mappings":";;;;;;;AAoBA,MAAM,aAAa;AACnB,MAAM,mBAAmB,UAAU,YAAY,eAAe,UAAU;AAiBxE,SAAwB,iBAAiB,OAAO;AAE5C,QAAM;AAAA,IACN;AAAA,IAAM;AAAA,IAAS;AAAA,IAAO;AAAA,IAAU;AAAA,IAAY;AAAA,IAAW;AAAA,IAAiB;AAAA,IAAkB,eAAe,CAAA;AAAA,IAAI,SAAS;AAAA,IAAM,gBAAgB;AAAA,IAC5I,GAAG;AAAA,EAAA,IACH,cAAc,OAAO,UAAU;AAC/B,mBAAiB,YAAY,YAAY;AACzC,SACI,qBAAC,eAAA,EAAc,MAAY,SAAkB,OACxC,UAAA;AAAA,IAAA,YACG,oBAAC,kBAAA,EAAiB,SAAQ,SAAQ,OAAM,kBAAiB,OAAO,EAAC,IAAI,EAAA,GAAK,UAAA,SAAA,CAAS;AAAA,IAEtF;AAAA,IACD;AAAA,MAAC;AAAA,MAAA;AAAA,QACG,YAAY,OAAO,aAAa;AAAA,QAChC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,WAAW;AAAA,QACX,eAAe;AAAA,MAAA;AAAA,IAAA;AAAA,EACnB,GACJ;AAER;"}
1
+ {"version":3,"file":"AuditTrailDialog.js","sources":["../../src/DataTable/AuditTrailDialog.jsx"],"sourcesContent":["// Diálogo de auditoría de un renglón: su historia de eventos, paginada y filtrada EN EL SERVIDOR (AuditTrailTable).\n//\n// Sirve para el historial de cualquier registro (un pedido, una cuenta).\n// Solo abre una vista: no ejecuta nada y por eso no lleva confirmación.\n//\n// Se abre como diálogo de contenido (ContentDialog): sube desde abajo y ocupa la pantalla debajo de la barra superior. Una\n// tabla con su historia se lee mejor a todo lo ancho que en un recuadro al centro, también en el teléfono.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaAuditTrailDialog con defaultProps y\n// styleOverrides de sus partes: description.\n//\n// Genérico del paquete: function (regla de la casa).\nimport Typography from '@mui/material/Typography';\n\nimport AuditTrailTable from '@/DataTable/AuditTrailTable.jsx';\nimport ContentDialog from '@/ui/layout/ContentDialog.jsx';\nimport {piecePart, usePieceProps, withPieceSlots} from '@/theme/themePieces.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaAuditTrailDialog)\nconst THEME_NAME = 'CalcitaAuditTrailDialog';\nconst AuditDescription = piecePart(THEME_NAME, 'Description', Typography);\n\n/**\n * @param {object} props\n * @param {boolean} props.open\n * @param {() => void} props.onClose\n * @param {string} props.title ya traducido («Historial de Q3 · ago 2026»)\n * @param {import('react').ReactNode} [props.subtitle]\n * @param {string} props.subjectKey\n * @param {(tableRequest: object, signal: AbortSignal) => Promise<{rows: Array, total: number, facets?: object}>} props.fetchPage\n * @param {Record<string, object>} props.eventDictionary\n * @param {Record<string, object>} [props.resultDictionary]\n * @param {Array<object>} [props.extraColumns]\n * @param {import('react').ReactNode} [props.header] va entre el subtítulo y la tabla (p. ej. pestañas que cambian lo que se\n * consulta)\n * @param {(row: any) => string|null} [props.eventSuffixOf] complemento del evento de un renglón (AuditTrailTable)\n */\nfunction AuditTrailDialogContent(props) {\n // lo que el proyecto puso en su tema (components.CalcitaAuditTrailDialog.defaultProps) y encima lo que llega aquí\n const {\n open, onClose, title, subtitle, subjectKey, fetchPage, eventDictionary, resultDictionary, extraColumns = [], header = null, eventSuffixOf = null,\n ...unknownProps\n} = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n return (\n <ContentDialog open={open} onClose={onClose} title={title}>\n {subtitle && (\n <AuditDescription variant=\"body2\" color=\"text.secondary\" ownSx={{mb: 2}}>{subtitle}</AuditDescription>\n )}\n {header}\n <AuditTrailTable\n subjectKey={open ? subjectKey : ''}\n fetchPage={fetchPage}\n eventDictionary={eventDictionary}\n resultDictionary={resultDictionary}\n extraColumns={extraColumns}\n eventSuffixOf={eventSuffixOf}\n ariaLabel={title}\n maxGridHeight={null}\n />\n </ContentDialog>\n );\n}\n\n// lo que se exporta: la pieza con slots y slotProps para cambiar sus partes (themePieces.js)\nconst AuditTrailDialog = withPieceSlots(AuditTrailDialogContent, THEME_NAME);\nexport default AuditTrailDialog;\n"],"names":[],"mappings":";;;;;;;AAoBA,MAAM,aAAa;AACnB,MAAM,mBAAmB,UAAU,YAAY,eAAe,UAAU;AAiBxE,SAAS,wBAAwB,OAAO;AAEpC,QAAM;AAAA,IACN;AAAA,IAAM;AAAA,IAAS;AAAA,IAAO;AAAA,IAAU;AAAA,IAAY;AAAA,IAAW;AAAA,IAAiB;AAAA,IAAkB,eAAe,CAAA;AAAA,IAAI,SAAS;AAAA,IAAM,gBAAgB;AAAA,IAC5I,GAAG;AAAA,EAAA,IACH,cAAc,OAAO,UAAU;AAC/B,mBAAiB,YAAY,YAAY;AACzC,SACI,qBAAC,eAAA,EAAc,MAAY,SAAkB,OACxC,UAAA;AAAA,IAAA,YACG,oBAAC,kBAAA,EAAiB,SAAQ,SAAQ,OAAM,kBAAiB,OAAO,EAAC,IAAI,EAAA,GAAK,UAAA,SAAA,CAAS;AAAA,IAEtF;AAAA,IACD;AAAA,MAAC;AAAA,MAAA;AAAA,QACG,YAAY,OAAO,aAAa;AAAA,QAChC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,WAAW;AAAA,QACX,eAAe;AAAA,MAAA;AAAA,IAAA;AAAA,EACnB,GACJ;AAER;AAGA,MAAM,mBAAmB,eAAe,yBAAyB,UAAU;"}
@@ -11,13 +11,13 @@ import Stack from "@mui/material/Stack";
11
11
  import Tooltip from "@mui/material/Tooltip";
12
12
  import FilterAltIcon from "@mui/icons-material/FilterAlt";
13
13
  import FilterAltOutlinedIcon from "@mui/icons-material/FilterAltOutlined";
14
- import { piecePart, usePieceProps } from "../../theme/themePieces.js";
14
+ import { piecePart, withPieceSlots, usePieceProps } from "../../theme/themePieces.js";
15
15
  import { warnUnknownProps } from "../../toolkit/unknownProps.js";
16
16
  const THEME_NAME = "CalcitaHeaderFilter";
17
17
  const FilterButton = piecePart(THEME_NAME, "Button", IconButton);
18
18
  const FilterBadge = piecePart(THEME_NAME, "Badge", Badge);
19
19
  const FilterPanel = piecePart(THEME_NAME, "Panel", Box);
20
- function HeaderFilterPopover(props) {
20
+ function HeaderFilterPopoverContent(props) {
21
21
  const { activeCount = 0, onClear, children, ...unknownProps } = usePieceProps(props, THEME_NAME);
22
22
  warnUnknownProps(THEME_NAME, unknownProps);
23
23
  const { t } = useToolkitTranslation("table");
@@ -73,6 +73,7 @@ function HeaderFilterPopover(props) {
73
73
  )
74
74
  ] });
75
75
  }
76
+ const HeaderFilterPopover = withPieceSlots(HeaderFilterPopoverContent, THEME_NAME);
76
77
  export {
77
78
  HeaderFilterPopover as default
78
79
  };
@@ -1 +1 @@
1
- {"version":3,"file":"HeaderFilterPopover.js","sources":["../../../src/DataTable/ColumnFilters/HeaderFilterPopover.jsx"],"sourcesContent":["// El icono de filtro del encabezado, con su globo.\n//\n// El badge trae el NÚMERO de valores activos en esa columna, no un punto: así se sabe sin abrir nada si se filtró por\n// uno o por seis.\n//\n// OJO: stopPropagation en el clic no es adorno. El título del encabezado ordena, y sin eso abrir el filtro también\n// cambiaría el orden.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaHeaderFilter con defaultProps y\n// styleOverrides de sus partes: button, badge, panel.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useState} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport Badge from '@mui/material/Badge';\nimport Box from '@mui/material/Box';\nimport Button from '@mui/material/Button';\nimport IconButton from '@mui/material/IconButton';\nimport Popover from '@mui/material/Popover';\nimport Stack from '@mui/material/Stack';\nimport Tooltip from '@mui/material/Tooltip';\nimport FilterAltIcon from '@mui/icons-material/FilterAlt';\nimport FilterAltOutlinedIcon from '@mui/icons-material/FilterAltOutlined';\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.CalcitaHeaderFilter)\nconst THEME_NAME = 'CalcitaHeaderFilter';\nconst FilterButton = piecePart(THEME_NAME, 'Button', IconButton);\nconst FilterBadge = piecePart(THEME_NAME, 'Badge', Badge);\nconst FilterPanel = piecePart(THEME_NAME, 'Panel', Box);\n\n/**\n * @param {{activeCount?: number, onClear: () => void, children: import('react').ReactNode}} props children = el filtro\n */\nexport default function HeaderFilterPopover(props) {\n // lo que el proyecto puso en su tema (components.CalcitaHeaderFilter.defaultProps) y encima lo que llega aquí\n const {activeCount = 0, onClear, children, ...unknownProps} = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const {t} = useToolkitTranslation('table');\n const [anchorElement, setAnchorElement] = useState(null);\n const isActive = activeCount > 0;\n const tooltip = isActive ? t('columnFilter.activeTooltip', {count: activeCount}) : t('columnFilter.tooltip');\n\n return (\n <>\n <Tooltip title={tooltip}>\n <FilterButton size=\"small\" aria-label={tooltip}\n ownSx={{color: isActive ? 'primary.main' : 'action.active', opacity: isActive ? 1 : 0.45, overflow: 'visible'}}\n onClick={(event) => {\n event.stopPropagation();\n setAnchorElement(event.currentTarget);\n }}>\n {/* sin filtros NO se monta el Badge: MUI lo esconde con scale(0) y deja un artefacto en la esquina */}\n {isActive ? (\n <FilterBadge badgeContent={activeCount} color=\"primary\" overlap=\"circular\"\n ownSx={{'& .MuiBadge-badge': {fontSize: 10, fontWeight: 700, minWidth: 16, height: 16}}}>\n <FilterAltIcon fontSize=\"inherit\" />\n </FilterBadge>\n ) : <FilterAltOutlinedIcon fontSize=\"inherit\" />}\n </FilterButton>\n </Tooltip>\n\n <Popover open={Boolean(anchorElement)} anchorEl={anchorElement} onClose={() => setAnchorElement(null)}\n anchorOrigin={{vertical: 'bottom', horizontal: 'left'}}>\n <FilterPanel ownSx={{p: 2, minWidth: 280}}>\n <Stack spacing={1}>\n {children}\n <Button size=\"small\" disabled={!isActive}\n onClick={() => {\n onClear();\n setAnchorElement(null);\n }}>\n {t('columnFilter.clear')}\n </Button>\n </Stack>\n </FilterPanel>\n </Popover>\n </>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;AA2BA,MAAM,aAAa;AACnB,MAAM,eAAe,UAAU,YAAY,UAAU,UAAU;AAC/D,MAAM,cAAc,UAAU,YAAY,SAAS,KAAK;AACxD,MAAM,cAAc,UAAU,YAAY,SAAS,GAAG;AAKtD,SAAwB,oBAAoB,OAAO;AAE/C,QAAM,EAAC,cAAc,GAAG,SAAS,UAAU,GAAG,iBAAgB,cAAc,OAAO,UAAU;AAC7F,mBAAiB,YAAY,YAAY;AACzC,QAAM,EAAC,EAAA,IAAK,sBAAsB,OAAO;AACzC,QAAM,CAAC,eAAe,gBAAgB,IAAI,SAAS,IAAI;AACvD,QAAM,WAAW,cAAc;AAC/B,QAAM,UAAU,WAAW,EAAE,8BAA8B,EAAC,OAAO,YAAA,CAAY,IAAI,EAAE,sBAAsB;AAE3G,SACI,qBAAA,UAAA,EACI,UAAA;AAAA,IAAA,oBAAC,SAAA,EAAQ,OAAO,SACZ,UAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QAAa,MAAK;AAAA,QAAQ,cAAY;AAAA,QAC3B,OAAO,EAAC,OAAO,WAAW,iBAAiB,iBAAiB,SAAS,WAAW,IAAI,MAAM,UAAU,UAAA;AAAA,QACpG,SAAS,CAAC,UAAU;AAChB,gBAAM,gBAAA;AACN,2BAAiB,MAAM,aAAa;AAAA,QACxC;AAAA,QAEP,UAAA,WACG;AAAA,UAAC;AAAA,UAAA;AAAA,YAAY,cAAc;AAAA,YAAa,OAAM;AAAA,YAAU,SAAQ;AAAA,YACzD,OAAO,EAAC,qBAAqB,EAAC,UAAU,IAAI,YAAY,KAAK,UAAU,IAAI,QAAQ,GAAA,EAAE;AAAA,YACxF,UAAA,oBAAC,eAAA,EAAc,UAAS,UAAA,CAAU;AAAA,UAAA;AAAA,QAAA,IAEtC,oBAAC,uBAAA,EAAsB,UAAS,UAAA,CAAU;AAAA,MAAA;AAAA,IAAA,GAEtD;AAAA,IAEA;AAAA,MAAC;AAAA,MAAA;AAAA,QAAQ,MAAM,QAAQ,aAAa;AAAA,QAAG,UAAU;AAAA,QAAe,SAAS,MAAM,iBAAiB,IAAI;AAAA,QAC3F,cAAc,EAAC,UAAU,UAAU,YAAY,OAAA;AAAA,QACpD,UAAA,oBAAC,aAAA,EAAY,OAAO,EAAC,GAAG,GAAG,UAAU,IAAA,GACjC,UAAA,qBAAC,OAAA,EAAM,SAAS,GACX,UAAA;AAAA,UAAA;AAAA,UACD;AAAA,YAAC;AAAA,YAAA;AAAA,cAAO,MAAK;AAAA,cAAQ,UAAU,CAAC;AAAA,cACxB,SAAS,MAAM;AACX,wBAAA;AACA,iCAAiB,IAAI;AAAA,cACzB;AAAA,cACH,YAAE,oBAAoB;AAAA,YAAA;AAAA,UAAA;AAAA,QAC3B,EAAA,CACJ,EAAA,CACJ;AAAA,MAAA;AAAA,IAAA;AAAA,EACJ,GACJ;AAER;"}
1
+ {"version":3,"file":"HeaderFilterPopover.js","sources":["../../../src/DataTable/ColumnFilters/HeaderFilterPopover.jsx"],"sourcesContent":["// El icono de filtro del encabezado, con su globo.\n//\n// El badge trae el NÚMERO de valores activos en esa columna, no un punto: así se sabe sin abrir nada si se filtró por\n// uno o por seis.\n//\n// OJO: stopPropagation en el clic no es adorno. El título del encabezado ordena, y sin eso abrir el filtro también\n// cambiaría el orden.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaHeaderFilter con defaultProps y\n// styleOverrides de sus partes: button, badge, panel.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useState} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport Badge from '@mui/material/Badge';\nimport Box from '@mui/material/Box';\nimport Button from '@mui/material/Button';\nimport IconButton from '@mui/material/IconButton';\nimport Popover from '@mui/material/Popover';\nimport Stack from '@mui/material/Stack';\nimport Tooltip from '@mui/material/Tooltip';\nimport FilterAltIcon from '@mui/icons-material/FilterAlt';\nimport FilterAltOutlinedIcon from '@mui/icons-material/FilterAltOutlined';\nimport {piecePart, usePieceProps, withPieceSlots} from '@/theme/themePieces.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaHeaderFilter)\nconst THEME_NAME = 'CalcitaHeaderFilter';\nconst FilterButton = piecePart(THEME_NAME, 'Button', IconButton);\nconst FilterBadge = piecePart(THEME_NAME, 'Badge', Badge);\nconst FilterPanel = piecePart(THEME_NAME, 'Panel', Box);\n\n/**\n * @param {{activeCount?: number, onClear: () => void, children: import('react').ReactNode}} props children = el filtro\n */\nfunction HeaderFilterPopoverContent(props) {\n // lo que el proyecto puso en su tema (components.CalcitaHeaderFilter.defaultProps) y encima lo que llega aquí\n const {activeCount = 0, onClear, children, ...unknownProps} = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const {t} = useToolkitTranslation('table');\n const [anchorElement, setAnchorElement] = useState(null);\n const isActive = activeCount > 0;\n const tooltip = isActive ? t('columnFilter.activeTooltip', {count: activeCount}) : t('columnFilter.tooltip');\n\n return (\n <>\n <Tooltip title={tooltip}>\n <FilterButton size=\"small\" aria-label={tooltip}\n ownSx={{color: isActive ? 'primary.main' : 'action.active', opacity: isActive ? 1 : 0.45, overflow: 'visible'}}\n onClick={(event) => {\n event.stopPropagation();\n setAnchorElement(event.currentTarget);\n }}>\n {/* sin filtros NO se monta el Badge: MUI lo esconde con scale(0) y deja un artefacto en la esquina */}\n {isActive ? (\n <FilterBadge badgeContent={activeCount} color=\"primary\" overlap=\"circular\"\n ownSx={{'& .MuiBadge-badge': {fontSize: 10, fontWeight: 700, minWidth: 16, height: 16}}}>\n <FilterAltIcon fontSize=\"inherit\" />\n </FilterBadge>\n ) : <FilterAltOutlinedIcon fontSize=\"inherit\" />}\n </FilterButton>\n </Tooltip>\n\n <Popover open={Boolean(anchorElement)} anchorEl={anchorElement} onClose={() => setAnchorElement(null)}\n anchorOrigin={{vertical: 'bottom', horizontal: 'left'}}>\n <FilterPanel ownSx={{p: 2, minWidth: 280}}>\n <Stack spacing={1}>\n {children}\n <Button size=\"small\" disabled={!isActive}\n onClick={() => {\n onClear();\n setAnchorElement(null);\n }}>\n {t('columnFilter.clear')}\n </Button>\n </Stack>\n </FilterPanel>\n </Popover>\n </>\n );\n}\n\n// lo que se exporta: la pieza con slots y slotProps para cambiar sus partes (themePieces.js)\nconst HeaderFilterPopover = withPieceSlots(HeaderFilterPopoverContent, THEME_NAME);\nexport default HeaderFilterPopover;\n"],"names":[],"mappings":";;;;;;;;;;;;;;;AA2BA,MAAM,aAAa;AACnB,MAAM,eAAe,UAAU,YAAY,UAAU,UAAU;AAC/D,MAAM,cAAc,UAAU,YAAY,SAAS,KAAK;AACxD,MAAM,cAAc,UAAU,YAAY,SAAS,GAAG;AAKtD,SAAS,2BAA2B,OAAO;AAEvC,QAAM,EAAC,cAAc,GAAG,SAAS,UAAU,GAAG,iBAAgB,cAAc,OAAO,UAAU;AAC7F,mBAAiB,YAAY,YAAY;AACzC,QAAM,EAAC,EAAA,IAAK,sBAAsB,OAAO;AACzC,QAAM,CAAC,eAAe,gBAAgB,IAAI,SAAS,IAAI;AACvD,QAAM,WAAW,cAAc;AAC/B,QAAM,UAAU,WAAW,EAAE,8BAA8B,EAAC,OAAO,YAAA,CAAY,IAAI,EAAE,sBAAsB;AAE3G,SACI,qBAAA,UAAA,EACI,UAAA;AAAA,IAAA,oBAAC,SAAA,EAAQ,OAAO,SACZ,UAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QAAa,MAAK;AAAA,QAAQ,cAAY;AAAA,QAC3B,OAAO,EAAC,OAAO,WAAW,iBAAiB,iBAAiB,SAAS,WAAW,IAAI,MAAM,UAAU,UAAA;AAAA,QACpG,SAAS,CAAC,UAAU;AAChB,gBAAM,gBAAA;AACN,2BAAiB,MAAM,aAAa;AAAA,QACxC;AAAA,QAEP,UAAA,WACG;AAAA,UAAC;AAAA,UAAA;AAAA,YAAY,cAAc;AAAA,YAAa,OAAM;AAAA,YAAU,SAAQ;AAAA,YACzD,OAAO,EAAC,qBAAqB,EAAC,UAAU,IAAI,YAAY,KAAK,UAAU,IAAI,QAAQ,GAAA,EAAE;AAAA,YACxF,UAAA,oBAAC,eAAA,EAAc,UAAS,UAAA,CAAU;AAAA,UAAA;AAAA,QAAA,IAEtC,oBAAC,uBAAA,EAAsB,UAAS,UAAA,CAAU;AAAA,MAAA;AAAA,IAAA,GAEtD;AAAA,IAEA;AAAA,MAAC;AAAA,MAAA;AAAA,QAAQ,MAAM,QAAQ,aAAa;AAAA,QAAG,UAAU;AAAA,QAAe,SAAS,MAAM,iBAAiB,IAAI;AAAA,QAC3F,cAAc,EAAC,UAAU,UAAU,YAAY,OAAA;AAAA,QACpD,UAAA,oBAAC,aAAA,EAAY,OAAO,EAAC,GAAG,GAAG,UAAU,IAAA,GACjC,UAAA,qBAAC,OAAA,EAAM,SAAS,GACX,UAAA;AAAA,UAAA;AAAA,UACD;AAAA,YAAC;AAAA,YAAA;AAAA,cAAO,MAAK;AAAA,cAAQ,UAAU,CAAC;AAAA,cACxB,SAAS,MAAM;AACX,wBAAA;AACA,iCAAiB,IAAI;AAAA,cACzB;AAAA,cACH,YAAE,oBAAoB;AAAA,YAAA;AAAA,UAAA;AAAA,QAC3B,EAAA,CACJ,EAAA,CACJ;AAAA,MAAA;AAAA,IAAA;AAAA,EACJ,GACJ;AAER;AAGA,MAAM,sBAAsB,eAAe,4BAA4B,UAAU;"}
@@ -8,13 +8,13 @@ import Stack from "@mui/material/Stack";
8
8
  import TextField from "@mui/material/TextField";
9
9
  import Typography from "@mui/material/Typography";
10
10
  import { useFormatters } from "../../toolkit/useFormatters.js";
11
- import { piecePart, usePieceProps } from "../../theme/themePieces.js";
11
+ import { piecePart, withPieceSlots, usePieceProps } from "../../theme/themePieces.js";
12
12
  import { warnUnknownProps } from "../../toolkit/unknownProps.js";
13
13
  const THEME_NAME = "CalcitaMultiSelectFilter";
14
14
  const MultiList = piecePart(THEME_NAME, "List", Stack);
15
15
  const MultiOption = piecePart(THEME_NAME, "Option", Stack);
16
16
  const SEARCHABLE_FROM_OPTIONS = 8;
17
- function MultiSelectColumnFilter(props) {
17
+ function MultiSelectColumnFilterContent(props) {
18
18
  const { value = [], onChange, options = [], renderOption, ...unknownProps } = usePieceProps(props, THEME_NAME);
19
19
  warnUnknownProps(THEME_NAME, unknownProps);
20
20
  const { t } = useToolkitTranslation("table");
@@ -52,6 +52,7 @@ function MultiSelectColumnFilter(props) {
52
52
  ] })
53
53
  ] });
54
54
  }
55
+ const MultiSelectColumnFilter = withPieceSlots(MultiSelectColumnFilterContent, THEME_NAME);
55
56
  export {
56
57
  MultiSelectColumnFilter as default
57
58
  };
@@ -1 +1 @@
1
- {"version":3,"file":"MultiSelectColumnFilter.js","sources":["../../../src/DataTable/ColumnFilters/MultiSelectColumnFilter.jsx"],"sourcesContent":["// Filtro de opciones de una columna.\n//\n// Dos cosas lo separan de un multi-select cualquiera:\n// - las opciones NO salen de las filas visibles: salen de las FACETAS que el servidor calcula sobre el resultado\n// COMPLETO (ofrecer solo lo que aparece en las primeras 25 filas convierte el filtro en adorno);\n// - cada opción trae su conteo, que es lo que deja decidir si vale la pena filtrar.\n//\n// Desde ocho opciones aparece una caja para buscar entre ellas.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaMultiSelectFilter con defaultProps y\n// styleOverrides de sus partes: list, option.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useState} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport Checkbox from '@mui/material/Checkbox';\nimport FormControlLabel from '@mui/material/FormControlLabel';\nimport Stack from '@mui/material/Stack';\nimport TextField from '@mui/material/TextField';\nimport Typography from '@mui/material/Typography';\n\nimport {useFormatters} from '@/toolkit/useFormatters.js';\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.CalcitaMultiSelectFilter)\nconst THEME_NAME = 'CalcitaMultiSelectFilter';\nconst MultiList = piecePart(THEME_NAME, 'List', Stack);\nconst MultiOption = piecePart(THEME_NAME, 'Option', Stack);\n\nconst SEARCHABLE_FROM_OPTIONS = 8;\n\n/**\n * @param {object} props\n * @param {string[]} [props.value]\n * @param {(value: string[]) => void} props.onChange\n * @param {Array<{value: string, total: number}>} [props.options] faceta del servidor\n * @param {(value: string) => import('react').ReactNode} [props.renderOption] cómo se lee el valor (diccionario)\n */\nexport default function MultiSelectColumnFilter(props) {\n // lo que el proyecto puso en su tema (components.CalcitaMultiSelectFilter.defaultProps) y encima lo que llega aquí\n const {value = [], onChange, options = [], renderOption, ...unknownProps} = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const {t} = useToolkitTranslation('table');\n const {formatNumber} = useFormatters();\n const [searchText, setSearchText] = useState('');\n\n const toggleOption = (optionValue) => onChange(value.includes(optionValue)\n ? value.filter((currentValue) => currentValue !== optionValue)\n : [...value, optionValue]);\n\n const isSearchable = options.length >= SEARCHABLE_FROM_OPTIONS;\n const visibleOptions = isSearchable && searchText\n ? options.filter((option) => String(option.value).toLowerCase().includes(searchText.toLowerCase()))\n : options;\n\n return (\n <Stack spacing={1}>\n {isSearchable && (\n <TextField size=\"small\" autoFocus fullWidth placeholder={t('options.searchPlaceholder')} value={searchText}\n onChange={(event) => setSearchText(event.target.value)} />\n )}\n <MultiList ownSx={{maxHeight: 300, overflowY: 'auto'}}>\n {options.length === 0 && (\n <Typography variant=\"caption\" color=\"text.secondary\">{t('options.empty')}</Typography>\n )}\n {options.length > 0 && visibleOptions.length === 0 && (\n <Typography variant=\"caption\" color=\"text.secondary\">{t('options.noMatch', {text: searchText})}</Typography>\n )}\n {visibleOptions.map((option) => (\n <FormControlLabel\n key={option.value}\n control={<Checkbox size=\"small\" checked={value.includes(option.value)} onChange={() => toggleOption(option.value)} />}\n label={(\n <MultiOption direction=\"row\" spacing={0.75} ownSx={{alignItems: 'center'}}>\n {renderOption ? renderOption(option.value) : <Typography variant=\"body2\">{option.value}</Typography>}\n <Typography variant=\"caption\" color=\"text.secondary\">{formatNumber(option.total)}</Typography>\n </MultiOption>\n )}\n />\n ))}\n </MultiList>\n </Stack>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;;AA0BA,MAAM,aAAa;AACnB,MAAM,YAAY,UAAU,YAAY,QAAQ,KAAK;AACrD,MAAM,cAAc,UAAU,YAAY,UAAU,KAAK;AAEzD,MAAM,0BAA0B;AAShC,SAAwB,wBAAwB,OAAO;AAEnD,QAAM,EAAC,QAAQ,IAAI,UAAU,UAAU,CAAA,GAAI,cAAc,GAAG,aAAA,IAAgB,cAAc,OAAO,UAAU;AAC3G,mBAAiB,YAAY,YAAY;AACzC,QAAM,EAAC,EAAA,IAAK,sBAAsB,OAAO;AACzC,QAAM,EAAC,aAAA,IAAgB,cAAA;AACvB,QAAM,CAAC,YAAY,aAAa,IAAI,SAAS,EAAE;AAE/C,QAAM,eAAe,CAAC,gBAAgB,SAAS,MAAM,SAAS,WAAW,IACnE,MAAM,OAAO,CAAC,iBAAiB,iBAAiB,WAAW,IAC3D,CAAC,GAAG,OAAO,WAAW,CAAC;AAE7B,QAAM,eAAe,QAAQ,UAAU;AACvC,QAAM,iBAAiB,gBAAgB,aACjC,QAAQ,OAAO,CAAC,WAAW,OAAO,OAAO,KAAK,EAAE,cAAc,SAAS,WAAW,YAAA,CAAa,CAAC,IAChG;AAEN,SACI,qBAAC,OAAA,EAAM,SAAS,GACX,UAAA;AAAA,IAAA,gBACG;AAAA,MAAC;AAAA,MAAA;AAAA,QAAU,MAAK;AAAA,QAAQ,WAAS;AAAA,QAAC,WAAS;AAAA,QAAC,aAAa,EAAE,2BAA2B;AAAA,QAAG,OAAO;AAAA,QACrF,UAAU,CAAC,UAAU,cAAc,MAAM,OAAO,KAAK;AAAA,MAAA;AAAA,IAAA;AAAA,IAEpE,qBAAC,aAAU,OAAO,EAAC,WAAW,KAAK,WAAW,UACzC,UAAA;AAAA,MAAA,QAAQ,WAAW,KAChB,oBAAC,YAAA,EAAW,SAAQ,WAAU,OAAM,kBAAkB,UAAA,EAAE,eAAe,EAAA,CAAE;AAAA,MAE5E,QAAQ,SAAS,KAAK,eAAe,WAAW,yBAC5C,YAAA,EAAW,SAAQ,WAAU,OAAM,kBAAkB,UAAA,EAAE,mBAAmB,EAAC,MAAM,WAAA,CAAW,GAAE;AAAA,MAElG,eAAe,IAAI,CAAC,WACjB;AAAA,QAAC;AAAA,QAAA;AAAA,UAEG,SAAS,oBAAC,UAAA,EAAS,MAAK,SAAQ,SAAS,MAAM,SAAS,OAAO,KAAK,GAAG,UAAU,MAAM,aAAa,OAAO,KAAK,GAAG;AAAA,UACnH,OACI,qBAAC,aAAA,EAAY,WAAU,OAAM,SAAS,MAAM,OAAO,EAAC,YAAY,SAAA,GAC3D,UAAA;AAAA,YAAA,eAAe,aAAa,OAAO,KAAK,wBAAK,YAAA,EAAW,SAAQ,SAAS,UAAA,OAAO,MAAA,CAAM;AAAA,YACvF,oBAAC,cAAW,SAAQ,WAAU,OAAM,kBAAkB,UAAA,aAAa,OAAO,KAAK,EAAA,CAAE;AAAA,UAAA,EAAA,CACrF;AAAA,QAAA;AAAA,QANC,OAAO;AAAA,MAAA,CASnB;AAAA,IAAA,EAAA,CACL;AAAA,EAAA,GACJ;AAER;"}
1
+ {"version":3,"file":"MultiSelectColumnFilter.js","sources":["../../../src/DataTable/ColumnFilters/MultiSelectColumnFilter.jsx"],"sourcesContent":["// Filtro de opciones de una columna.\n//\n// Dos cosas lo separan de un multi-select cualquiera:\n// - las opciones NO salen de las filas visibles: salen de las FACETAS que el servidor calcula sobre el resultado\n// COMPLETO (ofrecer solo lo que aparece en las primeras 25 filas convierte el filtro en adorno);\n// - cada opción trae su conteo, que es lo que deja decidir si vale la pena filtrar.\n//\n// Desde ocho opciones aparece una caja para buscar entre ellas.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaMultiSelectFilter con defaultProps y\n// styleOverrides de sus partes: list, option.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useState} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport Checkbox from '@mui/material/Checkbox';\nimport FormControlLabel from '@mui/material/FormControlLabel';\nimport Stack from '@mui/material/Stack';\nimport TextField from '@mui/material/TextField';\nimport Typography from '@mui/material/Typography';\n\nimport {useFormatters} from '@/toolkit/useFormatters.js';\nimport {piecePart, usePieceProps, withPieceSlots} from '@/theme/themePieces.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaMultiSelectFilter)\nconst THEME_NAME = 'CalcitaMultiSelectFilter';\nconst MultiList = piecePart(THEME_NAME, 'List', Stack);\nconst MultiOption = piecePart(THEME_NAME, 'Option', Stack);\n\nconst SEARCHABLE_FROM_OPTIONS = 8;\n\n/**\n * @param {object} props\n * @param {string[]} [props.value]\n * @param {(value: string[]) => void} props.onChange\n * @param {Array<{value: string, total: number}>} [props.options] faceta del servidor\n * @param {(value: string) => import('react').ReactNode} [props.renderOption] cómo se lee el valor (diccionario)\n */\nfunction MultiSelectColumnFilterContent(props) {\n // lo que el proyecto puso en su tema (components.CalcitaMultiSelectFilter.defaultProps) y encima lo que llega aquí\n const {value = [], onChange, options = [], renderOption, ...unknownProps} = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const {t} = useToolkitTranslation('table');\n const {formatNumber} = useFormatters();\n const [searchText, setSearchText] = useState('');\n\n const toggleOption = (optionValue) => onChange(value.includes(optionValue)\n ? value.filter((currentValue) => currentValue !== optionValue)\n : [...value, optionValue]);\n\n const isSearchable = options.length >= SEARCHABLE_FROM_OPTIONS;\n const visibleOptions = isSearchable && searchText\n ? options.filter((option) => String(option.value).toLowerCase().includes(searchText.toLowerCase()))\n : options;\n\n return (\n <Stack spacing={1}>\n {isSearchable && (\n <TextField size=\"small\" autoFocus fullWidth placeholder={t('options.searchPlaceholder')} value={searchText}\n onChange={(event) => setSearchText(event.target.value)} />\n )}\n <MultiList ownSx={{maxHeight: 300, overflowY: 'auto'}}>\n {options.length === 0 && (\n <Typography variant=\"caption\" color=\"text.secondary\">{t('options.empty')}</Typography>\n )}\n {options.length > 0 && visibleOptions.length === 0 && (\n <Typography variant=\"caption\" color=\"text.secondary\">{t('options.noMatch', {text: searchText})}</Typography>\n )}\n {visibleOptions.map((option) => (\n <FormControlLabel\n key={option.value}\n control={<Checkbox size=\"small\" checked={value.includes(option.value)} onChange={() => toggleOption(option.value)} />}\n label={(\n <MultiOption direction=\"row\" spacing={0.75} ownSx={{alignItems: 'center'}}>\n {renderOption ? renderOption(option.value) : <Typography variant=\"body2\">{option.value}</Typography>}\n <Typography variant=\"caption\" color=\"text.secondary\">{formatNumber(option.total)}</Typography>\n </MultiOption>\n )}\n />\n ))}\n </MultiList>\n </Stack>\n );\n}\n\n// lo que se exporta: la pieza con slots y slotProps para cambiar sus partes (themePieces.js)\nconst MultiSelectColumnFilter = withPieceSlots(MultiSelectColumnFilterContent, THEME_NAME);\nexport default MultiSelectColumnFilter;\n"],"names":[],"mappings":";;;;;;;;;;;;AA0BA,MAAM,aAAa;AACnB,MAAM,YAAY,UAAU,YAAY,QAAQ,KAAK;AACrD,MAAM,cAAc,UAAU,YAAY,UAAU,KAAK;AAEzD,MAAM,0BAA0B;AAShC,SAAS,+BAA+B,OAAO;AAE3C,QAAM,EAAC,QAAQ,IAAI,UAAU,UAAU,CAAA,GAAI,cAAc,GAAG,aAAA,IAAgB,cAAc,OAAO,UAAU;AAC3G,mBAAiB,YAAY,YAAY;AACzC,QAAM,EAAC,EAAA,IAAK,sBAAsB,OAAO;AACzC,QAAM,EAAC,aAAA,IAAgB,cAAA;AACvB,QAAM,CAAC,YAAY,aAAa,IAAI,SAAS,EAAE;AAE/C,QAAM,eAAe,CAAC,gBAAgB,SAAS,MAAM,SAAS,WAAW,IACnE,MAAM,OAAO,CAAC,iBAAiB,iBAAiB,WAAW,IAC3D,CAAC,GAAG,OAAO,WAAW,CAAC;AAE7B,QAAM,eAAe,QAAQ,UAAU;AACvC,QAAM,iBAAiB,gBAAgB,aACjC,QAAQ,OAAO,CAAC,WAAW,OAAO,OAAO,KAAK,EAAE,cAAc,SAAS,WAAW,YAAA,CAAa,CAAC,IAChG;AAEN,SACI,qBAAC,OAAA,EAAM,SAAS,GACX,UAAA;AAAA,IAAA,gBACG;AAAA,MAAC;AAAA,MAAA;AAAA,QAAU,MAAK;AAAA,QAAQ,WAAS;AAAA,QAAC,WAAS;AAAA,QAAC,aAAa,EAAE,2BAA2B;AAAA,QAAG,OAAO;AAAA,QACrF,UAAU,CAAC,UAAU,cAAc,MAAM,OAAO,KAAK;AAAA,MAAA;AAAA,IAAA;AAAA,IAEpE,qBAAC,aAAU,OAAO,EAAC,WAAW,KAAK,WAAW,UACzC,UAAA;AAAA,MAAA,QAAQ,WAAW,KAChB,oBAAC,YAAA,EAAW,SAAQ,WAAU,OAAM,kBAAkB,UAAA,EAAE,eAAe,EAAA,CAAE;AAAA,MAE5E,QAAQ,SAAS,KAAK,eAAe,WAAW,yBAC5C,YAAA,EAAW,SAAQ,WAAU,OAAM,kBAAkB,UAAA,EAAE,mBAAmB,EAAC,MAAM,WAAA,CAAW,GAAE;AAAA,MAElG,eAAe,IAAI,CAAC,WACjB;AAAA,QAAC;AAAA,QAAA;AAAA,UAEG,SAAS,oBAAC,UAAA,EAAS,MAAK,SAAQ,SAAS,MAAM,SAAS,OAAO,KAAK,GAAG,UAAU,MAAM,aAAa,OAAO,KAAK,GAAG;AAAA,UACnH,OACI,qBAAC,aAAA,EAAY,WAAU,OAAM,SAAS,MAAM,OAAO,EAAC,YAAY,SAAA,GAC3D,UAAA;AAAA,YAAA,eAAe,aAAa,OAAO,KAAK,wBAAK,YAAA,EAAW,SAAQ,SAAS,UAAA,OAAO,MAAA,CAAM;AAAA,YACvF,oBAAC,cAAW,SAAQ,WAAU,OAAM,kBAAkB,UAAA,aAAa,OAAO,KAAK,EAAA,CAAE;AAAA,UAAA,EAAA,CACrF;AAAA,QAAA;AAAA,QANC,OAAO;AAAA,MAAA,CASnB;AAAA,IAAA,EAAA,CACL;AAAA,EAAA,GACJ;AAER;AAGA,MAAM,0BAA0B,eAAe,gCAAgC,UAAU;"}
@@ -7,7 +7,7 @@ import Stack from "@mui/material/Stack";
7
7
  import Tooltip from "@mui/material/Tooltip";
8
8
  import Typography from "@mui/material/Typography";
9
9
  import HeaderFilterPopover from "./HeaderFilterPopover.js";
10
- import { piecePart, usePieceProps } from "../../theme/themePieces.js";
10
+ import { piecePart, withPieceSlots, usePieceProps } from "../../theme/themePieces.js";
11
11
  import { warnUnknownProps } from "../../toolkit/unknownProps.js";
12
12
  const THEME_NAME = "CalcitaSortableFilterHeader";
13
13
  const SortableTitle = piecePart(THEME_NAME, "Title", Typography);
@@ -15,7 +15,7 @@ const SortableRoot = piecePart(THEME_NAME, "Root", Stack);
15
15
  const SortableSortButton = piecePart(THEME_NAME, "SortButton", ButtonBase);
16
16
  const SortableSortIndicator = piecePart(THEME_NAME, "SortIndicator", Box);
17
17
  const SortableClearButton = piecePart(THEME_NAME, "ClearButton", ButtonBase);
18
- function SortableFilterHeader(props) {
18
+ function SortableFilterHeaderContent(props) {
19
19
  const {
20
20
  label,
21
21
  sortable = true,
@@ -59,6 +59,7 @@ function SortableFilterHeader(props) {
59
59
  filterable && /* @__PURE__ */ jsx(HeaderFilterPopover, { activeCount, onClear, children })
60
60
  ] });
61
61
  }
62
+ const SortableFilterHeader = withPieceSlots(SortableFilterHeaderContent, THEME_NAME);
62
63
  export {
63
64
  SortableFilterHeader as default
64
65
  };