@stackcraftmx/calcita 0.27.0 → 0.30.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 (298) hide show
  1. package/README.md +149 -0
  2. package/dist/DataTable/AuditTrailDialog.js +18 -14
  3. package/dist/DataTable/AuditTrailDialog.js.map +1 -1
  4. package/dist/DataTable/ColumnFilters/HeaderFilterPopover.js +12 -6
  5. package/dist/DataTable/ColumnFilters/HeaderFilterPopover.js.map +1 -1
  6. package/dist/DataTable/ColumnFilters/MultiSelectColumnFilter.js +8 -3
  7. package/dist/DataTable/ColumnFilters/MultiSelectColumnFilter.js.map +1 -1
  8. package/dist/DataTable/ColumnFilters/SortableFilterHeader.js +26 -18
  9. package/dist/DataTable/ColumnFilters/SortableFilterHeader.js.map +1 -1
  10. package/dist/DataTable/EmptyState.js +8 -3
  11. package/dist/DataTable/EmptyState.js.map +1 -1
  12. package/dist/DataTable/FilterChips.js +11 -5
  13. package/dist/DataTable/FilterChips.js.map +1 -1
  14. package/dist/DataTable/GoToPage.js +9 -4
  15. package/dist/DataTable/GoToPage.js.map +1 -1
  16. package/dist/DataTable/ServerTable.js +74 -61
  17. package/dist/DataTable/ServerTable.js.map +1 -1
  18. package/dist/DataTable/columns.js +1 -8
  19. package/dist/DataTable/columns.js.map +1 -1
  20. package/dist/access/PermissionMatrix.js +18 -9
  21. package/dist/access/PermissionMatrix.js.map +1 -1
  22. package/dist/account/AccountSecuritySection.js +8 -3
  23. package/dist/account/AccountSecuritySection.js.map +1 -1
  24. package/dist/account/DemoTotpCodeButton.js +8 -3
  25. package/dist/account/DemoTotpCodeButton.js.map +1 -1
  26. package/dist/account/ProfileScreen.js +19 -13
  27. package/dist/account/ProfileScreen.js.map +1 -1
  28. package/dist/account/SignInScreen.js +6 -2
  29. package/dist/account/SignInScreen.js.map +1 -1
  30. package/dist/appShell/AccountMenu.js +13 -5
  31. package/dist/appShell/AccountMenu.js.map +1 -1
  32. package/dist/appShell/AppLayout.js +100 -71
  33. package/dist/appShell/AppLayout.js.map +1 -1
  34. package/dist/appShell/AppearanceSettings.js +21 -13
  35. package/dist/appShell/AppearanceSettings.js.map +1 -1
  36. package/dist/appShell/AuthLayout.js +26 -13
  37. package/dist/appShell/AuthLayout.js.map +1 -1
  38. package/dist/appShell/BarActions.js +10 -5
  39. package/dist/appShell/BarActions.js.map +1 -1
  40. package/dist/appShell/BarCapsule.js +9 -4
  41. package/dist/appShell/BarCapsule.js.map +1 -1
  42. package/dist/appShell/BarSearch.js +7 -3
  43. package/dist/appShell/BarSearch.js.map +1 -1
  44. package/dist/appShell/HeaderTools.js +20 -15
  45. package/dist/appShell/HeaderTools.js.map +1 -1
  46. package/dist/appShell/MobileTabBar.js +25 -14
  47. package/dist/appShell/MobileTabBar.js.map +1 -1
  48. package/dist/appShell/SettingsMenu.js +7 -3
  49. package/dist/appShell/SettingsMenu.js.map +1 -1
  50. package/dist/appShell/SideMenu.js +53 -35
  51. package/dist/appShell/SideMenu.js.map +1 -1
  52. package/dist/appShell/SideMenuEntry.js +18 -9
  53. package/dist/appShell/SideMenuEntry.js.map +1 -1
  54. package/dist/appShell/SubmenuActionsMenu.js +6 -2
  55. package/dist/appShell/SubmenuActionsMenu.js.map +1 -1
  56. package/dist/appShell/TabBarEditor.js +16 -7
  57. package/dist/appShell/TabBarEditor.js.map +1 -1
  58. package/dist/appShell/TopMenu.js +12 -7
  59. package/dist/appShell/TopMenu.js.map +1 -1
  60. package/dist/appShell/Wordmark.js +6 -2
  61. package/dist/appShell/Wordmark.js.map +1 -1
  62. package/dist/auth/PasswordChecklist.js +14 -7
  63. package/dist/auth/PasswordChecklist.js.map +1 -1
  64. package/dist/calendar/EventCalendar.js +26 -19
  65. package/dist/calendar/EventCalendar.js.map +1 -1
  66. package/dist/calendar/EventDetails.js +28 -13
  67. package/dist/calendar/EventDetails.js.map +1 -1
  68. package/dist/downloads/DownloadManager.js +34 -18
  69. package/dist/downloads/DownloadManager.js.map +1 -1
  70. package/dist/feedback/ConfirmDialog.js +22 -17
  71. package/dist/feedback/ConfirmDialog.js.map +1 -1
  72. package/dist/feedback/ModalWaiting.js +12 -5
  73. package/dist/feedback/ModalWaiting.js.map +1 -1
  74. package/dist/feedback/NewBuildNotice.js +16 -8
  75. package/dist/feedback/NewBuildNotice.js.map +1 -1
  76. package/dist/filepond/FileDropzone.js +39 -31
  77. package/dist/filepond/FileDropzone.js.map +1 -1
  78. package/dist/forms/FormProblemAlert.js +4 -1
  79. package/dist/forms/FormProblemAlert.js.map +1 -1
  80. package/dist/forms/auth/SignInForm.js +6 -2
  81. package/dist/forms/auth/SignInForm.js.map +1 -1
  82. package/dist/forms/security/SecondStepForm.js +6 -2
  83. package/dist/forms/security/SecondStepForm.js.map +1 -1
  84. package/dist/forms/security/TwoStepVerificationCard.js +19 -9
  85. package/dist/forms/security/TwoStepVerificationCard.js.map +1 -1
  86. package/dist/hookForm/FormProblemAlert.js +4 -1
  87. package/dist/hookForm/FormProblemAlert.js.map +1 -1
  88. package/dist/inbox/InboxLayout.js +47 -29
  89. package/dist/inbox/InboxLayout.js.map +1 -1
  90. package/dist/inbox/MessageList.js +53 -32
  91. package/dist/inbox/MessageList.js.map +1 -1
  92. package/dist/inbox/MessageView.js +31 -15
  93. package/dist/inbox/MessageView.js.map +1 -1
  94. package/dist/index.js +7 -1
  95. package/dist/index.js.map +1 -1
  96. package/dist/inputs/ColorInput.js +22 -17
  97. package/dist/inputs/ColorInput.js.map +1 -1
  98. package/dist/inputs/FormSection.js +66 -63
  99. package/dist/inputs/FormSection.js.map +1 -1
  100. package/dist/inputs/SelectInput.js +21 -16
  101. package/dist/inputs/SelectInput.js.map +1 -1
  102. package/dist/inputs/TextInput.js +25 -20
  103. package/dist/inputs/TextInput.js.map +1 -1
  104. package/dist/inputs/WizardLayout.js +21 -15
  105. package/dist/inputs/WizardLayout.js.map +1 -1
  106. package/dist/kanban/KanbanBoard.js +82 -50
  107. package/dist/kanban/KanbanBoard.js.map +1 -1
  108. package/dist/maps/MapView.js +19 -14
  109. package/dist/maps/MapView.js.map +1 -1
  110. package/dist/pwa/InstallAppSetting.js +4 -1
  111. package/dist/pwa/InstallAppSetting.js.map +1 -1
  112. package/dist/pwa/InstallInstructionsDialog.js +6 -2
  113. package/dist/pwa/InstallInstructionsDialog.js.map +1 -1
  114. package/dist/pwa/InstallPromptBanner.js +10 -5
  115. package/dist/pwa/InstallPromptBanner.js.map +1 -1
  116. package/dist/security/ActivityLog.js +6 -2
  117. package/dist/security/ActivityLog.js.map +1 -1
  118. package/dist/security/RecoveryCodesDialog.js +8 -3
  119. package/dist/security/RecoveryCodesDialog.js.map +1 -1
  120. package/dist/security/SessionsCard.js +12 -5
  121. package/dist/security/SessionsCard.js.map +1 -1
  122. package/dist/theme/components/glassComponents.js +19 -16
  123. package/dist/theme/components/glassComponents.js.map +1 -1
  124. package/dist/theme/components/minimalComponents.js +10 -6
  125. package/dist/theme/components/minimalComponents.js.map +1 -1
  126. package/dist/theme/createToolkitTheme.js +23 -6
  127. package/dist/theme/createToolkitTheme.js.map +1 -1
  128. package/dist/theme/cssTokens.js +23 -0
  129. package/dist/theme/cssTokens.js.map +1 -0
  130. package/dist/theme/glassSurface.js +64 -10
  131. package/dist/theme/glassSurface.js.map +1 -1
  132. package/dist/theme/glassToasterStyles.js +2 -3
  133. package/dist/theme/glassToasterStyles.js.map +1 -1
  134. package/dist/theme/themePieces.js +16 -0
  135. package/dist/theme/themePieces.js.map +1 -0
  136. package/dist/types/DataTable/AuditTrailDialog.d.ts +1 -1
  137. package/dist/types/DataTable/ColumnFilters/HeaderFilterPopover.d.ts +1 -1
  138. package/dist/types/DataTable/ColumnFilters/MultiSelectColumnFilter.d.ts +1 -1
  139. package/dist/types/DataTable/ColumnFilters/SortableFilterHeader.d.ts +1 -1
  140. package/dist/types/DataTable/EmptyState.d.ts +1 -1
  141. package/dist/types/DataTable/FilterChips.d.ts +1 -1
  142. package/dist/types/DataTable/GoToPage.d.ts +1 -1
  143. package/dist/types/DataTable/ServerTable.d.ts +1 -1
  144. package/dist/types/access/PermissionMatrix.d.ts +1 -1
  145. package/dist/types/account/AccountSecuritySection.d.ts +1 -1
  146. package/dist/types/account/DemoTotpCodeButton.d.ts +1 -1
  147. package/dist/types/account/ProfileScreen.d.ts +1 -1
  148. package/dist/types/account/SignInScreen.d.ts +1 -1
  149. package/dist/types/appShell/AccountMenu.d.ts +1 -1
  150. package/dist/types/appShell/AppLayout.d.ts +1 -1
  151. package/dist/types/appShell/AppearanceSettings.d.ts +1 -1
  152. package/dist/types/appShell/AuthLayout.d.ts +1 -1
  153. package/dist/types/appShell/BarActions.d.ts +1 -1
  154. package/dist/types/appShell/BarCapsule.d.ts +1 -1
  155. package/dist/types/appShell/BarSearch.d.ts +1 -1
  156. package/dist/types/appShell/HeaderTools.d.ts +1 -1
  157. package/dist/types/appShell/MobileTabBar.d.ts +1 -1
  158. package/dist/types/appShell/SettingsMenu.d.ts +1 -1
  159. package/dist/types/appShell/SideMenu.d.ts +1 -1
  160. package/dist/types/appShell/SideMenuEntry.d.ts +1 -1
  161. package/dist/types/appShell/SubmenuActionsMenu.d.ts +1 -1
  162. package/dist/types/appShell/TabBarEditor.d.ts +1 -1
  163. package/dist/types/appShell/TopMenu.d.ts +1 -1
  164. package/dist/types/appShell/Wordmark.d.ts +1 -1
  165. package/dist/types/auth/PasswordChecklist.d.ts +1 -1
  166. package/dist/types/calendar/EventCalendar.d.ts +1 -1
  167. package/dist/types/calendar/EventDetails.d.ts +1 -1
  168. package/dist/types/feedback/ConfirmDialog.d.ts +1 -1
  169. package/dist/types/feedback/ModalWaiting.d.ts +1 -1
  170. package/dist/types/feedback/NewBuildNotice.d.ts +1 -1
  171. package/dist/types/filepond/FileDropzone.d.ts +1 -1
  172. package/dist/types/forms/auth/SignInForm.d.ts +1 -1
  173. package/dist/types/forms/security/SecondStepForm.d.ts +1 -1
  174. package/dist/types/forms/security/TwoStepVerificationCard.d.ts +1 -1
  175. package/dist/types/inbox/InboxLayout.d.ts +1 -1
  176. package/dist/types/inbox/MessageList.d.ts +1 -1
  177. package/dist/types/inbox/MessageView.d.ts +1 -1
  178. package/dist/types/index.d.ts +2 -1
  179. package/dist/types/inputs/ColorInput.d.ts +1 -1
  180. package/dist/types/inputs/FormSection.d.ts +2 -2
  181. package/dist/types/inputs/SelectInput.d.ts +1 -1
  182. package/dist/types/inputs/TextInput.d.ts +1 -1
  183. package/dist/types/inputs/WizardLayout.d.ts +1 -1
  184. package/dist/types/kanban/KanbanBoard.d.ts +1 -1
  185. package/dist/types/maps/MapView.d.ts +1 -1
  186. package/dist/types/pwa/InstallInstructionsDialog.d.ts +1 -1
  187. package/dist/types/pwa/InstallPromptBanner.d.ts +1 -1
  188. package/dist/types/security/ActivityLog.d.ts +1 -1
  189. package/dist/types/security/RecoveryCodesDialog.d.ts +1 -1
  190. package/dist/types/security/SessionsCard.d.ts +1 -1
  191. package/dist/types/theme/createToolkitTheme.d.ts +33 -1
  192. package/dist/types/theme/cssTokens.d.ts +46 -0
  193. package/dist/types/theme/glassSurface.d.ts +57 -1
  194. package/dist/types/theme/themePieces.d.ts +20 -0
  195. package/dist/types/ui/actions/ActionsMenu.d.ts +1 -1
  196. package/dist/types/ui/actions/DownloadMenu.d.ts +1 -1
  197. package/dist/types/ui/actions/SelectionActions.d.ts +1 -1
  198. package/dist/types/ui/filters/DateRangeFilter.d.ts +1 -1
  199. package/dist/types/ui/filters/PeriodRangeSelector.d.ts +1 -1
  200. package/dist/types/ui/filters/QuickFilters.d.ts +1 -1
  201. package/dist/types/ui/filters/SegmentedControl.d.ts +7 -2
  202. package/dist/types/ui/labels/StatusChip.d.ts +1 -1
  203. package/dist/types/ui/labels/Tag.d.ts +1 -1
  204. package/dist/types/ui/layout/CollapsibleSection.d.ts +1 -1
  205. package/dist/types/ui/layout/ContentDialog.d.ts +1 -1
  206. package/dist/types/ui/layout/DefinitionList.d.ts +1 -1
  207. package/dist/types/ui/layout/DetailHeader.d.ts +1 -1
  208. package/dist/types/ui/layout/ErrorPage.d.ts +1 -1
  209. package/dist/types/ui/layout/GlassPanel.d.ts +1 -1
  210. package/dist/types/ui/layout/PageHeader.d.ts +1 -1
  211. package/dist/types/ui/layout/PageHeading.d.ts +1 -1
  212. package/dist/types/ui/layout/ProfileHeader.d.ts +1 -1
  213. package/dist/types/ui/layout/ScrollableRow.d.ts +1 -1
  214. package/dist/types/ui/layout/SectionCard.d.ts +1 -1
  215. package/dist/types/ui/metrics/BarList.d.ts +1 -1
  216. package/dist/types/ui/metrics/CalendarHeatmap.d.ts +1 -1
  217. package/dist/types/ui/metrics/ChartTooltip.d.ts +1 -1
  218. package/dist/types/ui/metrics/ColumnChart.d.ts +1 -1
  219. package/dist/types/ui/metrics/LineChart.d.ts +1 -1
  220. package/dist/types/ui/metrics/MetricCard.d.ts +1 -1
  221. package/dist/types/ui/metrics/TableToggle.d.ts +1 -1
  222. package/dist/types/ui/navigation/ColorSchemeToggle.d.ts +1 -1
  223. package/dist/types/ui/navigation/FontSizeToggle.d.ts +1 -1
  224. package/dist/types/ui/navigation/LanguageToggle.d.ts +1 -1
  225. package/dist/types/ui/navigation/PageBreadcrumbs.d.ts +1 -1
  226. package/dist/types/ui/timeline/GroupedTimeline.d.ts +1 -1
  227. package/dist/types/upload/FileDropzone.d.ts +1 -1
  228. package/dist/ui/actions/ActionsMenu.js +9 -4
  229. package/dist/ui/actions/ActionsMenu.js.map +1 -1
  230. package/dist/ui/actions/DownloadMenu.js +21 -17
  231. package/dist/ui/actions/DownloadMenu.js.map +1 -1
  232. package/dist/ui/actions/SelectionActions.js +15 -7
  233. package/dist/ui/actions/SelectionActions.js.map +1 -1
  234. package/dist/ui/filters/DateRangeFilter.js +11 -5
  235. package/dist/ui/filters/DateRangeFilter.js.map +1 -1
  236. package/dist/ui/filters/PeriodRangeSelector.js +11 -5
  237. package/dist/ui/filters/PeriodRangeSelector.js.map +1 -1
  238. package/dist/ui/filters/QuickFilters.js +16 -8
  239. package/dist/ui/filters/QuickFilters.js.map +1 -1
  240. package/dist/ui/filters/SegmentedControl.js +12 -5
  241. package/dist/ui/filters/SegmentedControl.js.map +1 -1
  242. package/dist/ui/labels/StatusChip.js +35 -25
  243. package/dist/ui/labels/StatusChip.js.map +1 -1
  244. package/dist/ui/labels/Tag.js +8 -3
  245. package/dist/ui/labels/Tag.js.map +1 -1
  246. package/dist/ui/layout/CollapsibleSection.js +18 -8
  247. package/dist/ui/layout/CollapsibleSection.js.map +1 -1
  248. package/dist/ui/layout/ContentDialog.js +13 -6
  249. package/dist/ui/layout/ContentDialog.js.map +1 -1
  250. package/dist/ui/layout/DefinitionList.js +13 -6
  251. package/dist/ui/layout/DefinitionList.js.map +1 -1
  252. package/dist/ui/layout/DetailHeader.js +14 -6
  253. package/dist/ui/layout/DetailHeader.js.map +1 -1
  254. package/dist/ui/layout/ErrorPage.js +20 -15
  255. package/dist/ui/layout/ErrorPage.js.map +1 -1
  256. package/dist/ui/layout/FullPageLoader.js +4 -1
  257. package/dist/ui/layout/FullPageLoader.js.map +1 -1
  258. package/dist/ui/layout/GlassPanel.js +8 -4
  259. package/dist/ui/layout/GlassPanel.js.map +1 -1
  260. package/dist/ui/layout/PageHeader.js +10 -4
  261. package/dist/ui/layout/PageHeader.js.map +1 -1
  262. package/dist/ui/layout/PageHeading.js +17 -8
  263. package/dist/ui/layout/PageHeading.js.map +1 -1
  264. package/dist/ui/layout/ProfileHeader.js +21 -10
  265. package/dist/ui/layout/ProfileHeader.js.map +1 -1
  266. package/dist/ui/layout/ScrollableRow.js +19 -15
  267. package/dist/ui/layout/ScrollableRow.js.map +1 -1
  268. package/dist/ui/layout/SectionCard.js +10 -4
  269. package/dist/ui/layout/SectionCard.js.map +1 -1
  270. package/dist/ui/metrics/BarList.js +9 -4
  271. package/dist/ui/metrics/BarList.js.map +1 -1
  272. package/dist/ui/metrics/CalendarHeatmap.js +40 -21
  273. package/dist/ui/metrics/CalendarHeatmap.js.map +1 -1
  274. package/dist/ui/metrics/ChartTooltip.js +6 -2
  275. package/dist/ui/metrics/ChartTooltip.js.map +1 -1
  276. package/dist/ui/metrics/ColumnChart.js +22 -18
  277. package/dist/ui/metrics/ColumnChart.js.map +1 -1
  278. package/dist/ui/metrics/LineChart.js +20 -16
  279. package/dist/ui/metrics/LineChart.js.map +1 -1
  280. package/dist/ui/metrics/MetricCard.js +42 -24
  281. package/dist/ui/metrics/MetricCard.js.map +1 -1
  282. package/dist/ui/metrics/TableToggle.js +7 -2
  283. package/dist/ui/metrics/TableToggle.js.map +1 -1
  284. package/dist/ui/metrics/chartParts.js +14 -5
  285. package/dist/ui/metrics/chartParts.js.map +1 -1
  286. package/dist/ui/navigation/ColorSchemeToggle.js +8 -4
  287. package/dist/ui/navigation/ColorSchemeToggle.js.map +1 -1
  288. package/dist/ui/navigation/FontSizeToggle.js +6 -2
  289. package/dist/ui/navigation/FontSizeToggle.js.map +1 -1
  290. package/dist/ui/navigation/LanguageToggle.js +8 -4
  291. package/dist/ui/navigation/LanguageToggle.js.map +1 -1
  292. package/dist/ui/navigation/PageBreadcrumbs.js +12 -6
  293. package/dist/ui/navigation/PageBreadcrumbs.js.map +1 -1
  294. package/dist/ui/timeline/GroupedTimeline.js +26 -13
  295. package/dist/ui/timeline/GroupedTimeline.js.map +1 -1
  296. package/dist/upload/FileDropzone.js +35 -25
  297. package/dist/upload/FileDropzone.js.map +1 -1
  298. package/package.json +1 -1
@@ -16,21 +16,25 @@ import { startDownload } from "../../downloads/downloadQueue.js";
16
16
  import { useFileTypes } from "../../downloads/fileTypes.js";
17
17
  import ConfirmDialog from "../../feedback/ConfirmDialog.js";
18
18
  import { useConfirmAction } from "../../feedback/useConfirmAction.js";
19
- function DownloadMenu({
20
- formats,
21
- download,
22
- label,
23
- downloadLabel,
24
- disabled = false,
25
- confirmation,
26
- onDownloaded,
27
- onFailed,
28
- onCanceled,
29
- variant = "button",
30
- ariaLabel,
31
- onChooseFormat,
32
- icon: ButtonIcon = FileDownloadOutlinedIcon
33
- }) {
19
+ import { piecePart, usePieceProps } from "../../theme/themePieces.js";
20
+ const THEME_NAME = "CalcitaDownloadMenu";
21
+ const DownloadButton = piecePart(THEME_NAME, "Button", Button);
22
+ function DownloadMenu(props) {
23
+ const {
24
+ formats,
25
+ download,
26
+ label,
27
+ downloadLabel,
28
+ disabled = false,
29
+ confirmation,
30
+ onDownloaded,
31
+ onFailed,
32
+ onCanceled,
33
+ variant = "button",
34
+ ariaLabel,
35
+ onChooseFormat,
36
+ icon: ButtonIcon = FileDownloadOutlinedIcon
37
+ } = usePieceProps(props, THEME_NAME);
34
38
  const { t } = useToolkitTranslation("common");
35
39
  const fileTypes = useFileTypes();
36
40
  const [anchorElement, setAnchorElement] = useState(null);
@@ -106,12 +110,12 @@ function DownloadMenu({
106
110
  }
107
111
  ) }) })
108
112
  ) : /* @__PURE__ */ jsx(
109
- Button,
113
+ DownloadButton,
110
114
  {
111
115
  variant: "outlined",
112
116
  startIcon: /* @__PURE__ */ jsx(ButtonIcon, {}),
113
117
  endIcon: /* @__PURE__ */ jsx(ArrowDropDownIcon, {}),
114
- sx: { flexShrink: 0 },
118
+ ownSx: { flexShrink: 0 },
115
119
  disabled,
116
120
  "aria-haspopup": "menu",
117
121
  onClick: openMenu,
@@ -1 +1 @@
1
- {"version":3,"file":"DownloadMenu.js","sources":["../../../src/ui/actions/DownloadMenu.jsx"],"sourcesContent":["// \"Descargar ▾\" con los formatos de un reporte: PDF para leer o archivar, Excel o CSV para trabajar.\n//\n// Un solo botón y no tres: en la barra de una pantalla tres botones de descarga compiten con la acción principal.\n// Se acomoda a donde quedó, como BarActionButton: en una cápsula de la barra (las `actions` de PageHeading con cápsulas)\n// es solo el ícono con su nombre en el tooltip, como las demás acciones; antes salía la píldora «Descargar ▾» dentro de\n// la cápsula y se veía una cápsula dentro de otra. En el «⋯» del teléfono es un renglón que abre los formatos.\n// Quien llama pasa QUÉ descargar por formato (download(format, transferOptions) → {blob, fileName}, la forma de\n// un cliente HTTP que descarga). La descarga va al panel de descargas (startDownload, downloads): el diálogo se cierra al\n// confirmar y el avance, el aviso de \"descargado\" y el error se ven allá, mientras la persona sigue trabajando.\n//\n// Regla de tablas: una descarga pasa por un diálogo de confirmación que dice qué se va a bajar (p. ej. que la base\n// completa de clientes tarda unos minutos). Con `confirmation` el formato elegido NO se descarga de inmediato: abre\n// ConfirmDialog (máquina confirmActionMachine) y solo al confirmar se pide el archivo, una sola vez aunque haya doble clic.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useState} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport Button from '@mui/material/Button';\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 ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';\nimport FileDownloadOutlinedIcon from '@mui/icons-material/FileDownloadOutlined';\n\nimport {BAR_ACTIONS_PLACEMENT, useBarActionsPlacement, useCloseBarActionsMenu} from '@/appShell/barActionsSlot.js';\nimport {startDownload} from '@/downloads/downloadQueue.js';\nimport {useFileTypes} from '@/downloads/fileTypes.js';\nimport ConfirmDialog from '@/feedback/ConfirmDialog.jsx';\nimport {useConfirmAction} from '@/feedback/useConfirmAction.js';\n\n\n/**\n * @param {object} props\n * @param {string[]} props.formats los tipos de archivo, en orden: los de la casa (xlsx, csv, pdf) y los que el proyecto declare\n * en ToolkitProvider fileTypes (zip, xml…)\n * @param {(format: string, transferOptions: {signal: AbortSignal, onDownloadProgress: (progress: {loaded: number, total?: number|null}) => void}) => Promise<{blob: Blob, fileName: string}>} props.download\n * @param {string} [props.label] ya traducido; por defecto \"Descargar\"\n * @param {string} [props.downloadLabel] ya traducido; lo que se ve en el panel de descargas antes de saber el nombre\n * del archivo\n * @param {boolean} [props.disabled]\n * @param {(format: string) => {title: string, description: import('react').ReactNode, confirmLabel?: string}} [props.confirmation]\n * @param {(format: string) => void} [props.onDownloaded] p. ej. volver a pedir la tabla (si la descarga cambia algo en\n * el servidor)\n * @param {(format: string, error: unknown) => void} [props.onFailed] cuando la descarga no se pudo (el panel ya lo dice)\n * @param {(format: string) => void} [props.onCanceled] cuando la persona la canceló desde el panel\n * @param {'button'|'icon'} [props.variant] icon = solo el icono (columna de acciones de una tabla)\n * @param {string} [props.ariaLabel] ya traducido; obligatorio en la variante icon\n * @param {(format: string) => void} [props.onChooseFormat] quien llama se encarga de confirmar y descargar\n * @param {import('react').ElementType} [props.icon] ícono del botón; por defecto el de descarga. Distingue dos descargas en la\n * misma fila\n *\n * IMPORTANTE: hay que pasar `confirmation` u `onChooseFormat`. Sin ninguno de los dos el menú no descarga nada (regla de\n * tablas: toda descarga pasa por confirmación).\n *\n * OJO onChooseFormat: dentro de una celda del DataGrid el diálogo de confirmación NO puede vivir en la celda (el grid\n * virtualiza y vuelve a pintar sus celdas; el diálogo se perdía al elegir el formato). Ahí el menú solo avisa qué formato\n * se eligió y la pantalla abre SU confirmación (useConfirmAction) fuera de la tabla.\n */\nexport default function DownloadMenu({\n formats, download, label, downloadLabel, disabled = false, confirmation, onDownloaded, onFailed, onCanceled, variant = 'button', ariaLabel,\n onChooseFormat, icon: ButtonIcon = FileDownloadOutlinedIcon,\n}) {\n const {t} = useToolkitTranslation('common');\n const fileTypes = useFileTypes();\n // de dónde sale el menú de formatos: del botón, o (desde el «⋯» del teléfono, que se cierra) de donde estaba el renglón\n const [anchorElement, setAnchorElement] = useState(null);\n const [anchorPosition, setAnchorPosition] = useState(null);\n const placement = useBarActionsPlacement();\n const closeMoreMenu = useCloseBarActionsMenu();\n const closeFormats = () => {\n setAnchorElement(null);\n setAnchorPosition(null);\n };\n\n // no se espera a que termine: la descarga sigue en el panel y el diálogo de confirmación se cierra de inmediato\n const runDownload = (format) => {\n startDownload({\n label: downloadLabel ?? label ?? ariaLabel ?? t('actions.download'),\n format,\n request: (transferOptions) => download(format, transferOptions),\n onCompleted: () => onDownloaded?.(format),\n onFailed: (error) => onFailed?.(format, error),\n onCanceled: () => onCanceled?.(format),\n });\n };\n\n const confirmedDownload = useConfirmAction({run: ({subject}) => runDownload(subject.format)});\n\n const chooseFormat = (format) => {\n closeFormats();\n if (onChooseFormat) {\n onChooseFormat(format);\n return;\n }\n if (confirmation) {\n confirmedDownload.open({format});\n return;\n }\n // Regla de tablas: nada se descarga con un solo clic. Sin confirmation ni onChooseFormat es un error de quien\n // llama, no un camino válido: lo decimos en la consola y no descargamos\n console.error('DownloadMenu: falta confirmation u onChooseFormat; toda descarga pasa por un diálogo de confirmación');\n };\n\n const buttonLabel = label ?? t('actions.download');\n const openMenu = (event) => setAnchorElement(event.currentTarget);\n // desde el «⋯»: se cierra ese menú y los formatos salen donde estaba el renglón\n const openFromMoreMenu = (event) => {\n const {bottom, right} = event.currentTarget.getBoundingClientRect();\n closeMoreMenu();\n setAnchorPosition({top: bottom, left: right});\n };\n const isFormatsOpen = Boolean(anchorElement || anchorPosition);\n const confirmationTexts = confirmedDownload.subject ? confirmation?.(confirmedDownload.subject.format) : null;\n\n return (\n <>\n {placement === BAR_ACTIONS_PLACEMENT.menu && (\n <MenuItem disabled={disabled} aria-haspopup=\"menu\" onClick={openFromMoreMenu}>\n <ListItemIcon><ButtonIcon /></ListItemIcon>\n <ListItemText>{buttonLabel}</ListItemText>\n </MenuItem>\n )}\n {placement === BAR_ACTIONS_PLACEMENT.capsule && (\n // en la cápsula, del tamaño y color de las demás acciones (BarActionButton)\n <Tooltip title={buttonLabel} describeChild>\n <span>\n <IconButton color=\"primary\" aria-label={buttonLabel} aria-haspopup=\"menu\" aria-expanded={isFormatsOpen}\n disabled={disabled} onClick={openMenu}>\n <ButtonIcon />\n </IconButton>\n </span>\n </Tooltip>\n )}\n {placement === BAR_ACTIONS_PLACEMENT.inline && (variant === 'icon' ? (\n // describeChild: el nombre accesible ya lo trae el botón; sin esto el Tooltip le pone el mismo aria-label al\n // span y un lector de pantalla (o una prueba) encuentra dos \"botones\" con el mismo nombre\n <Tooltip title={ariaLabel ?? buttonLabel} describeChild>\n {/* el span deja que el tooltip funcione con el botón inhabilitado */}\n <span>\n <IconButton size=\"small\" aria-label={ariaLabel ?? buttonLabel} aria-haspopup=\"menu\"\n disabled={disabled} onClick={openMenu}>\n <ButtonIcon fontSize=\"small\" />\n </IconButton>\n </span>\n </Tooltip>\n ) : (\n <Button variant=\"outlined\" startIcon={<ButtonIcon />} endIcon={<ArrowDropDownIcon />}\n sx={{flexShrink: 0}} disabled={disabled} aria-haspopup=\"menu\" onClick={openMenu}>\n {buttonLabel}\n </Button>\n ))}\n <Menu open={isFormatsOpen} onClose={closeFormats}\n anchorReference={anchorPosition ? 'anchorPosition' : 'anchorEl'} anchorEl={anchorElement} anchorPosition={anchorPosition ?? undefined}\n anchorOrigin={{vertical: 'bottom', horizontal: 'right'}} transformOrigin={{vertical: 'top', horizontal: 'right'}}>\n {formats.map((format) => {\n const fileType = fileTypes[format];\n const Icon = fileType?.Icon ?? FileDownloadOutlinedIcon;\n return (\n <MenuItem key={format} onClick={() => chooseFormat(format)}>\n <ListItemIcon><Icon fontSize=\"small\" /></ListItemIcon>\n {/* los de la casa con sus textos; los del proyecto con su label y hint (o esas llaves en sus texts) */}\n <ListItemText primary={t(`download.formats.${format}.label`, {defaultValue: fileType?.label ?? format.toUpperCase()})}\n secondary={t(`download.formats.${format}.hint`, {defaultValue: fileType?.hint ?? ''})}\n slotProps={{secondary: {variant: 'caption'}}} />\n </MenuItem>\n );\n })}\n </Menu>\n {confirmation && (\n <ConfirmDialog\n {...confirmedDownload.dialogProps}\n title={confirmationTexts?.title ?? ''}\n description={confirmationTexts?.description}\n confirmLabel={confirmationTexts?.confirmLabel ?? t('actions.download')}\n />\n )}\n </>\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"DownloadMenu.js","sources":["../../../src/ui/actions/DownloadMenu.jsx"],"sourcesContent":["// \"Descargar ▾\" con los formatos de un reporte: PDF para leer o archivar, Excel o CSV para trabajar.\n//\n// Un solo botón y no tres: en la barra de una pantalla tres botones de descarga compiten con la acción principal.\n// Se acomoda a donde quedó, como BarActionButton: en una cápsula de la barra (las `actions` de PageHeading con cápsulas)\n// es solo el ícono con su nombre en el tooltip, como las demás acciones; antes salía la píldora «Descargar ▾» dentro de\n// la cápsula y se veía una cápsula dentro de otra. En el «⋯» del teléfono es un renglón que abre los formatos.\n// Quien llama pasa QUÉ descargar por formato (download(format, transferOptions) → {blob, fileName}, la forma de\n// un cliente HTTP que descarga). La descarga va al panel de descargas (startDownload, downloads): el diálogo se cierra al\n// confirmar y el avance, el aviso de \"descargado\" y el error se ven allá, mientras la persona sigue trabajando.\n//\n// Regla de tablas: una descarga pasa por un diálogo de confirmación que dice qué se va a bajar (p. ej. que la base\n// completa de clientes tarda unos minutos). Con `confirmation` el formato elegido NO se descarga de inmediato: abre\n// ConfirmDialog (máquina confirmActionMachine) y solo al confirmar se pide el archivo, una sola vez aunque haya doble clic.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaDownloadMenu con defaultProps y\n// styleOverrides de sus partes: button.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useState} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport Button from '@mui/material/Button';\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 ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';\nimport FileDownloadOutlinedIcon from '@mui/icons-material/FileDownloadOutlined';\n\nimport {BAR_ACTIONS_PLACEMENT, useBarActionsPlacement, useCloseBarActionsMenu} from '@/appShell/barActionsSlot.js';\nimport {startDownload} from '@/downloads/downloadQueue.js';\nimport {useFileTypes} from '@/downloads/fileTypes.js';\nimport ConfirmDialog from '@/feedback/ConfirmDialog.jsx';\nimport {useConfirmAction} from '@/feedback/useConfirmAction.js';\nimport {piecePart, usePieceProps} from '@/theme/themePieces.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaDownloadMenu)\nconst THEME_NAME = 'CalcitaDownloadMenu';\nconst DownloadButton = piecePart(THEME_NAME, 'Button', Button);\n\n\n/**\n * @param {object} props\n * @param {string[]} props.formats los tipos de archivo, en orden: los de la casa (xlsx, csv, pdf) y los que el proyecto declare\n * en ToolkitProvider fileTypes (zip, xml…)\n * @param {(format: string, transferOptions: {signal: AbortSignal, onDownloadProgress: (progress: {loaded: number, total?: number|null}) => void}) => Promise<{blob: Blob, fileName: string}>} props.download\n * @param {string} [props.label] ya traducido; por defecto \"Descargar\"\n * @param {string} [props.downloadLabel] ya traducido; lo que se ve en el panel de descargas antes de saber el nombre\n * del archivo\n * @param {boolean} [props.disabled]\n * @param {(format: string) => {title: string, description: import('react').ReactNode, confirmLabel?: string}} [props.confirmation]\n * @param {(format: string) => void} [props.onDownloaded] p. ej. volver a pedir la tabla (si la descarga cambia algo en\n * el servidor)\n * @param {(format: string, error: unknown) => void} [props.onFailed] cuando la descarga no se pudo (el panel ya lo dice)\n * @param {(format: string) => void} [props.onCanceled] cuando la persona la canceló desde el panel\n * @param {'button'|'icon'} [props.variant] icon = solo el icono (columna de acciones de una tabla)\n * @param {string} [props.ariaLabel] ya traducido; obligatorio en la variante icon\n * @param {(format: string) => void} [props.onChooseFormat] quien llama se encarga de confirmar y descargar\n * @param {import('react').ElementType} [props.icon] ícono del botón; por defecto el de descarga. Distingue dos descargas en la\n * misma fila\n *\n * IMPORTANTE: hay que pasar `confirmation` u `onChooseFormat`. Sin ninguno de los dos el menú no descarga nada (regla de\n * tablas: toda descarga pasa por confirmación).\n *\n * OJO onChooseFormat: dentro de una celda del DataGrid el diálogo de confirmación NO puede vivir en la celda (el grid\n * virtualiza y vuelve a pintar sus celdas; el diálogo se perdía al elegir el formato). Ahí el menú solo avisa qué formato\n * se eligió y la pantalla abre SU confirmación (useConfirmAction) fuera de la tabla.\n */\nexport default function DownloadMenu(props) {\n // lo que el proyecto puso en su tema (components.CalcitaDownloadMenu.defaultProps) y encima lo que llega aquí\n const {\n formats, download, label, downloadLabel, disabled = false, confirmation, onDownloaded, onFailed, onCanceled, variant = 'button', ariaLabel,\n onChooseFormat, icon: ButtonIcon = FileDownloadOutlinedIcon,\n} = usePieceProps(props, THEME_NAME);\n const {t} = useToolkitTranslation('common');\n const fileTypes = useFileTypes();\n // de dónde sale el menú de formatos: del botón, o (desde el «⋯» del teléfono, que se cierra) de donde estaba el renglón\n const [anchorElement, setAnchorElement] = useState(null);\n const [anchorPosition, setAnchorPosition] = useState(null);\n const placement = useBarActionsPlacement();\n const closeMoreMenu = useCloseBarActionsMenu();\n const closeFormats = () => {\n setAnchorElement(null);\n setAnchorPosition(null);\n };\n\n // no se espera a que termine: la descarga sigue en el panel y el diálogo de confirmación se cierra de inmediato\n const runDownload = (format) => {\n startDownload({\n label: downloadLabel ?? label ?? ariaLabel ?? t('actions.download'),\n format,\n request: (transferOptions) => download(format, transferOptions),\n onCompleted: () => onDownloaded?.(format),\n onFailed: (error) => onFailed?.(format, error),\n onCanceled: () => onCanceled?.(format),\n });\n };\n\n const confirmedDownload = useConfirmAction({run: ({subject}) => runDownload(subject.format)});\n\n const chooseFormat = (format) => {\n closeFormats();\n if (onChooseFormat) {\n onChooseFormat(format);\n return;\n }\n if (confirmation) {\n confirmedDownload.open({format});\n return;\n }\n // Regla de tablas: nada se descarga con un solo clic. Sin confirmation ni onChooseFormat es un error de quien\n // llama, no un camino válido: lo decimos en la consola y no descargamos\n console.error('DownloadMenu: falta confirmation u onChooseFormat; toda descarga pasa por un diálogo de confirmación');\n };\n\n const buttonLabel = label ?? t('actions.download');\n const openMenu = (event) => setAnchorElement(event.currentTarget);\n // desde el «⋯»: se cierra ese menú y los formatos salen donde estaba el renglón\n const openFromMoreMenu = (event) => {\n const {bottom, right} = event.currentTarget.getBoundingClientRect();\n closeMoreMenu();\n setAnchorPosition({top: bottom, left: right});\n };\n const isFormatsOpen = Boolean(anchorElement || anchorPosition);\n const confirmationTexts = confirmedDownload.subject ? confirmation?.(confirmedDownload.subject.format) : null;\n\n return (\n <>\n {placement === BAR_ACTIONS_PLACEMENT.menu && (\n <MenuItem disabled={disabled} aria-haspopup=\"menu\" onClick={openFromMoreMenu}>\n <ListItemIcon><ButtonIcon /></ListItemIcon>\n <ListItemText>{buttonLabel}</ListItemText>\n </MenuItem>\n )}\n {placement === BAR_ACTIONS_PLACEMENT.capsule && (\n // en la cápsula, del tamaño y color de las demás acciones (BarActionButton)\n <Tooltip title={buttonLabel} describeChild>\n <span>\n <IconButton color=\"primary\" aria-label={buttonLabel} aria-haspopup=\"menu\" aria-expanded={isFormatsOpen}\n disabled={disabled} onClick={openMenu}>\n <ButtonIcon />\n </IconButton>\n </span>\n </Tooltip>\n )}\n {placement === BAR_ACTIONS_PLACEMENT.inline && (variant === 'icon' ? (\n // describeChild: el nombre accesible ya lo trae el botón; sin esto el Tooltip le pone el mismo aria-label al\n // span y un lector de pantalla (o una prueba) encuentra dos \"botones\" con el mismo nombre\n <Tooltip title={ariaLabel ?? buttonLabel} describeChild>\n {/* el span deja que el tooltip funcione con el botón inhabilitado */}\n <span>\n <IconButton size=\"small\" aria-label={ariaLabel ?? buttonLabel} aria-haspopup=\"menu\"\n disabled={disabled} onClick={openMenu}>\n <ButtonIcon fontSize=\"small\" />\n </IconButton>\n </span>\n </Tooltip>\n ) : (\n <DownloadButton variant=\"outlined\" startIcon={<ButtonIcon />} endIcon={<ArrowDropDownIcon />}\n ownSx={{flexShrink: 0}} disabled={disabled} aria-haspopup=\"menu\" onClick={openMenu}>\n {buttonLabel}\n </DownloadButton>\n ))}\n <Menu open={isFormatsOpen} onClose={closeFormats}\n anchorReference={anchorPosition ? 'anchorPosition' : 'anchorEl'} anchorEl={anchorElement} anchorPosition={anchorPosition ?? undefined}\n anchorOrigin={{vertical: 'bottom', horizontal: 'right'}} transformOrigin={{vertical: 'top', horizontal: 'right'}}>\n {formats.map((format) => {\n const fileType = fileTypes[format];\n const Icon = fileType?.Icon ?? FileDownloadOutlinedIcon;\n return (\n <MenuItem key={format} onClick={() => chooseFormat(format)}>\n <ListItemIcon><Icon fontSize=\"small\" /></ListItemIcon>\n {/* los de la casa con sus textos; los del proyecto con su label y hint (o esas llaves en sus texts) */}\n <ListItemText primary={t(`download.formats.${format}.label`, {defaultValue: fileType?.label ?? format.toUpperCase()})}\n secondary={t(`download.formats.${format}.hint`, {defaultValue: fileType?.hint ?? ''})}\n slotProps={{secondary: {variant: 'caption'}}} />\n </MenuItem>\n );\n })}\n </Menu>\n {confirmation && (\n <ConfirmDialog\n {...confirmedDownload.dialogProps}\n title={confirmationTexts?.title ?? ''}\n description={confirmationTexts?.description}\n confirmLabel={confirmationTexts?.confirmLabel ?? t('actions.download')}\n />\n )}\n </>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;AAsCA,MAAM,aAAa;AACnB,MAAM,iBAAiB,UAAU,YAAY,UAAU,MAAM;AA8B7D,SAAwB,aAAa,OAAO;AAExC,QAAM;AAAA,IACN;AAAA,IAAS;AAAA,IAAU;AAAA,IAAO;AAAA,IAAe,WAAW;AAAA,IAAO;AAAA,IAAc;AAAA,IAAc;AAAA,IAAU;AAAA,IAAY,UAAU;AAAA,IAAU;AAAA,IACjI;AAAA,IAAgB,MAAM,aAAa;AAAA,EAAA,IACnC,cAAc,OAAO,UAAU;AAC/B,QAAM,EAAC,EAAA,IAAK,sBAAsB,QAAQ;AAC1C,QAAM,YAAY,aAAA;AAElB,QAAM,CAAC,eAAe,gBAAgB,IAAI,SAAS,IAAI;AACvD,QAAM,CAAC,gBAAgB,iBAAiB,IAAI,SAAS,IAAI;AACzD,QAAM,YAAY,uBAAA;AAClB,QAAM,gBAAgB,uBAAA;AACtB,QAAM,eAAe,MAAM;AACvB,qBAAiB,IAAI;AACrB,sBAAkB,IAAI;AAAA,EAC1B;AAGA,QAAM,cAAc,CAAC,WAAW;AAC5B,kBAAc;AAAA,MACV,OAAO,iBAAiB,SAAS,aAAa,EAAE,kBAAkB;AAAA,MAClE;AAAA,MACA,SAAS,CAAC,oBAAoB,SAAS,QAAQ,eAAe;AAAA,MAC9D,aAAa,MAAM,eAAe,MAAM;AAAA,MACxC,UAAU,CAAC,UAAU,WAAW,QAAQ,KAAK;AAAA,MAC7C,YAAY,MAAM,aAAa,MAAM;AAAA,IAAA,CACxC;AAAA,EACL;AAEA,QAAM,oBAAoB,iBAAiB,EAAC,KAAK,CAAC,EAAC,QAAA,MAAa,YAAY,QAAQ,MAAM,GAAE;AAE5F,QAAM,eAAe,CAAC,WAAW;AAC7B,iBAAA;AACA,QAAI,gBAAgB;AAChB,qBAAe,MAAM;AACrB;AAAA,IACJ;AACA,QAAI,cAAc;AACd,wBAAkB,KAAK,EAAC,QAAO;AAC/B;AAAA,IACJ;AAGA,YAAQ,MAAM,sGAAsG;AAAA,EACxH;AAEA,QAAM,cAAc,SAAS,EAAE,kBAAkB;AACjD,QAAM,WAAW,CAAC,UAAU,iBAAiB,MAAM,aAAa;AAEhE,QAAM,mBAAmB,CAAC,UAAU;AAChC,UAAM,EAAC,QAAQ,MAAA,IAAS,MAAM,cAAc,sBAAA;AAC5C,kBAAA;AACA,sBAAkB,EAAC,KAAK,QAAQ,MAAM,OAAM;AAAA,EAChD;AACA,QAAM,gBAAgB,QAAQ,iBAAiB,cAAc;AAC7D,QAAM,oBAAoB,kBAAkB,UAAU,eAAe,kBAAkB,QAAQ,MAAM,IAAI;AAEzG,SACI,qBAAA,UAAA,EACK,UAAA;AAAA,IAAA,cAAc,sBAAsB,QACjC,qBAAC,UAAA,EAAS,UAAoB,iBAAc,QAAO,SAAS,kBACxD,UAAA;AAAA,MAAA,oBAAC,cAAA,EAAa,UAAA,oBAAC,YAAA,CAAA,CAAW,GAAE;AAAA,MAC5B,oBAAC,gBAAc,UAAA,YAAA,CAAY;AAAA,IAAA,GAC/B;AAAA,IAEH,cAAc,sBAAsB;AAAA,wBAEhC,SAAA,EAAQ,OAAO,aAAa,eAAa,MACtC,8BAAC,QAAA,EACG,UAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QAAW,OAAM;AAAA,QAAU,cAAY;AAAA,QAAa,iBAAc;AAAA,QAAO,iBAAe;AAAA,QAC7E;AAAA,QAAoB,SAAS;AAAA,QACrC,8BAAC,YAAA,CAAA,CAAW;AAAA,MAAA;AAAA,IAAA,GAEpB,EAAA,CACJ;AAAA,IAEH,cAAc,sBAAsB,WAAW,YAAY;AAAA;AAAA;AAAA,MAGxD,oBAAC,WAAQ,OAAO,aAAa,aAAa,eAAa,MAEnD,8BAAC,QAAA,EACG,UAAA;AAAA,QAAC;AAAA,QAAA;AAAA,UAAW,MAAK;AAAA,UAAQ,cAAY,aAAa;AAAA,UAAa,iBAAc;AAAA,UACjE;AAAA,UAAoB,SAAS;AAAA,UACrC,UAAA,oBAAC,YAAA,EAAW,UAAS,QAAA,CAAQ;AAAA,QAAA;AAAA,MAAA,GAErC,EAAA,CACJ;AAAA,QAEA;AAAA,MAAC;AAAA,MAAA;AAAA,QAAe,SAAQ;AAAA,QAAW,+BAAY,YAAA,EAAW;AAAA,QAAI,6BAAU,mBAAA,EAAkB;AAAA,QAClF,OAAO,EAAC,YAAY,EAAA;AAAA,QAAI;AAAA,QAAoB,iBAAc;AAAA,QAAO,SAAS;AAAA,QAC7E,UAAA;AAAA,MAAA;AAAA,IAAA;AAAA,IAGT;AAAA,MAAC;AAAA,MAAA;AAAA,QAAK,MAAM;AAAA,QAAe,SAAS;AAAA,QAC9B,iBAAiB,iBAAiB,mBAAmB;AAAA,QAAY,UAAU;AAAA,QAAe,gBAAgB,kBAAkB;AAAA,QAC5H,cAAc,EAAC,UAAU,UAAU,YAAY,QAAA;AAAA,QAAU,iBAAiB,EAAC,UAAU,OAAO,YAAY,QAAA;AAAA,QACzG,UAAA,QAAQ,IAAI,CAAC,WAAW;AACrB,gBAAM,WAAW,UAAU,MAAM;AACjC,gBAAM,OAAO,UAAU,QAAQ;AAC/B,sCACK,UAAA,EAAsB,SAAS,MAAM,aAAa,MAAM,GACrD,UAAA;AAAA,YAAA,oBAAC,cAAA,EAAa,UAAA,oBAAC,MAAA,EAAK,UAAS,SAAQ,GAAE;AAAA,YAEvC;AAAA,cAAC;AAAA,cAAA;AAAA,gBAAa,SAAS,EAAE,oBAAoB,MAAM,UAAU,EAAC,cAAc,UAAU,SAAS,OAAO,YAAA,EAAY,CAAE;AAAA,gBACtG,WAAW,EAAE,oBAAoB,MAAM,SAAS,EAAC,cAAc,UAAU,QAAQ,GAAA,CAAG;AAAA,gBACpF,WAAW,EAAC,WAAW,EAAC,SAAS,YAAS;AAAA,cAAC;AAAA,YAAA;AAAA,UAAG,EAAA,GALjD,MAMf;AAAA,QAER,CAAC;AAAA,MAAA;AAAA,IAAA;AAAA,IAEJ,gBACG;AAAA,MAAC;AAAA,MAAA;AAAA,QACI,GAAG,kBAAkB;AAAA,QACtB,OAAO,mBAAmB,SAAS;AAAA,QACnC,aAAa,mBAAmB;AAAA,QAChC,cAAc,mBAAmB,gBAAgB,EAAE,kBAAkB;AAAA,MAAA;AAAA,IAAA;AAAA,EACzE,GAER;AAER;"}
@@ -18,6 +18,13 @@ import { usesMobileTabBar } from "../../appShell/tabBarRule.js";
18
18
  import { minimalFloatingStylesOf } from "../../theme/components/minimalComponents.js";
19
19
  import { isGlassTheme, glassStylesOf } from "../../theme/glassSurface.js";
20
20
  import { useToolkitTranslation } from "../../toolkit/useToolkitTranslation.js";
21
+ import { piecePart, usePieceProps } from "../../theme/themePieces.js";
22
+ const THEME_NAME = "CalcitaSelectionActions";
23
+ const SelectionBar = piecePart(THEME_NAME, "Bar", Stack);
24
+ const SelectionCount = piecePart(THEME_NAME, "Count", Typography);
25
+ const SelectionClear = piecePart(THEME_NAME, "Clear", Button);
26
+ const SelectionPhoneCount = piecePart(THEME_NAME, "PhoneCount", Typography);
27
+ const SelectionPhoneActions = piecePart(THEME_NAME, "PhoneActions", Box);
21
28
  const SELECTION_PHONE_LAYOUT = Object.freeze({ top: "top", split: "split" });
22
29
  const FLOATING_GAP_PIXELS = 8;
23
30
  const FLOATING_BOTTOM_SPACE_PIXELS = 64;
@@ -41,7 +48,8 @@ function bottomCapsuleSxOf(theme) {
41
48
  ...aboveMobileTabBarOf(FLOATING_GAP_PIXELS)
42
49
  };
43
50
  }
44
- function SelectionActions({ countText, actions = [], onClear, phoneLayout = SELECTION_PHONE_LAYOUT.top, texts, sx }) {
51
+ function SelectionActions(props) {
52
+ const { countText, actions = [], onClear, phoneLayout = SELECTION_PHONE_LAYOUT.top, texts, sx } = usePieceProps(props, THEME_NAME);
45
53
  const theme = useTheme();
46
54
  const { t } = useToolkitTranslation("common", texts);
47
55
  const [runningActionKey, setRunningActionKey] = useState(null);
@@ -58,20 +66,20 @@ function SelectionActions({ countText, actions = [], onClear, phoneLayout = SELE
58
66
  const isDisabledOf = (action) => runningActionKey !== null || Boolean(action.disabled);
59
67
  const clearButton = /* @__PURE__ */ jsx(Tooltip, { title: t("selection.clear"), children: /* @__PURE__ */ jsx(IconButton, { "aria-label": t("selection.clear"), onClick: onClear, children: /* @__PURE__ */ jsx(CloseRoundedIcon, {}) }) });
60
68
  const row = /* @__PURE__ */ jsxs(
61
- Stack,
69
+ SelectionBar,
62
70
  {
63
71
  role: "toolbar",
64
72
  "aria-label": t("selection.actionsLabel"),
65
73
  direction: "row",
66
74
  spacing: 1,
67
75
  useFlexGap: true,
68
- sx: [
76
+ ownSx: [
69
77
  { flexGrow: 1, alignItems: "center", flexWrap: "wrap", minHeight: 32 },
70
78
  floatsOnPhone && { [PHONE_MEDIA_QUERY]: { display: "none" } },
71
79
  ...extraSx
72
80
  ],
73
81
  children: [
74
- /* @__PURE__ */ jsx(Typography, { variant: "body2", sx: { fontWeight: 600, mr: 1 }, children: countText }),
82
+ /* @__PURE__ */ jsx(SelectionCount, { variant: "body2", ownSx: { fontWeight: 600, mr: 1 }, children: countText }),
75
83
  actions.map((action) => /* @__PURE__ */ jsx(
76
84
  Button,
77
85
  {
@@ -84,7 +92,7 @@ function SelectionActions({ countText, actions = [], onClear, phoneLayout = SELE
84
92
  },
85
93
  action.key
86
94
  )),
87
- /* @__PURE__ */ jsx(Button, { size: "small", color: "inherit", startIcon: /* @__PURE__ */ jsx(CloseRoundedIcon, {}), onClick: onClear, sx: { ml: "auto" }, children: t("selection.clear") })
95
+ /* @__PURE__ */ jsx(SelectionClear, { size: "small", color: "inherit", startIcon: /* @__PURE__ */ jsx(CloseRoundedIcon, {}), onClick: onClear, ownSx: { ml: "auto" }, children: t("selection.clear") })
88
96
  ]
89
97
  }
90
98
  );
@@ -95,7 +103,7 @@ function SelectionActions({ countText, actions = [], onClear, phoneLayout = SELE
95
103
  return /* @__PURE__ */ jsxs(Fragment, { children: [
96
104
  row,
97
105
  /* @__PURE__ */ jsx(BarActions, { bare: true, exclusive: true, order: -2, sx: PHONE_ONLY_SX, inlineSx: PHONE_ONLY_SX, children: /* @__PURE__ */ jsxs(BarCapsule, { "aria-label": t("selection.summaryLabel"), sx: { pl: 2 }, children: [
98
- /* @__PURE__ */ jsx(Typography, { variant: "body2", role: "status", "aria-live": "polite", sx: { fontWeight: 600, whiteSpace: "nowrap", mr: 0.5 }, children: countText }),
106
+ /* @__PURE__ */ jsx(SelectionPhoneCount, { variant: "body2", role: "status", "aria-live": "polite", ownSx: { fontWeight: 600, whiteSpace: "nowrap", mr: 0.5 }, children: countText }),
99
107
  clearButton
100
108
  ] }) }),
101
109
  phoneLayout === SELECTION_PHONE_LAYOUT.top && actions.length > 0 && /* @__PURE__ */ jsx(BarActions, { label: t("selection.actionsLabel"), exclusive: true, order: -1, sx: PHONE_ONLY_SX, inlineSx: PHONE_ONLY_SX, children: actions.map((action) => /* @__PURE__ */ jsx(
@@ -111,7 +119,7 @@ function SelectionActions({ countText, actions = [], onClear, phoneLayout = SELE
111
119
  )) }),
112
120
  showsBottomCapsule && /* @__PURE__ */ jsxs(Fragment, { children: [
113
121
  /* @__PURE__ */ jsx(GlobalStyles, { styles: { ":root": { [FLOATING_BOTTOM_SPACE_CSS_VARIABLE]: `${FLOATING_BOTTOM_SPACE_PIXELS}px` } } }),
114
- /* @__PURE__ */ jsx(Box, { role: "toolbar", "aria-label": t("selection.actionsLabel"), sx: [bottomCapsuleSxOf, PHONE_ONLY_SX], children: actions.map((action) => action.icon ? /* @__PURE__ */ jsx(Tooltip, { title: action.label, children: /* @__PURE__ */ jsx("span", { children: /* @__PURE__ */ jsx(
122
+ /* @__PURE__ */ jsx(SelectionPhoneActions, { role: "toolbar", "aria-label": t("selection.actionsLabel"), ownSx: [bottomCapsuleSxOf, PHONE_ONLY_SX], children: actions.map((action) => action.icon ? /* @__PURE__ */ jsx(Tooltip, { title: action.label, children: /* @__PURE__ */ jsx("span", { children: /* @__PURE__ */ jsx(
115
123
  IconButton,
116
124
  {
117
125
  "aria-label": action.label,
@@ -1 +1 @@
1
- {"version":3,"file":"SelectionActions.js","sources":["../../../src/ui/actions/SelectionActions.jsx"],"sourcesContent":["// Las acciones de lo elegido en una lista («5 pedidos elegidos · Cancelar · Borrar · Quitar selección»), en el lugar que le\n// toca según el equipo. Una sola pieza para que cada lista con casillas (la tabla de servidor, una propia) no repita dónde\n// va cada cosa:\n// - escritorio, y el teléfono sin barra de pestañas abajo (el tema de MUI sin vidrio): un renglón donde se pone la pieza,\n// cada acción como botón con su texto;\n// - teléfono con vidrio, o minimalista con cápsulas (usesMobileTabBar), según phoneLayout (SELECTION_PHONE_LAYOUT):\n// top (por omisión): arriba, en la barra flotante, «5 elegidos ✕» y las acciones como íconos en su propia cápsula,\n// como Apple separa los grupos; las que no caben, al «⋯» (BarActions);\n// en los dos, mientras hay algo elegido, el renglón de arriba queda solo para lo elegido (BarActions exclusive): las\n// demás cápsulas de la pantalla y la lupa se esconden, como el modo de edición del iPhone;\n// split: como iOS en modo de edición (Fotos, Mail, Archivos): arriba «5 elegidos ✕» y abajo, encima de las pestañas,\n// una cápsula fija con las acciones, donde llega el pulgar. Mientras se ve, pide su alto al marco\n// (FLOATING_BOTTOM_SPACE_CSS_VARIABLE) para no tapar el final de la página.\n// Lo pidió el autor: en el teléfono, configurable, y por omisión todo arriba.\n//\n// Con vidrio, la cápsula de arriba deja sus botones del color del texto (BarCapsule: la regla de la barra); abajo, una acción\n// destructiva (color: 'error') conserva su rojo, como pide la guía de Apple para lo que borra.\n//\n// Cada acción puede regresar una promesa: mientras corre, las demás se apagan (nadie cancela dos veces lo mismo).\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useState} from 'react';\nimport Box from '@mui/material/Box';\nimport Button from '@mui/material/Button';\nimport GlobalStyles from '@mui/material/GlobalStyles';\nimport IconButton from '@mui/material/IconButton';\nimport Stack from '@mui/material/Stack';\nimport Tooltip from '@mui/material/Tooltip';\nimport Typography from '@mui/material/Typography';\nimport {useTheme} from '@mui/material/styles';\nimport CloseRoundedIcon from '@mui/icons-material/CloseRounded';\n\nimport BarActionButton from '@/appShell/BarActionButton.jsx';\nimport BarActions from '@/appShell/BarActions.jsx';\nimport BarCapsule from '@/appShell/BarCapsule.jsx';\nimport {\n FLOATING_BOTTOM_SPACE_CSS_VARIABLE, GLASS_PANEL_GAP_PIXELS, PHONE_MEDIA_QUERY, aboveMobileTabBarOf,\n} from '@/appShell/shellSizes.js';\nimport {usesMobileTabBar} from '@/appShell/tabBarRule.js';\nimport {minimalFloatingStylesOf} from '@/theme/components/minimalComponents.js';\nimport {glassStylesOf, isGlassTheme} from '@/theme/glassSurface.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\n\n/** Dónde van las acciones de lo elegido en el teléfono con barra de pestañas abajo: todo arriba o divididas (iOS). */\nexport const SELECTION_PHONE_LAYOUT = Object.freeze({top: 'top', split: 'split'});\n\n// lo que se separa la cápsula de abajo de la barra de pestañas (px), lo mismo que el aviso de versión nueva\nconst FLOATING_GAP_PIXELS = 8;\n// lo que pide al marco mientras flota abajo: su alto (48) y su separación, con aire\nconst FLOATING_BOTTOM_SPACE_PIXELS = 64;\n// lo que solo se ve en el teléfono\nconst PHONE_ONLY_SX = {display: 'none', [PHONE_MEDIA_QUERY]: {display: 'flex'}};\n\n/**\n * ¿En el teléfono lo elegido sale de su renglón (arriba o abajo)? La misma regla que la barra de pestañas de abajo: con ella,\n * el renglón de la pantalla se queda con lo suyo (el conteo de la tabla) y las acciones flotan.\n *\n * @param {object} theme\n * @returns {boolean}\n */\nexport function selectionFloatsOnPhone(theme) {\n return usesMobileTabBar(theme);\n}\n\n/**\n * La cápsula fija abajo, centrada en la ventana, encima de la barra de pestañas.\n *\n * @param {object} theme\n * @returns {object}\n */\nfunction bottomCapsuleSxOf(theme) {\n return {\n ...(isGlassTheme(theme) ? glassStylesOf(theme) : minimalFloatingStylesOf(theme)),\n position: 'fixed',\n left: '50%',\n transform: 'translateX(-50%)',\n zIndex: theme.zIndex.appBar,\n alignItems: 'center',\n gap: 0.5,\n width: 'max-content',\n maxWidth: `calc(100% - ${GLASS_PANEL_GAP_PIXELS * 2}px)`,\n borderRadius: '999px',\n p: 0.5,\n ...aboveMobileTabBarOf(FLOATING_GAP_PIXELS),\n };\n}\n\n/**\n * @param {object} props\n * @param {string} props.countText «5 pedidos elegidos», ya traducido. La pieza se pinta mientras la pantalla la ponga: con\n * nada elegido, no la ponga\n * @param {Array<{key: string, label: string, icon?: import('react').ReactNode, color?: 'inherit'|'primary'|'secondary'|'error'|'info'|'success'|'warning', disabled?: boolean, onClick: () => unknown}>} [props.actions]\n * label ya traducido; en el teléfono se ve el ícono (sin él, el texto). onClick puede regresar una promesa\n * @param {() => void} props.onClear quitar la selección\n * @param {'top'|'split'} [props.phoneLayout] SELECTION_PHONE_LAYOUT; por omisión todo arriba\n * @param {object} [props.texts] los textos que cambian, con la forma de `common`: {selection: {clear, actionsLabel, summaryLabel}}\n * @param {object} [props.sx] estilos del renglón\n */\nexport default function SelectionActions({countText, actions = [], onClear, phoneLayout = SELECTION_PHONE_LAYOUT.top, texts, sx}) {\n const theme = useTheme();\n const {t} = useToolkitTranslation('common', texts);\n const [runningActionKey, setRunningActionKey] = useState(null);\n const floatsOnPhone = selectionFloatsOnPhone(theme);\n const extraSx = Array.isArray(sx) ? sx : [sx];\n\n const runAction = async (action) => {\n setRunningActionKey(action.key);\n try {\n await action.onClick();\n } finally {\n setRunningActionKey(null);\n }\n };\n const isDisabledOf = (action) => runningActionKey !== null || Boolean(action.disabled);\n\n const clearButton = (\n <Tooltip title={t('selection.clear')}>\n <IconButton aria-label={t('selection.clear')} onClick={onClear}>\n <CloseRoundedIcon />\n </IconButton>\n </Tooltip>\n );\n\n // el renglón: escritorio siempre; en el teléfono, solo si ahí no flota\n const row = (\n <Stack role=\"toolbar\" aria-label={t('selection.actionsLabel')} direction=\"row\" spacing={1} useFlexGap\n sx={[{flexGrow: 1, alignItems: 'center', flexWrap: 'wrap', minHeight: 32},\n floatsOnPhone && {[PHONE_MEDIA_QUERY]: {display: 'none'}}, ...extraSx]}>\n <Typography variant=\"body2\" sx={{fontWeight: 600, mr: 1}}>{countText}</Typography>\n {actions.map((action) => (\n <Button key={action.key} size=\"small\" color={action.color ?? 'primary'} startIcon={action.icon}\n disabled={isDisabledOf(action)} onClick={() => runAction(action)}>\n {action.label}\n </Button>\n ))}\n <Button size=\"small\" color=\"inherit\" startIcon={<CloseRoundedIcon />} onClick={onClear} sx={{ml: 'auto'}}>\n {t('selection.clear')}\n </Button>\n </Stack>\n );\n\n if (!floatsOnPhone) {\n return row;\n }\n\n const showsBottomCapsule = phoneLayout === SELECTION_PHONE_LAYOUT.split && actions.length > 0;\n\n return (\n <>\n {row}\n {/* arriba: con las demás cápsulas flotantes de la pantalla (BarActions), antes que ellas. Sin label en BarActions:\n el nombre ya lo lleva la cápsula (dos grupos anidados con el mismo nombre se leen dos veces) */}\n <BarActions bare exclusive order={-2} sx={PHONE_ONLY_SX} inlineSx={PHONE_ONLY_SX}>\n <BarCapsule aria-label={t('selection.summaryLabel')} sx={{pl: 2}}>\n <Typography variant=\"body2\" role=\"status\" aria-live=\"polite\" sx={{fontWeight: 600, whiteSpace: 'nowrap', mr: 0.5}}>\n {countText}\n </Typography>\n {clearButton}\n </BarCapsule>\n </BarActions>\n {phoneLayout === SELECTION_PHONE_LAYOUT.top && actions.length > 0 && (\n <BarActions label={t('selection.actionsLabel')} exclusive order={-1} sx={PHONE_ONLY_SX} inlineSx={PHONE_ONLY_SX}>\n {actions.map((action) => (\n <BarActionButton key={action.key} label={action.label} icon={action.icon} color={action.color}\n disabled={isDisabledOf(action)} onClick={() => runAction(action)} />\n ))}\n </BarActions>\n )}\n {showsBottomCapsule && (\n <>\n <GlobalStyles styles={{':root': {[FLOATING_BOTTOM_SPACE_CSS_VARIABLE]: `${FLOATING_BOTTOM_SPACE_PIXELS}px`}}} />\n <Box role=\"toolbar\" aria-label={t('selection.actionsLabel')} sx={[bottomCapsuleSxOf, PHONE_ONLY_SX]}>\n {actions.map((action) => (action.icon ? (\n <Tooltip key={action.key} title={action.label}>\n {/* el span deja ver el tooltip aunque el botón esté apagado */}\n <span>\n <IconButton aria-label={action.label} color={action.color ?? 'default'} disabled={isDisabledOf(action)}\n onClick={() => runAction(action)}>\n {action.icon}\n </IconButton>\n </span>\n </Tooltip>\n ) : (\n <Button key={action.key} size=\"small\" color={action.color ?? 'primary'} disabled={isDisabledOf(action)}\n onClick={() => runAction(action)}>\n {action.label}\n </Button>\n )))}\n </Box>\n </>\n )}\n </>\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"SelectionActions.js","sources":["../../../src/ui/actions/SelectionActions.jsx"],"sourcesContent":["// Las acciones de lo elegido en una lista («5 pedidos elegidos · Cancelar · Borrar · Quitar selección»), en el lugar que le\n// toca según el equipo. Una sola pieza para que cada lista con casillas (la tabla de servidor, una propia) no repita dónde\n// va cada cosa:\n// - escritorio, y el teléfono sin barra de pestañas abajo (el tema de MUI sin vidrio): un renglón donde se pone la pieza,\n// cada acción como botón con su texto;\n// - teléfono con vidrio, o minimalista con cápsulas (usesMobileTabBar), según phoneLayout (SELECTION_PHONE_LAYOUT):\n// top (por omisión): arriba, en la barra flotante, «5 elegidos ✕» y las acciones como íconos en su propia cápsula,\n// como Apple separa los grupos; las que no caben, al «⋯» (BarActions);\n// en los dos, mientras hay algo elegido, el renglón de arriba queda solo para lo elegido (BarActions exclusive): las\n// demás cápsulas de la pantalla y la lupa se esconden, como el modo de edición del iPhone;\n// split: como iOS en modo de edición (Fotos, Mail, Archivos): arriba «5 elegidos ✕» y abajo, encima de las pestañas,\n// una cápsula fija con las acciones, donde llega el pulgar. Mientras se ve, pide su alto al marco\n// (FLOATING_BOTTOM_SPACE_CSS_VARIABLE) para no tapar el final de la página.\n// Lo pidió el autor: en el teléfono, configurable, y por omisión todo arriba.\n//\n// Con vidrio, la cápsula de arriba deja sus botones del color del texto (BarCapsule: la regla de la barra); abajo, una acción\n// destructiva (color: 'error') conserva su rojo, como pide la guía de Apple para lo que borra.\n//\n// Cada acción puede regresar una promesa: mientras corre, las demás se apagan (nadie cancela dos veces lo mismo).\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaSelectionActions con defaultProps y\n// styleOverrides de sus partes: bar, count, clear, phoneCount, phoneActions.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useState} from 'react';\nimport Box from '@mui/material/Box';\nimport Button from '@mui/material/Button';\nimport GlobalStyles from '@mui/material/GlobalStyles';\nimport IconButton from '@mui/material/IconButton';\nimport Stack from '@mui/material/Stack';\nimport Tooltip from '@mui/material/Tooltip';\nimport Typography from '@mui/material/Typography';\nimport {useTheme} from '@mui/material/styles';\nimport CloseRoundedIcon from '@mui/icons-material/CloseRounded';\n\nimport BarActionButton from '@/appShell/BarActionButton.jsx';\nimport BarActions from '@/appShell/BarActions.jsx';\nimport BarCapsule from '@/appShell/BarCapsule.jsx';\nimport {\n FLOATING_BOTTOM_SPACE_CSS_VARIABLE, GLASS_PANEL_GAP_PIXELS, PHONE_MEDIA_QUERY, aboveMobileTabBarOf,\n} from '@/appShell/shellSizes.js';\nimport {usesMobileTabBar} from '@/appShell/tabBarRule.js';\nimport {minimalFloatingStylesOf} from '@/theme/components/minimalComponents.js';\nimport {glassStylesOf, isGlassTheme} from '@/theme/glassSurface.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {piecePart, usePieceProps} from '@/theme/themePieces.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaSelectionActions)\nconst THEME_NAME = 'CalcitaSelectionActions';\nconst SelectionBar = piecePart(THEME_NAME, 'Bar', Stack);\nconst SelectionCount = piecePart(THEME_NAME, 'Count', Typography);\nconst SelectionClear = piecePart(THEME_NAME, 'Clear', Button);\nconst SelectionPhoneCount = piecePart(THEME_NAME, 'PhoneCount', Typography);\nconst SelectionPhoneActions = piecePart(THEME_NAME, 'PhoneActions', Box);\n\n/** Dónde van las acciones de lo elegido en el teléfono con barra de pestañas abajo: todo arriba o divididas (iOS). */\nexport const SELECTION_PHONE_LAYOUT = Object.freeze({top: 'top', split: 'split'});\n\n// lo que se separa la cápsula de abajo de la barra de pestañas (px), lo mismo que el aviso de versión nueva\nconst FLOATING_GAP_PIXELS = 8;\n// lo que pide al marco mientras flota abajo: su alto (48) y su separación, con aire\nconst FLOATING_BOTTOM_SPACE_PIXELS = 64;\n// lo que solo se ve en el teléfono\nconst PHONE_ONLY_SX = {display: 'none', [PHONE_MEDIA_QUERY]: {display: 'flex'}};\n\n/**\n * ¿En el teléfono lo elegido sale de su renglón (arriba o abajo)? La misma regla que la barra de pestañas de abajo: con ella,\n * el renglón de la pantalla se queda con lo suyo (el conteo de la tabla) y las acciones flotan.\n *\n * @param {object} theme\n * @returns {boolean}\n */\nexport function selectionFloatsOnPhone(theme) {\n return usesMobileTabBar(theme);\n}\n\n/**\n * La cápsula fija abajo, centrada en la ventana, encima de la barra de pestañas.\n *\n * @param {object} theme\n * @returns {object}\n */\nfunction bottomCapsuleSxOf(theme) {\n return {\n ...(isGlassTheme(theme) ? glassStylesOf(theme) : minimalFloatingStylesOf(theme)),\n position: 'fixed',\n left: '50%',\n transform: 'translateX(-50%)',\n zIndex: theme.zIndex.appBar,\n alignItems: 'center',\n gap: 0.5,\n width: 'max-content',\n maxWidth: `calc(100% - ${GLASS_PANEL_GAP_PIXELS * 2}px)`,\n borderRadius: '999px',\n p: 0.5,\n ...aboveMobileTabBarOf(FLOATING_GAP_PIXELS),\n };\n}\n\n/**\n * @param {object} props\n * @param {string} props.countText «5 pedidos elegidos», ya traducido. La pieza se pinta mientras la pantalla la ponga: con\n * nada elegido, no la ponga\n * @param {Array<{key: string, label: string, icon?: import('react').ReactNode, color?: 'inherit'|'primary'|'secondary'|'error'|'info'|'success'|'warning', disabled?: boolean, onClick: () => unknown}>} [props.actions]\n * label ya traducido; en el teléfono se ve el ícono (sin él, el texto). onClick puede regresar una promesa\n * @param {() => void} props.onClear quitar la selección\n * @param {'top'|'split'} [props.phoneLayout] SELECTION_PHONE_LAYOUT; por omisión todo arriba\n * @param {object} [props.texts] los textos que cambian, con la forma de `common`: {selection: {clear, actionsLabel, summaryLabel}}\n * @param {object} [props.sx] estilos del renglón\n */\nexport default function SelectionActions(props) {\n // lo que el proyecto puso en su tema (components.CalcitaSelectionActions.defaultProps) y encima lo que llega aquí\n const {countText, actions = [], onClear, phoneLayout = SELECTION_PHONE_LAYOUT.top, texts, sx} = usePieceProps(props, THEME_NAME);\n const theme = useTheme();\n const {t} = useToolkitTranslation('common', texts);\n const [runningActionKey, setRunningActionKey] = useState(null);\n const floatsOnPhone = selectionFloatsOnPhone(theme);\n const extraSx = Array.isArray(sx) ? sx : [sx];\n\n const runAction = async (action) => {\n setRunningActionKey(action.key);\n try {\n await action.onClick();\n } finally {\n setRunningActionKey(null);\n }\n };\n const isDisabledOf = (action) => runningActionKey !== null || Boolean(action.disabled);\n\n const clearButton = (\n <Tooltip title={t('selection.clear')}>\n <IconButton aria-label={t('selection.clear')} onClick={onClear}>\n <CloseRoundedIcon />\n </IconButton>\n </Tooltip>\n );\n\n // el renglón: escritorio siempre; en el teléfono, solo si ahí no flota\n const row = (\n <SelectionBar role=\"toolbar\" aria-label={t('selection.actionsLabel')} direction=\"row\" spacing={1} useFlexGap\n ownSx={[{flexGrow: 1, alignItems: 'center', flexWrap: 'wrap', minHeight: 32},\n floatsOnPhone && {[PHONE_MEDIA_QUERY]: {display: 'none'}}, ...extraSx]}>\n <SelectionCount variant=\"body2\" ownSx={{fontWeight: 600, mr: 1}}>{countText}</SelectionCount>\n {actions.map((action) => (\n <Button key={action.key} size=\"small\" color={action.color ?? 'primary'} startIcon={action.icon}\n disabled={isDisabledOf(action)} onClick={() => runAction(action)}>\n {action.label}\n </Button>\n ))}\n <SelectionClear size=\"small\" color=\"inherit\" startIcon={<CloseRoundedIcon />} onClick={onClear} ownSx={{ml: 'auto'}}>\n {t('selection.clear')}\n </SelectionClear>\n </SelectionBar>\n );\n\n if (!floatsOnPhone) {\n return row;\n }\n\n const showsBottomCapsule = phoneLayout === SELECTION_PHONE_LAYOUT.split && actions.length > 0;\n\n return (\n <>\n {row}\n {/* arriba: con las demás cápsulas flotantes de la pantalla (BarActions), antes que ellas. Sin label en BarActions:\n el nombre ya lo lleva la cápsula (dos grupos anidados con el mismo nombre se leen dos veces) */}\n <BarActions bare exclusive order={-2} sx={PHONE_ONLY_SX} inlineSx={PHONE_ONLY_SX}>\n <BarCapsule aria-label={t('selection.summaryLabel')} sx={{pl: 2}}>\n <SelectionPhoneCount variant=\"body2\" role=\"status\" aria-live=\"polite\" ownSx={{fontWeight: 600, whiteSpace: 'nowrap', mr: 0.5}}>\n {countText}\n </SelectionPhoneCount>\n {clearButton}\n </BarCapsule>\n </BarActions>\n {phoneLayout === SELECTION_PHONE_LAYOUT.top && actions.length > 0 && (\n <BarActions label={t('selection.actionsLabel')} exclusive order={-1} sx={PHONE_ONLY_SX} inlineSx={PHONE_ONLY_SX}>\n {actions.map((action) => (\n <BarActionButton key={action.key} label={action.label} icon={action.icon} color={action.color}\n disabled={isDisabledOf(action)} onClick={() => runAction(action)} />\n ))}\n </BarActions>\n )}\n {showsBottomCapsule && (\n <>\n <GlobalStyles styles={{':root': {[FLOATING_BOTTOM_SPACE_CSS_VARIABLE]: `${FLOATING_BOTTOM_SPACE_PIXELS}px`}}} />\n <SelectionPhoneActions role=\"toolbar\" aria-label={t('selection.actionsLabel')} ownSx={[bottomCapsuleSxOf, PHONE_ONLY_SX]}>\n {actions.map((action) => (action.icon ? (\n <Tooltip key={action.key} title={action.label}>\n {/* el span deja ver el tooltip aunque el botón esté apagado */}\n <span>\n <IconButton aria-label={action.label} color={action.color ?? 'default'} disabled={isDisabledOf(action)}\n onClick={() => runAction(action)}>\n {action.icon}\n </IconButton>\n </span>\n </Tooltip>\n ) : (\n <Button key={action.key} size=\"small\" color={action.color ?? 'primary'} disabled={isDisabledOf(action)}\n onClick={() => runAction(action)}>\n {action.label}\n </Button>\n )))}\n </SelectionPhoneActions>\n </>\n )}\n </>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -10,7 +10,13 @@ import TextField from "@mui/material/TextField";
10
10
  import Typography from "@mui/material/Typography";
11
11
  import DateRangeOutlinedIcon from "@mui/icons-material/DateRangeOutlined";
12
12
  import { useFormatters } from "../../toolkit/useFormatters.js";
13
- function DateRangeFilter({ from = "", to = "", onChange, label, texts }) {
13
+ import { piecePart, usePieceProps } from "../../theme/themePieces.js";
14
+ const THEME_NAME = "CalcitaDateRangeFilter";
15
+ const RangeChip = piecePart(THEME_NAME, "Chip", Chip);
16
+ const RangePanel = piecePart(THEME_NAME, "Panel", Stack);
17
+ const RangeActions = piecePart(THEME_NAME, "Actions", Stack);
18
+ function DateRangeFilter(props) {
19
+ const { from = "", to = "", onChange, label, texts } = usePieceProps(props, THEME_NAME);
14
20
  const { t } = useToolkitTranslation("table", texts);
15
21
  const { formatDate } = useFormatters();
16
22
  const [anchorElement, setAnchorElement] = useState(null);
@@ -33,7 +39,7 @@ function DateRangeFilter({ from = "", to = "", onChange, label, texts }) {
33
39
  const summary = isActive ? t("dateRange.summary", { from: from ? formatDate(from) : "…", to: to ? formatDate(to) : t("dateRange.today") }) : label ?? t("dateRange.label");
34
40
  return /* @__PURE__ */ jsxs(Fragment, { children: [
35
41
  /* @__PURE__ */ jsx(
36
- Chip,
42
+ RangeChip,
37
43
  {
38
44
  icon: /* @__PURE__ */ jsx(DateRangeOutlinedIcon, {}),
39
45
  label: summary,
@@ -42,10 +48,10 @@ function DateRangeFilter({ from = "", to = "", onChange, label, texts }) {
42
48
  variant: isActive ? "filled" : "outlined",
43
49
  color: isActive ? "primary" : "default",
44
50
  onDelete: isActive ? clear : void 0,
45
- sx: { flex: "0 0 auto", height: 32 }
51
+ ownSx: { flex: "0 0 auto", height: 32 }
46
52
  }
47
53
  ),
48
- /* @__PURE__ */ jsx(Popover, { open: Boolean(anchorElement), anchorEl: anchorElement, onClose: close, anchorOrigin: { vertical: "bottom", horizontal: "left" }, children: /* @__PURE__ */ jsxs(Stack, { spacing: 2, sx: { p: 2, width: 280 }, children: [
54
+ /* @__PURE__ */ jsx(Popover, { open: Boolean(anchorElement), anchorEl: anchorElement, onClose: close, anchorOrigin: { vertical: "bottom", horizontal: "left" }, children: /* @__PURE__ */ jsxs(RangePanel, { spacing: 2, ownSx: { p: 2, width: 280 }, children: [
49
55
  /* @__PURE__ */ jsx(Typography, { variant: "subtitle2", children: t("dateRange.title") }),
50
56
  /* @__PURE__ */ jsx(
51
57
  TextField,
@@ -74,7 +80,7 @@ function DateRangeFilter({ from = "", to = "", onChange, label, texts }) {
74
80
  slotProps: { inputLabel: { shrink: true }, htmlInput: { min: draft.from || void 0 } }
75
81
  }
76
82
  ),
77
- /* @__PURE__ */ jsxs(Stack, { direction: "row", spacing: 1, sx: { justifyContent: "flex-end" }, children: [
83
+ /* @__PURE__ */ jsxs(RangeActions, { direction: "row", spacing: 1, ownSx: { justifyContent: "flex-end" }, children: [
78
84
  isActive && /* @__PURE__ */ jsx(Button, { onClick: clear, children: t("dateRange.clear") }),
79
85
  /* @__PURE__ */ jsx(Button, { variant: "contained", onClick: apply, disabled: isInvalid || !(draft.from || draft.to), children: t("dateRange.apply") })
80
86
  ] })
@@ -1 +1 @@
1
- {"version":3,"file":"DateRangeFilter.js","sources":["../../../src/ui/filters/DateRangeFilter.jsx"],"sourcesContent":["// Un rango de fechas personalizado, como chip al lado de los accesos rápidos.\n//\n// Viaja como AAAA-MM-DD (día, no instante). Convertirlo a \"inicio del día\" y \"fin del día\" lo hace el SERVIDOR con la\n// zona del negocio: si lo hiciera el navegador con la suya, alguien en otra zona pediría otro rango sin saberlo.\n//\n// El calendario es el <input type=\"date\"> del TextField de MUI y no el DatePicker de @mui/x-date-pickers con moment,\n// para no sumar dos dependencias (x-date-pickers y moment) que ninguna\n// pantalla del 22-sep necesita. Si el calendario nativo estorba (Safari de escritorio), se cambia SOLO este archivo.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useState} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport Button from '@mui/material/Button';\nimport Chip from '@mui/material/Chip';\nimport Popover from '@mui/material/Popover';\nimport Stack from '@mui/material/Stack';\nimport TextField from '@mui/material/TextField';\nimport Typography from '@mui/material/Typography';\nimport DateRangeOutlinedIcon from '@mui/icons-material/DateRangeOutlined';\n\nimport {useFormatters} from '@/toolkit/useFormatters.js';\n\n/**\n * @param {object} props\n * @param {string} [props.from] AAAA-MM-DD o '' si no hay\n * @param {string} [props.to] AAAA-MM-DD o '' si no hay\n * @param {(range: {from: string, to: string}) => void} props.onChange '' para quitar\n * @param {string} [props.label] ya traducido; por defecto \"Personalizado\"\n * @param {object} [props.texts] los textos que cambian, con la forma de `table` en los textos del paquete\n */\nexport default function DateRangeFilter({from = '', to = '', onChange, label, texts}) {\n const {t} = useToolkitTranslation('table', texts);\n const {formatDate} = useFormatters();\n const [anchorElement, setAnchorElement] = useState(null);\n const [draft, setDraft] = useState({from, to});\n const isActive = Boolean(from || to);\n // AAAA-MM-DD se compara bien como texto\n const isInvalid = Boolean(draft.from && draft.to && draft.from > draft.to);\n\n const open = (event) => {\n setDraft({from, to});\n setAnchorElement(event.currentTarget);\n };\n const close = () => setAnchorElement(null);\n const apply = () => {\n onChange(draft);\n close();\n };\n const clear = () => {\n onChange({from: '', to: ''});\n close();\n };\n\n const summary = isActive\n ? t('dateRange.summary', {from: from ? formatDate(from) : '…', to: to ? formatDate(to) : t('dateRange.today')})\n : (label ?? t('dateRange.label'));\n\n return (\n <>\n <Chip icon={<DateRangeOutlinedIcon />} label={summary} clickable onClick={open}\n variant={isActive ? 'filled' : 'outlined'} color={isActive ? 'primary' : 'default'}\n onDelete={isActive ? clear : undefined} sx={{flex: '0 0 auto', height: 32}} />\n <Popover open={Boolean(anchorElement)} anchorEl={anchorElement} onClose={close} anchorOrigin={{vertical: 'bottom', horizontal: 'left'}}>\n <Stack spacing={2} sx={{p: 2, width: 280}}>\n <Typography variant=\"subtitle2\">{t('dateRange.title')}</Typography>\n <TextField type=\"date\" size=\"small\" fullWidth label={t('dateRange.from')} value={draft.from}\n helperText={t('dateRange.fromHelp')}\n onChange={(event) => setDraft((currentDraft) => ({...currentDraft, from: event.target.value}))}\n slotProps={{inputLabel: {shrink: true}, htmlInput: {max: draft.to || undefined}}} />\n <TextField type=\"date\" size=\"small\" fullWidth label={t('dateRange.to')} value={draft.to} error={isInvalid}\n helperText={isInvalid ? t('dateRange.invalid') : t('dateRange.toHelp')}\n onChange={(event) => setDraft((currentDraft) => ({...currentDraft, to: event.target.value}))}\n slotProps={{inputLabel: {shrink: true}, htmlInput: {min: draft.from || undefined}}} />\n <Stack direction=\"row\" spacing={1} sx={{justifyContent: 'flex-end'}}>\n {isActive && <Button onClick={clear}>{t('dateRange.clear')}</Button>}\n <Button variant=\"contained\" onClick={apply} disabled={isInvalid || !(draft.from || draft.to)}>\n {t('dateRange.apply')}\n </Button>\n </Stack>\n </Stack>\n </Popover>\n </>\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"DateRangeFilter.js","sources":["../../../src/ui/filters/DateRangeFilter.jsx"],"sourcesContent":["// Un rango de fechas personalizado, como chip al lado de los accesos rápidos.\n//\n// Viaja como AAAA-MM-DD (día, no instante). Convertirlo a \"inicio del día\" y \"fin del día\" lo hace el SERVIDOR con la\n// zona del negocio: si lo hiciera el navegador con la suya, alguien en otra zona pediría otro rango sin saberlo.\n//\n// El calendario es el <input type=\"date\"> del TextField de MUI y no el DatePicker de @mui/x-date-pickers con moment,\n// para no sumar dos dependencias (x-date-pickers y moment) que ninguna\n// pantalla del 22-sep necesita. Si el calendario nativo estorba (Safari de escritorio), se cambia SOLO este archivo.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaDateRangeFilter con defaultProps y\n// styleOverrides de sus partes: chip, panel, actions.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useState} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport Button from '@mui/material/Button';\nimport Chip from '@mui/material/Chip';\nimport Popover from '@mui/material/Popover';\nimport Stack from '@mui/material/Stack';\nimport TextField from '@mui/material/TextField';\nimport Typography from '@mui/material/Typography';\nimport DateRangeOutlinedIcon from '@mui/icons-material/DateRangeOutlined';\n\nimport {useFormatters} from '@/toolkit/useFormatters.js';\nimport {piecePart, usePieceProps} from '@/theme/themePieces.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaDateRangeFilter)\nconst THEME_NAME = 'CalcitaDateRangeFilter';\nconst RangeChip = piecePart(THEME_NAME, 'Chip', Chip);\nconst RangePanel = piecePart(THEME_NAME, 'Panel', Stack);\nconst RangeActions = piecePart(THEME_NAME, 'Actions', Stack);\n\n/**\n * @param {object} props\n * @param {string} [props.from] AAAA-MM-DD o '' si no hay\n * @param {string} [props.to] AAAA-MM-DD o '' si no hay\n * @param {(range: {from: string, to: string}) => void} props.onChange '' para quitar\n * @param {string} [props.label] ya traducido; por defecto \"Personalizado\"\n * @param {object} [props.texts] los textos que cambian, con la forma de `table` en los textos del paquete\n */\nexport default function DateRangeFilter(props) {\n // lo que el proyecto puso en su tema (components.CalcitaDateRangeFilter.defaultProps) y encima lo que llega aquí\n const {from = '', to = '', onChange, label, texts} = usePieceProps(props, THEME_NAME);\n const {t} = useToolkitTranslation('table', texts);\n const {formatDate} = useFormatters();\n const [anchorElement, setAnchorElement] = useState(null);\n const [draft, setDraft] = useState({from, to});\n const isActive = Boolean(from || to);\n // AAAA-MM-DD se compara bien como texto\n const isInvalid = Boolean(draft.from && draft.to && draft.from > draft.to);\n\n const open = (event) => {\n setDraft({from, to});\n setAnchorElement(event.currentTarget);\n };\n const close = () => setAnchorElement(null);\n const apply = () => {\n onChange(draft);\n close();\n };\n const clear = () => {\n onChange({from: '', to: ''});\n close();\n };\n\n const summary = isActive\n ? t('dateRange.summary', {from: from ? formatDate(from) : '…', to: to ? formatDate(to) : t('dateRange.today')})\n : (label ?? t('dateRange.label'));\n\n return (\n <>\n <RangeChip icon={<DateRangeOutlinedIcon />} label={summary} clickable onClick={open}\n variant={isActive ? 'filled' : 'outlined'} color={isActive ? 'primary' : 'default'}\n onDelete={isActive ? clear : undefined} ownSx={{flex: '0 0 auto', height: 32}} />\n <Popover open={Boolean(anchorElement)} anchorEl={anchorElement} onClose={close} anchorOrigin={{vertical: 'bottom', horizontal: 'left'}}>\n <RangePanel spacing={2} ownSx={{p: 2, width: 280}}>\n <Typography variant=\"subtitle2\">{t('dateRange.title')}</Typography>\n <TextField type=\"date\" size=\"small\" fullWidth label={t('dateRange.from')} value={draft.from}\n helperText={t('dateRange.fromHelp')}\n onChange={(event) => setDraft((currentDraft) => ({...currentDraft, from: event.target.value}))}\n slotProps={{inputLabel: {shrink: true}, htmlInput: {max: draft.to || undefined}}} />\n <TextField type=\"date\" size=\"small\" fullWidth label={t('dateRange.to')} value={draft.to} error={isInvalid}\n helperText={isInvalid ? t('dateRange.invalid') : t('dateRange.toHelp')}\n onChange={(event) => setDraft((currentDraft) => ({...currentDraft, to: event.target.value}))}\n slotProps={{inputLabel: {shrink: true}, htmlInput: {min: draft.from || undefined}}} />\n <RangeActions direction=\"row\" spacing={1} ownSx={{justifyContent: 'flex-end'}}>\n {isActive && <Button onClick={clear}>{t('dateRange.clear')}</Button>}\n <Button variant=\"contained\" onClick={apply} disabled={isInvalid || !(draft.from || draft.to)}>\n {t('dateRange.apply')}\n </Button>\n </RangeActions>\n </RangePanel>\n </Popover>\n </>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -7,27 +7,33 @@ import MenuItem from "@mui/material/MenuItem";
7
7
  import Select from "@mui/material/Select";
8
8
  import Stack from "@mui/material/Stack";
9
9
  import { rangeEndOptionsOf, rangeEndAfterStartChange } from "../../periodRanges.js";
10
+ import { piecePart, usePieceProps } from "../../theme/themePieces.js";
11
+ const THEME_NAME = "CalcitaPeriodRangeSelector";
12
+ const PeriodField = piecePart(THEME_NAME, "Field", FormControl);
13
+ const PeriodSelectPart = piecePart(THEME_NAME, "Select", Select);
14
+ const PeriodRoot = piecePart(THEME_NAME, "Root", Stack);
10
15
  const SELECT_MINIMUM_WIDTH = 210;
11
16
  function PeriodSelect({ id, label, value, options, onChange }) {
12
- return /* @__PURE__ */ jsxs(FormControl, { size: "small", sx: { minWidth: SELECT_MINIMUM_WIDTH, flex: "0 0 auto" }, children: [
17
+ return /* @__PURE__ */ jsxs(PeriodField, { size: "small", ownSx: { minWidth: SELECT_MINIMUM_WIDTH, flex: "0 0 auto" }, children: [
13
18
  /* @__PURE__ */ jsx(InputLabel, { id: `${id}-label`, children: label }),
14
19
  /* @__PURE__ */ jsx(
15
- Select,
20
+ PeriodSelectPart,
16
21
  {
17
22
  labelId: `${id}-label`,
18
23
  label,
19
24
  value: value ?? "",
20
25
  onChange: (event) => onChange(event.target.value),
21
- sx: { bgcolor: "background.paper" },
26
+ ownSx: { bgcolor: "background.paper" },
22
27
  renderValue: (selectedValue) => options.find((option) => option.value === selectedValue)?.label ?? "",
23
28
  children: options.map((option) => /* @__PURE__ */ jsx(MenuItem, { value: option.value, disabled: option.disabled, children: /* @__PURE__ */ jsx(ListItemText, { primary: option.label, secondary: option.disabled ? option.hint : void 0 }) }, option.value))
24
29
  }
25
30
  )
26
31
  ] });
27
32
  }
28
- function PeriodRangeSelector({ id, options, from, to, fromLabel, toLabel, onChange }) {
33
+ function PeriodRangeSelector(props) {
34
+ const { id, options, from, to, fromLabel, toLabel, onChange } = usePieceProps(props, THEME_NAME);
29
35
  const endOptions = rangeEndOptionsOf(options, from);
30
- return /* @__PURE__ */ jsxs(Stack, { direction: "row", spacing: 1.5, useFlexGap: true, sx: { alignItems: "center", flexWrap: "wrap" }, children: [
36
+ return /* @__PURE__ */ jsxs(PeriodRoot, { direction: "row", spacing: 1.5, useFlexGap: true, ownSx: { alignItems: "center", flexWrap: "wrap" }, children: [
31
37
  /* @__PURE__ */ jsx(
32
38
  PeriodSelect,
33
39
  {
@@ -1 +1 @@
1
- {"version":3,"file":"PeriodRangeSelector.js","sources":["../../../src/ui/filters/PeriodRangeSelector.jsx"],"sourcesContent":["// Un rango de periodos: «desde» y «hasta», cada uno una lista para elegir (de tal quincena a tal quincena, de tal mes a\n// tal mes). Se usa en lugar de un calendario porque los periodos no son días: solo se ofrecen los que existen, y los que no\n// se pueden elegir salen apagados con su razón.\n//\n// «Hasta» solo ofrece lo que hace un rango válido con «desde»: nada de antes y nada que salte un periodo apagado\n// (periodRanges.js). Al cambiar «desde», si el «hasta» que había ya no vale, se iguala a «desde».\n//\n// No sabe de quincenas ni de productos: recibe las opciones ya ordenadas y con sus textos traducidos.\n//\n// Genérico del paquete: function (regla de la casa).\nimport FormControl from '@mui/material/FormControl';\nimport InputLabel from '@mui/material/InputLabel';\nimport ListItemText from '@mui/material/ListItemText';\nimport MenuItem from '@mui/material/MenuItem';\nimport Select from '@mui/material/Select';\nimport Stack from '@mui/material/Stack';\n\nimport {rangeEndAfterStartChange, rangeEndOptionsOf} from '@/periodRanges.js';\n\n// ancho de cada lista: cabe «septiembre 2026 · Q2» sin cortarse\nconst SELECT_MINIMUM_WIDTH = 210;\n\n/**\n * Una lista del rango.\n *\n * @param {object} props\n * @param {string} props.id\n * @param {string} props.label\n * @param {string} props.value\n * @param {(value: string) => void} props.onChange\n * @param {Array<{value: string, label: string, disabled: boolean, hint?: string}>} props.options\n */\nfunction PeriodSelect({id, label, value, options, onChange}) {\n return (\n <FormControl size=\"small\" sx={{minWidth: SELECT_MINIMUM_WIDTH, flex: '0 0 auto'}}>\n <InputLabel id={`${id}-label`}>{label}</InputLabel>\n <Select labelId={`${id}-label`} label={label} value={value ?? ''} onChange={(event) => onChange(event.target.value)}\n sx={{bgcolor: 'background.paper'}}\n renderValue={(selectedValue) => options.find((option) => option.value === selectedValue)?.label ?? ''}>\n {options.map((option) => (\n <MenuItem key={option.value} value={option.value} disabled={option.disabled}>\n {/* la razón de una opción apagada va debajo de su nombre: «sin validar» se lee sin tooltip */}\n <ListItemText primary={option.label} secondary={option.disabled ? option.hint : undefined} />\n </MenuItem>\n ))}\n </Select>\n </FormControl>\n );\n}\n\n/**\n * @param {object} props\n * @param {string} props.id prefijo de los id de las dos listas (accesibilidad)\n * @param {Array<{value: string, label: string, disabled: boolean, hint?: string}>} props.options en orden; ya\n * traducidas\n * @param {string|null} props.from\n * @param {string|null} props.to\n * @param {string} props.fromLabel ya traducido\n * @param {string} props.toLabel ya traducido\n * @param {(range: {from: string, to: string}) => void} props.onChange\n */\nexport default function PeriodRangeSelector({id, options, from, to, fromLabel, toLabel, onChange}) {\n const endOptions = rangeEndOptionsOf(options, from);\n\n return (\n <Stack direction=\"row\" spacing={1.5} useFlexGap sx={{alignItems: 'center', flexWrap: 'wrap'}}>\n <PeriodSelect id={`${id}-from`} label={fromLabel} value={from} options={options}\n onChange={(nextFrom) => onChange({from: nextFrom, to: rangeEndAfterStartChange(options, nextFrom, to)})} />\n <PeriodSelect id={`${id}-to`} label={toLabel} value={to} options={endOptions}\n onChange={(nextTo) => onChange({from, to: nextTo})} />\n </Stack>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;AAoBA,MAAM,uBAAuB;AAY7B,SAAS,aAAa,EAAC,IAAI,OAAO,OAAO,SAAS,YAAW;AACzD,SACI,qBAAC,aAAA,EAAY,MAAK,SAAQ,IAAI,EAAC,UAAU,sBAAsB,MAAM,WAAA,GACjE,UAAA;AAAA,IAAA,oBAAC,YAAA,EAAW,IAAI,GAAG,EAAE,UAAW,UAAA,OAAM;AAAA,IACtC;AAAA,MAAC;AAAA,MAAA;AAAA,QAAO,SAAS,GAAG,EAAE;AAAA,QAAU;AAAA,QAAc,OAAO,SAAS;AAAA,QAAI,UAAU,CAAC,UAAU,SAAS,MAAM,OAAO,KAAK;AAAA,QAC1G,IAAI,EAAC,SAAS,mBAAA;AAAA,QACd,aAAa,CAAC,kBAAkB,QAAQ,KAAK,CAAC,WAAW,OAAO,UAAU,aAAa,GAAG,SAAS;AAAA,QACtG,UAAA,QAAQ,IAAI,CAAC,WACV,oBAAC,UAAA,EAA4B,OAAO,OAAO,OAAO,UAAU,OAAO,UAE/D,UAAA,oBAAC,gBAAa,SAAS,OAAO,OAAO,WAAW,OAAO,WAAW,OAAO,OAAO,OAAA,CAAW,EAAA,GAFhF,OAAO,KAGtB,CACH;AAAA,MAAA;AAAA,IAAA;AAAA,EACL,GACJ;AAER;AAaA,SAAwB,oBAAoB,EAAC,IAAI,SAAS,MAAM,IAAI,WAAW,SAAS,YAAW;AAC/F,QAAM,aAAa,kBAAkB,SAAS,IAAI;AAElD,SACI,qBAAC,OAAA,EAAM,WAAU,OAAM,SAAS,KAAK,YAAU,MAAC,IAAI,EAAC,YAAY,UAAU,UAAU,UACjF,UAAA;AAAA,IAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QAAa,IAAI,GAAG,EAAE;AAAA,QAAS,OAAO;AAAA,QAAW,OAAO;AAAA,QAAM;AAAA,QACjD,UAAU,CAAC,aAAa,SAAS,EAAC,MAAM,UAAU,IAAI,yBAAyB,SAAS,UAAU,EAAE,GAAE;AAAA,MAAA;AAAA,IAAA;AAAA,IACpH;AAAA,MAAC;AAAA,MAAA;AAAA,QAAa,IAAI,GAAG,EAAE;AAAA,QAAO,OAAO;AAAA,QAAS,OAAO;AAAA,QAAI,SAAS;AAAA,QACpD,UAAU,CAAC,WAAW,SAAS,EAAC,MAAM,IAAI,QAAO;AAAA,MAAA;AAAA,IAAA;AAAA,EAAG,GACtE;AAER;"}
1
+ {"version":3,"file":"PeriodRangeSelector.js","sources":["../../../src/ui/filters/PeriodRangeSelector.jsx"],"sourcesContent":["// Un rango de periodos: «desde» y «hasta», cada uno una lista para elegir (de tal quincena a tal quincena, de tal mes a\n// tal mes). Se usa en lugar de un calendario porque los periodos no son días: solo se ofrecen los que existen, y los que no\n// se pueden elegir salen apagados con su razón.\n//\n// «Hasta» solo ofrece lo que hace un rango válido con «desde»: nada de antes y nada que salte un periodo apagado\n// (periodRanges.js). Al cambiar «desde», si el «hasta» que había ya no vale, se iguala a «desde».\n//\n// No sabe de quincenas ni de productos: recibe las opciones ya ordenadas y con sus textos traducidos.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaPeriodRangeSelector con defaultProps y\n// styleOverrides de sus partes: field, select, root.\n//\n// Genérico del paquete: function (regla de la casa).\nimport FormControl from '@mui/material/FormControl';\nimport InputLabel from '@mui/material/InputLabel';\nimport ListItemText from '@mui/material/ListItemText';\nimport MenuItem from '@mui/material/MenuItem';\nimport Select from '@mui/material/Select';\nimport Stack from '@mui/material/Stack';\n\nimport {rangeEndAfterStartChange, rangeEndOptionsOf} from '@/periodRanges.js';\nimport {piecePart, usePieceProps} from '@/theme/themePieces.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaPeriodRangeSelector)\nconst THEME_NAME = 'CalcitaPeriodRangeSelector';\nconst PeriodField = piecePart(THEME_NAME, 'Field', FormControl);\nconst PeriodSelectPart = piecePart(THEME_NAME, 'Select', Select);\nconst PeriodRoot = piecePart(THEME_NAME, 'Root', Stack);\n\n// ancho de cada lista: cabe «septiembre 2026 · Q2» sin cortarse\nconst SELECT_MINIMUM_WIDTH = 210;\n\n/**\n * Una lista del rango.\n *\n * @param {object} props\n * @param {string} props.id\n * @param {string} props.label\n * @param {string} props.value\n * @param {(value: string) => void} props.onChange\n * @param {Array<{value: string, label: string, disabled: boolean, hint?: string}>} props.options\n */\nfunction PeriodSelect({id, label, value, options, onChange}) {\n return (\n <PeriodField size=\"small\" ownSx={{minWidth: SELECT_MINIMUM_WIDTH, flex: '0 0 auto'}}>\n <InputLabel id={`${id}-label`}>{label}</InputLabel>\n <PeriodSelectPart labelId={`${id}-label`} label={label} value={value ?? ''} onChange={(event) => onChange(event.target.value)}\n ownSx={{bgcolor: 'background.paper'}}\n renderValue={(selectedValue) => options.find((option) => option.value === selectedValue)?.label ?? ''}>\n {options.map((option) => (\n <MenuItem key={option.value} value={option.value} disabled={option.disabled}>\n {/* la razón de una opción apagada va debajo de su nombre: «sin validar» se lee sin tooltip */}\n <ListItemText primary={option.label} secondary={option.disabled ? option.hint : undefined} />\n </MenuItem>\n ))}\n </PeriodSelectPart>\n </PeriodField>\n );\n}\n\n/**\n * @param {object} props\n * @param {string} props.id prefijo de los id de las dos listas (accesibilidad)\n * @param {Array<{value: string, label: string, disabled: boolean, hint?: string}>} props.options en orden; ya\n * traducidas\n * @param {string|null} props.from\n * @param {string|null} props.to\n * @param {string} props.fromLabel ya traducido\n * @param {string} props.toLabel ya traducido\n * @param {(range: {from: string, to: string}) => void} props.onChange\n */\nexport default function PeriodRangeSelector(props) {\n // lo que el proyecto puso en su tema (components.CalcitaPeriodRangeSelector.defaultProps) y encima lo que llega aquí\n const {id, options, from, to, fromLabel, toLabel, onChange} = usePieceProps(props, THEME_NAME);\n const endOptions = rangeEndOptionsOf(options, from);\n\n return (\n <PeriodRoot direction=\"row\" spacing={1.5} useFlexGap ownSx={{alignItems: 'center', flexWrap: 'wrap'}}>\n <PeriodSelect id={`${id}-from`} label={fromLabel} value={from} options={options}\n onChange={(nextFrom) => onChange({from: nextFrom, to: rangeEndAfterStartChange(options, nextFrom, to)})} />\n <PeriodSelect id={`${id}-to`} label={toLabel} value={to} options={endOptions}\n onChange={(nextTo) => onChange({from, to: nextTo})} />\n </PeriodRoot>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;AAwBA,MAAM,aAAa;AACnB,MAAM,cAAc,UAAU,YAAY,SAAS,WAAW;AAC9D,MAAM,mBAAmB,UAAU,YAAY,UAAU,MAAM;AAC/D,MAAM,aAAa,UAAU,YAAY,QAAQ,KAAK;AAGtD,MAAM,uBAAuB;AAY7B,SAAS,aAAa,EAAC,IAAI,OAAO,OAAO,SAAS,YAAW;AACzD,SACI,qBAAC,aAAA,EAAY,MAAK,SAAQ,OAAO,EAAC,UAAU,sBAAsB,MAAM,WAAA,GACpE,UAAA;AAAA,IAAA,oBAAC,YAAA,EAAW,IAAI,GAAG,EAAE,UAAW,UAAA,OAAM;AAAA,IACtC;AAAA,MAAC;AAAA,MAAA;AAAA,QAAiB,SAAS,GAAG,EAAE;AAAA,QAAU;AAAA,QAAc,OAAO,SAAS;AAAA,QAAI,UAAU,CAAC,UAAU,SAAS,MAAM,OAAO,KAAK;AAAA,QACpH,OAAO,EAAC,SAAS,mBAAA;AAAA,QACjB,aAAa,CAAC,kBAAkB,QAAQ,KAAK,CAAC,WAAW,OAAO,UAAU,aAAa,GAAG,SAAS;AAAA,QACtG,UAAA,QAAQ,IAAI,CAAC,WACV,oBAAC,UAAA,EAA4B,OAAO,OAAO,OAAO,UAAU,OAAO,UAE/D,UAAA,oBAAC,gBAAa,SAAS,OAAO,OAAO,WAAW,OAAO,WAAW,OAAO,OAAO,OAAA,CAAW,EAAA,GAFhF,OAAO,KAGtB,CACH;AAAA,MAAA;AAAA,IAAA;AAAA,EACL,GACJ;AAER;AAaA,SAAwB,oBAAoB,OAAO;AAE/C,QAAM,EAAC,IAAI,SAAS,MAAM,IAAI,WAAW,SAAS,SAAA,IAAY,cAAc,OAAO,UAAU;AAC7F,QAAM,aAAa,kBAAkB,SAAS,IAAI;AAElD,SACI,qBAAC,YAAA,EAAW,WAAU,OAAM,SAAS,KAAK,YAAU,MAAC,OAAO,EAAC,YAAY,UAAU,UAAU,UACzF,UAAA;AAAA,IAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QAAa,IAAI,GAAG,EAAE;AAAA,QAAS,OAAO;AAAA,QAAW,OAAO;AAAA,QAAM;AAAA,QACjD,UAAU,CAAC,aAAa,SAAS,EAAC,MAAM,UAAU,IAAI,yBAAyB,SAAS,UAAU,EAAE,GAAE;AAAA,MAAA;AAAA,IAAA;AAAA,IACpH;AAAA,MAAC;AAAA,MAAA;AAAA,QAAa,IAAI,GAAG,EAAE;AAAA,QAAO,OAAO;AAAA,QAAS,OAAO;AAAA,QAAI,SAAS;AAAA,QACpD,UAAU,CAAC,WAAW,SAAS,EAAC,MAAM,IAAI,QAAO;AAAA,MAAA;AAAA,IAAA;AAAA,EAAG,GACtE;AAER;"}
@@ -7,6 +7,13 @@ import { alpha } from "@mui/material/styles";
7
7
  import Chip from "@mui/material/Chip";
8
8
  import Typography from "@mui/material/Typography";
9
9
  import ScrollableRow from "../layout/ScrollableRow.js";
10
+ import { piecePart, usePieceProps } from "../../theme/themePieces.js";
11
+ const THEME_NAME = "CalcitaQuickFilters";
12
+ const QuickRoot = piecePart(THEME_NAME, "Root", Box);
13
+ const QuickCaption = piecePart(THEME_NAME, "Caption", Typography);
14
+ const QuickChip = piecePart(THEME_NAME, "Chip", Chip);
15
+ const QuickChipLabel = piecePart(THEME_NAME, "ChipLabel", Box);
16
+ const QuickChipCount = piecePart(THEME_NAME, "ChipCount", Box);
10
17
  const ACTIVE_COUNT_VEIL_OPACITY = 0.18;
11
18
  function activeCountVeilOf(theme) {
12
19
  if (theme.vars) {
@@ -24,7 +31,8 @@ function compactCount(count) {
24
31
  }
25
32
  return String(count);
26
33
  }
27
- function QuickFilters({ value, onChange, options = [], caption, ariaLabel, extra, texts }) {
34
+ function QuickFilters(props) {
35
+ const { value, onChange, options = [], caption, ariaLabel, extra, texts } = usePieceProps(props, THEME_NAME);
28
36
  const { t } = useToolkitTranslation("table", texts);
29
37
  const activeChip = useRef(null);
30
38
  const isFirstRender = useRef(true);
@@ -39,13 +47,13 @@ function QuickFilters({ value, onChange, options = [], caption, ariaLabel, extra
39
47
  if (options.length <= 1) {
40
48
  return null;
41
49
  }
42
- return /* @__PURE__ */ jsxs(Box, { sx: { mb: 1 }, children: [
43
- caption && /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "text.secondary", sx: { px: 0.25 }, children: caption }),
50
+ return /* @__PURE__ */ jsxs(QuickRoot, { ownSx: { mb: 1 }, children: [
51
+ caption && /* @__PURE__ */ jsx(QuickCaption, { variant: "caption", color: "text.secondary", ownSx: { px: 0.25 }, children: caption }),
44
52
  /* @__PURE__ */ jsxs(ScrollableRow, { role: "tablist", "aria-label": ariaLabel, children: [
45
53
  options.map((option) => {
46
54
  const isActive = option.value === value;
47
55
  return /* @__PURE__ */ jsx(
48
- Chip,
56
+ QuickChip,
49
57
  {
50
58
  ref: isActive ? activeChip : null,
51
59
  role: "tab",
@@ -55,14 +63,14 @@ function QuickFilters({ value, onChange, options = [], caption, ariaLabel, extra
55
63
  onDelete: isActive && option.value ? () => onChange("") : void 0,
56
64
  variant: isActive ? "filled" : "outlined",
57
65
  color: isActive ? "primary" : "default",
58
- label: /* @__PURE__ */ jsxs(Box, { component: "span", sx: { display: "inline-flex", alignItems: "center", gap: 0.75 }, children: [
66
+ label: /* @__PURE__ */ jsxs(QuickChipLabel, { component: "span", ownSx: { display: "inline-flex", alignItems: "center", gap: 0.75 }, children: [
59
67
  /* @__PURE__ */ jsx("span", { children: option.label }),
60
68
  option.count !== null && option.count !== void 0 && /* @__PURE__ */ jsx(
61
- Box,
69
+ QuickChipCount,
62
70
  {
63
71
  component: "span",
64
72
  "aria-label": t("quickFilters.countAriaLabel", { count: option.count }),
65
- sx: (theme) => ({
73
+ ownSx: (theme) => ({
66
74
  px: 0.75,
67
75
  borderRadius: "6px",
68
76
  fontSize: 11,
@@ -76,7 +84,7 @@ function QuickFilters({ value, onChange, options = [], caption, ariaLabel, extra
76
84
  }
77
85
  )
78
86
  ] }),
79
- sx: { flex: "0 0 auto", height: 32 }
87
+ ownSx: { flex: "0 0 auto", height: 32 }
80
88
  },
81
89
  option.value || "__all__"
82
90
  );
@@ -1 +1 @@
1
- {"version":3,"file":"QuickFilters.js","sources":["../../../src/ui/filters/QuickFilters.jsx"],"sourcesContent":["// Accesos rápidos: una tira de chips donde se elige UNO (p. ej. el estatus de un pedido).\n//\n// Es el complemento de FilterChips, no lo mismo:\n// - FilterChips dice qué filtros están PUESTOS y sirve para quitarlos;\n// - esto dice qué opciones HAY y sirve para saltar entre ellas de un toque.\n//\n// El conteo de cada chip sale de las facetas del servidor, contadas sobre el conjunto COMPLETO y sin este filtro\n// puesto: al elegir una, las demás siguen diciendo cuántas tienen.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useEffect, useRef} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport Box from '@mui/material/Box';\nimport {alpha} from '@mui/material/styles';\nimport Chip from '@mui/material/Chip';\nimport Typography from '@mui/material/Typography';\n\nimport ScrollableRow from '@/ui/layout/ScrollableRow.jsx';\n\n\n// qué tan marcado va el velo del contador del filtro elegido\nconst ACTIVE_COUNT_VEIL_OPACITY = 0.18;\n\n/**\n * El velo del contador del filtro elegido: el color del texto base (negro en claro, blanco en oscuro) casi transparente.\n * Un tema con variables CSS trae ese color en theme.vars; el tema por omisión de MUI, en su modo.\n *\n * @param {object} theme tema de MUI\n * @returns {string} color CSS\n */\nfunction activeCountVeilOf(theme) {\n if (theme.vars) {\n return `rgba(${theme.vars.palette.common.onBackgroundChannel} / ${ACTIVE_COUNT_VEIL_OPACITY})`;\n }\n const baseTextColor = theme.palette.mode === 'dark' ? theme.palette.common.white : theme.palette.common.black;\n\n return alpha(baseTextColor, ACTIVE_COUNT_VEIL_OPACITY);\n}\n\n/** \"99+\" arriba de 99 y \"1.2k\" arriba de 999: el chip no se estira. */\nfunction compactCount(count) {\n if (count > 999) {\n return `${(count / 1000).toFixed(1)}k`;\n }\n if (count > 99) {\n return '99+';\n }\n return String(count);\n}\n\n/**\n * @param {object} props\n * @param {string} props.value opción activa ('' = todas)\n * @param {(value: string) => void} props.onChange\n * @param {Array<{value: string, label: import('react').ReactNode, count?: number}>} [props.options]\n * @param {import('react').ReactNode} [props.caption]\n * @param {string} [props.ariaLabel]\n * @param {import('react').ReactNode} [props.extra] al final de la tira (p. ej. un rango de fechas)\n * @param {object} [props.texts] los textos que cambian, con la forma de `table` en los textos del paquete\n */\nexport default function QuickFilters({value, onChange, options = [], caption, ariaLabel, extra, texts}) {\n const {t} = useToolkitTranslation('table', texts);\n const activeChip = useRef(null);\n const isFirstRender = useRef(true);\n\n // al cambiar la selección desde fuera (alguien limpió los filtros) el chip activo se trae a la vista; en el primer\n // render sin animación: una tira que se mueve sola al abrir la pantalla se siente rota\n useEffect(() => {\n if (!activeChip.current) {\n return;\n }\n const behavior = isFirstRender.current ? 'instant' : 'smooth';\n isFirstRender.current = false;\n activeChip.current.scrollIntoView({behavior, block: 'nearest', inline: 'center'});\n }, [value]);\n\n // guard clause: con una sola opción no hay entre qué saltar\n if (options.length <= 1) {\n return null;\n }\n\n return (\n <Box sx={{mb: 1}}>\n {caption && (\n <Typography variant=\"caption\" color=\"text.secondary\" sx={{px: 0.25}}>{caption}</Typography>\n )}\n <ScrollableRow role=\"tablist\" aria-label={ariaLabel}>\n {options.map((option) => {\n const isActive = option.value === value;\n return (\n <Chip\n key={option.value || '__all__'}\n ref={isActive ? activeChip : null}\n role=\"tab\"\n aria-selected={isActive}\n clickable\n onClick={() => onChange(isActive ? '' : option.value)}\n // el elegido trae su ×: \"volver a Todas\" no se descubre solo\n onDelete={isActive && option.value ? () => onChange('') : undefined}\n variant={isActive ? 'filled' : 'outlined'}\n color={isActive ? 'primary' : 'default'}\n label={(\n <Box component=\"span\" sx={{display: 'inline-flex', alignItems: 'center', gap: 0.75}}>\n <span>{option.label}</span>\n {option.count !== null && option.count !== undefined && (\n <Box component=\"span\"\n aria-label={t('quickFilters.countAriaLabel', {count: option.count})}\n // velo del color del texto base (negro en claro, blanco en oscuro) y no rgba fijo\n sx={(theme) => ({\n px: 0.75, borderRadius: '6px', fontSize: 11, fontWeight: 700, minWidth: 18,\n textAlign: 'center', color: 'inherit',\n bgcolor: isActive ? activeCountVeilOf(theme) : 'action.selected',\n })}>\n {compactCount(option.count)}\n </Box>\n )}\n </Box>\n )}\n sx={{flex: '0 0 auto', height: 32}}\n />\n );\n })}\n {extra}\n </ScrollableRow>\n </Box>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;AAqBA,MAAM,4BAA4B;AASlC,SAAS,kBAAkB,OAAO;AAC9B,MAAI,MAAM,MAAM;AACZ,WAAO,QAAQ,MAAM,KAAK,QAAQ,OAAO,mBAAmB,MAAM,yBAAyB;AAAA,EAC/F;AACA,QAAM,gBAAgB,MAAM,QAAQ,SAAS,SAAS,MAAM,QAAQ,OAAO,QAAQ,MAAM,QAAQ,OAAO;AAExG,SAAO,MAAM,eAAe,yBAAyB;AACzD;AAGA,SAAS,aAAa,OAAO;AACzB,MAAI,QAAQ,KAAK;AACb,WAAO,IAAI,QAAQ,KAAM,QAAQ,CAAC,CAAC;AAAA,EACvC;AACA,MAAI,QAAQ,IAAI;AACZ,WAAO;AAAA,EACX;AACA,SAAO,OAAO,KAAK;AACvB;AAYA,SAAwB,aAAa,EAAC,OAAO,UAAU,UAAU,IAAI,SAAS,WAAW,OAAO,SAAQ;AACpG,QAAM,EAAC,EAAA,IAAK,sBAAsB,SAAS,KAAK;AAChD,QAAM,aAAa,OAAO,IAAI;AAC9B,QAAM,gBAAgB,OAAO,IAAI;AAIjC,YAAU,MAAM;AACZ,QAAI,CAAC,WAAW,SAAS;AACrB;AAAA,IACJ;AACA,UAAM,WAAW,cAAc,UAAU,YAAY;AACrD,kBAAc,UAAU;AACxB,eAAW,QAAQ,eAAe,EAAC,UAAU,OAAO,WAAW,QAAQ,UAAS;AAAA,EACpF,GAAG,CAAC,KAAK,CAAC;AAGV,MAAI,QAAQ,UAAU,GAAG;AACrB,WAAO;AAAA,EACX;AAEA,8BACK,KAAA,EAAI,IAAI,EAAC,IAAI,KACT,UAAA;AAAA,IAAA,WACG,oBAAC,YAAA,EAAW,SAAQ,WAAU,OAAM,kBAAiB,IAAI,EAAC,IAAI,KAAA,GAAQ,UAAA,QAAA,CAAQ;AAAA,IAElF,qBAAC,eAAA,EAAc,MAAK,WAAU,cAAY,WACrC,UAAA;AAAA,MAAA,QAAQ,IAAI,CAAC,WAAW;AACrB,cAAM,WAAW,OAAO,UAAU;AAClC,eACI;AAAA,UAAC;AAAA,UAAA;AAAA,YAEG,KAAK,WAAW,aAAa;AAAA,YAC7B,MAAK;AAAA,YACL,iBAAe;AAAA,YACf,WAAS;AAAA,YACT,SAAS,MAAM,SAAS,WAAW,KAAK,OAAO,KAAK;AAAA,YAEpD,UAAU,YAAY,OAAO,QAAQ,MAAM,SAAS,EAAE,IAAI;AAAA,YAC1D,SAAS,WAAW,WAAW;AAAA,YAC/B,OAAO,WAAW,YAAY;AAAA,YAC9B,OACI,qBAAC,KAAA,EAAI,WAAU,QAAO,IAAI,EAAC,SAAS,eAAe,YAAY,UAAU,KAAK,QAC1E,UAAA;AAAA,cAAA,oBAAC,QAAA,EAAM,iBAAO,MAAA,CAAM;AAAA,cACnB,OAAO,UAAU,QAAQ,OAAO,UAAU,UACvC;AAAA,gBAAC;AAAA,gBAAA;AAAA,kBAAI,WAAU;AAAA,kBACV,cAAY,EAAE,+BAA+B,EAAC,OAAO,OAAO,OAAM;AAAA,kBAElE,IAAI,CAAC,WAAW;AAAA,oBACZ,IAAI;AAAA,oBAAM,cAAc;AAAA,oBAAO,UAAU;AAAA,oBAAI,YAAY;AAAA,oBAAK,UAAU;AAAA,oBACxE,WAAW;AAAA,oBAAU,OAAO;AAAA,oBAC5B,SAAS,WAAW,kBAAkB,KAAK,IAAI;AAAA,kBAAA;AAAA,kBAEnD,UAAA,aAAa,OAAO,KAAK;AAAA,gBAAA;AAAA,cAAA;AAAA,YAC9B,GAER;AAAA,YAEJ,IAAI,EAAC,MAAM,YAAY,QAAQ,GAAA;AAAA,UAAE;AAAA,UA3B5B,OAAO,SAAS;AAAA,QAAA;AAAA,MA8BjC,CAAC;AAAA,MACA;AAAA,IAAA,EAAA,CACL;AAAA,EAAA,GACJ;AAER;"}
1
+ {"version":3,"file":"QuickFilters.js","sources":["../../../src/ui/filters/QuickFilters.jsx"],"sourcesContent":["// Accesos rápidos: una tira de chips donde se elige UNO (p. ej. el estatus de un pedido).\n//\n// Es el complemento de FilterChips, no lo mismo:\n// - FilterChips dice qué filtros están PUESTOS y sirve para quitarlos;\n// - esto dice qué opciones HAY y sirve para saltar entre ellas de un toque.\n//\n// El conteo de cada chip sale de las facetas del servidor, contadas sobre el conjunto COMPLETO y sin este filtro\n// puesto: al elegir una, las demás siguen diciendo cuántas tienen.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaQuickFilters con defaultProps y\n// styleOverrides de sus partes: root, caption, chip, chipLabel, chipCount.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useEffect, useRef} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport Box from '@mui/material/Box';\nimport {alpha} from '@mui/material/styles';\nimport Chip from '@mui/material/Chip';\nimport Typography from '@mui/material/Typography';\n\nimport ScrollableRow from '@/ui/layout/ScrollableRow.jsx';\nimport {piecePart, usePieceProps} from '@/theme/themePieces.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaQuickFilters)\nconst THEME_NAME = 'CalcitaQuickFilters';\nconst QuickRoot = piecePart(THEME_NAME, 'Root', Box);\nconst QuickCaption = piecePart(THEME_NAME, 'Caption', Typography);\nconst QuickChip = piecePart(THEME_NAME, 'Chip', Chip);\nconst QuickChipLabel = piecePart(THEME_NAME, 'ChipLabel', Box);\nconst QuickChipCount = piecePart(THEME_NAME, 'ChipCount', Box);\n\n\n// qué tan marcado va el velo del contador del filtro elegido\nconst ACTIVE_COUNT_VEIL_OPACITY = 0.18;\n\n/**\n * El velo del contador del filtro elegido: el color del texto base (negro en claro, blanco en oscuro) casi transparente.\n * Un tema con variables CSS trae ese color en theme.vars; el tema por omisión de MUI, en su modo.\n *\n * @param {object} theme tema de MUI\n * @returns {string} color CSS\n */\nfunction activeCountVeilOf(theme) {\n if (theme.vars) {\n return `rgba(${theme.vars.palette.common.onBackgroundChannel} / ${ACTIVE_COUNT_VEIL_OPACITY})`;\n }\n const baseTextColor = theme.palette.mode === 'dark' ? theme.palette.common.white : theme.palette.common.black;\n\n return alpha(baseTextColor, ACTIVE_COUNT_VEIL_OPACITY);\n}\n\n/** \"99+\" arriba de 99 y \"1.2k\" arriba de 999: el chip no se estira. */\nfunction compactCount(count) {\n if (count > 999) {\n return `${(count / 1000).toFixed(1)}k`;\n }\n if (count > 99) {\n return '99+';\n }\n return String(count);\n}\n\n/**\n * @param {object} props\n * @param {string} props.value opción activa ('' = todas)\n * @param {(value: string) => void} props.onChange\n * @param {Array<{value: string, label: import('react').ReactNode, count?: number}>} [props.options]\n * @param {import('react').ReactNode} [props.caption]\n * @param {string} [props.ariaLabel]\n * @param {import('react').ReactNode} [props.extra] al final de la tira (p. ej. un rango de fechas)\n * @param {object} [props.texts] los textos que cambian, con la forma de `table` en los textos del paquete\n */\nexport default function QuickFilters(props) {\n // lo que el proyecto puso en su tema (components.CalcitaQuickFilters.defaultProps) y encima lo que llega aquí\n const {value, onChange, options = [], caption, ariaLabel, extra, texts} = usePieceProps(props, THEME_NAME);\n const {t} = useToolkitTranslation('table', texts);\n const activeChip = useRef(null);\n const isFirstRender = useRef(true);\n\n // al cambiar la selección desde fuera (alguien limpió los filtros) el chip activo se trae a la vista; en el primer\n // render sin animación: una tira que se mueve sola al abrir la pantalla se siente rota\n useEffect(() => {\n if (!activeChip.current) {\n return;\n }\n const behavior = isFirstRender.current ? 'instant' : 'smooth';\n isFirstRender.current = false;\n activeChip.current.scrollIntoView({behavior, block: 'nearest', inline: 'center'});\n }, [value]);\n\n // guard clause: con una sola opción no hay entre qué saltar\n if (options.length <= 1) {\n return null;\n }\n\n return (\n <QuickRoot ownSx={{mb: 1}}>\n {caption && (\n <QuickCaption variant=\"caption\" color=\"text.secondary\" ownSx={{px: 0.25}}>{caption}</QuickCaption>\n )}\n <ScrollableRow role=\"tablist\" aria-label={ariaLabel}>\n {options.map((option) => {\n const isActive = option.value === value;\n return (\n <QuickChip\n key={option.value || '__all__'}\n ref={isActive ? activeChip : null}\n role=\"tab\"\n aria-selected={isActive}\n clickable\n onClick={() => onChange(isActive ? '' : option.value)}\n // el elegido trae su ×: \"volver a Todas\" no se descubre solo\n onDelete={isActive && option.value ? () => onChange('') : undefined}\n variant={isActive ? 'filled' : 'outlined'}\n color={isActive ? 'primary' : 'default'}\n label={(\n <QuickChipLabel component=\"span\" ownSx={{display: 'inline-flex', alignItems: 'center', gap: 0.75}}>\n <span>{option.label}</span>\n {option.count !== null && option.count !== undefined && (\n <QuickChipCount component=\"span\"\n aria-label={t('quickFilters.countAriaLabel', {count: option.count})}\n // velo del color del texto base (negro en claro, blanco en oscuro) y no rgba fijo\n ownSx={(theme) => ({\n px: 0.75, borderRadius: '6px', fontSize: 11, fontWeight: 700, minWidth: 18,\n textAlign: 'center', color: 'inherit',\n bgcolor: isActive ? activeCountVeilOf(theme) : 'action.selected',\n })}>\n {compactCount(option.count)}\n </QuickChipCount>\n )}\n </QuickChipLabel>\n )}\n ownSx={{flex: '0 0 auto', height: 32}}\n />\n );\n })}\n {extra}\n </ScrollableRow>\n </QuickRoot>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;AAwBA,MAAM,aAAa;AACnB,MAAM,YAAY,UAAU,YAAY,QAAQ,GAAG;AACnD,MAAM,eAAe,UAAU,YAAY,WAAW,UAAU;AAChE,MAAM,YAAY,UAAU,YAAY,QAAQ,IAAI;AACpD,MAAM,iBAAiB,UAAU,YAAY,aAAa,GAAG;AAC7D,MAAM,iBAAiB,UAAU,YAAY,aAAa,GAAG;AAI7D,MAAM,4BAA4B;AASlC,SAAS,kBAAkB,OAAO;AAC9B,MAAI,MAAM,MAAM;AACZ,WAAO,QAAQ,MAAM,KAAK,QAAQ,OAAO,mBAAmB,MAAM,yBAAyB;AAAA,EAC/F;AACA,QAAM,gBAAgB,MAAM,QAAQ,SAAS,SAAS,MAAM,QAAQ,OAAO,QAAQ,MAAM,QAAQ,OAAO;AAExG,SAAO,MAAM,eAAe,yBAAyB;AACzD;AAGA,SAAS,aAAa,OAAO;AACzB,MAAI,QAAQ,KAAK;AACb,WAAO,IAAI,QAAQ,KAAM,QAAQ,CAAC,CAAC;AAAA,EACvC;AACA,MAAI,QAAQ,IAAI;AACZ,WAAO;AAAA,EACX;AACA,SAAO,OAAO,KAAK;AACvB;AAYA,SAAwB,aAAa,OAAO;AAExC,QAAM,EAAC,OAAO,UAAU,UAAU,CAAA,GAAI,SAAS,WAAW,OAAO,MAAA,IAAS,cAAc,OAAO,UAAU;AACzG,QAAM,EAAC,EAAA,IAAK,sBAAsB,SAAS,KAAK;AAChD,QAAM,aAAa,OAAO,IAAI;AAC9B,QAAM,gBAAgB,OAAO,IAAI;AAIjC,YAAU,MAAM;AACZ,QAAI,CAAC,WAAW,SAAS;AACrB;AAAA,IACJ;AACA,UAAM,WAAW,cAAc,UAAU,YAAY;AACrD,kBAAc,UAAU;AACxB,eAAW,QAAQ,eAAe,EAAC,UAAU,OAAO,WAAW,QAAQ,UAAS;AAAA,EACpF,GAAG,CAAC,KAAK,CAAC;AAGV,MAAI,QAAQ,UAAU,GAAG;AACrB,WAAO;AAAA,EACX;AAEA,8BACK,WAAA,EAAU,OAAO,EAAC,IAAI,KAClB,UAAA;AAAA,IAAA,WACG,oBAAC,cAAA,EAAa,SAAQ,WAAU,OAAM,kBAAiB,OAAO,EAAC,IAAI,KAAA,GAAQ,UAAA,QAAA,CAAQ;AAAA,IAEvF,qBAAC,eAAA,EAAc,MAAK,WAAU,cAAY,WACrC,UAAA;AAAA,MAAA,QAAQ,IAAI,CAAC,WAAW;AACrB,cAAM,WAAW,OAAO,UAAU;AAClC,eACI;AAAA,UAAC;AAAA,UAAA;AAAA,YAEG,KAAK,WAAW,aAAa;AAAA,YAC7B,MAAK;AAAA,YACL,iBAAe;AAAA,YACf,WAAS;AAAA,YACT,SAAS,MAAM,SAAS,WAAW,KAAK,OAAO,KAAK;AAAA,YAEpD,UAAU,YAAY,OAAO,QAAQ,MAAM,SAAS,EAAE,IAAI;AAAA,YAC1D,SAAS,WAAW,WAAW;AAAA,YAC/B,OAAO,WAAW,YAAY;AAAA,YAC9B,OACI,qBAAC,gBAAA,EAAe,WAAU,QAAO,OAAO,EAAC,SAAS,eAAe,YAAY,UAAU,KAAK,QACxF,UAAA;AAAA,cAAA,oBAAC,QAAA,EAAM,iBAAO,MAAA,CAAM;AAAA,cACnB,OAAO,UAAU,QAAQ,OAAO,UAAU,UACvC;AAAA,gBAAC;AAAA,gBAAA;AAAA,kBAAe,WAAU;AAAA,kBACrB,cAAY,EAAE,+BAA+B,EAAC,OAAO,OAAO,OAAM;AAAA,kBAElE,OAAO,CAAC,WAAW;AAAA,oBACf,IAAI;AAAA,oBAAM,cAAc;AAAA,oBAAO,UAAU;AAAA,oBAAI,YAAY;AAAA,oBAAK,UAAU;AAAA,oBACxE,WAAW;AAAA,oBAAU,OAAO;AAAA,oBAC5B,SAAS,WAAW,kBAAkB,KAAK,IAAI;AAAA,kBAAA;AAAA,kBAEnD,UAAA,aAAa,OAAO,KAAK;AAAA,gBAAA;AAAA,cAAA;AAAA,YAC9B,GAER;AAAA,YAEJ,OAAO,EAAC,MAAM,YAAY,QAAQ,GAAA;AAAA,UAAE;AAAA,UA3B/B,OAAO,SAAS;AAAA,QAAA;AAAA,MA8BjC,CAAC;AAAA,MACA;AAAA,IAAA,EAAA,CACL;AAAA,EAAA,GACJ;AAER;"}