@stackcraftmx/calcita 0.51.0 → 0.53.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 (214) hide show
  1. package/README.md +73 -6
  2. package/cli/calcita.js +4 -2
  3. package/cli/checkTexts.js +66 -9
  4. package/dist/appShell/BarActionButton.js.map +1 -1
  5. package/dist/datePickers/DateInput.js +5 -3
  6. package/dist/datePickers/DateInput.js.map +1 -1
  7. package/dist/datePickers/DateTimeInput.js +6 -4
  8. package/dist/datePickers/DateTimeInput.js.map +1 -1
  9. package/dist/datePickers/FormDateField.js +1 -1
  10. package/dist/datePickers/FormDateField.js.map +1 -1
  11. package/dist/datePickers/FormDateTimeField.js +1 -1
  12. package/dist/datePickers/FormDateTimeField.js.map +1 -1
  13. package/dist/datePickers/FormMonthField.js +1 -1
  14. package/dist/datePickers/FormMonthField.js.map +1 -1
  15. package/dist/datePickers/FormTimeField.js +1 -1
  16. package/dist/datePickers/FormTimeField.js.map +1 -1
  17. package/dist/datePickers/MonthInput.js +5 -3
  18. package/dist/datePickers/MonthInput.js.map +1 -1
  19. package/dist/datePickers/PickerFrame.js +2 -2
  20. package/dist/datePickers/PickerFrame.js.map +1 -1
  21. package/dist/datePickers/TimeInput.js +6 -4
  22. package/dist/datePickers/TimeInput.js.map +1 -1
  23. package/dist/feedback/Toaster.js +11 -2
  24. package/dist/feedback/Toaster.js.map +1 -1
  25. package/dist/forms/CalcitaFormik.js +11 -0
  26. package/dist/forms/CalcitaFormik.js.map +1 -0
  27. package/dist/forms/FormAttachmentsField.js +1 -1
  28. package/dist/forms/FormAttachmentsField.js.map +1 -1
  29. package/dist/forms/FormCatalogField.js +1 -1
  30. package/dist/forms/FormCatalogField.js.map +1 -1
  31. package/dist/forms/FormCheckboxGroupField.js +1 -1
  32. package/dist/forms/FormCheckboxGroupField.js.map +1 -1
  33. package/dist/forms/FormColorField.js +1 -1
  34. package/dist/forms/FormColorField.js.map +1 -1
  35. package/dist/forms/FormFileField.js +1 -1
  36. package/dist/forms/FormFileField.js.map +1 -1
  37. package/dist/forms/FormFileUploadField.js +1 -1
  38. package/dist/forms/FormFileUploadField.js.map +1 -1
  39. package/dist/forms/FormImageField.js +1 -1
  40. package/dist/forms/FormImageField.js.map +1 -1
  41. package/dist/forms/FormMaskedField.js +1 -1
  42. package/dist/forms/FormMaskedField.js.map +1 -1
  43. package/dist/forms/FormNumberField.js +1 -1
  44. package/dist/forms/FormNumberField.js.map +1 -1
  45. package/dist/forms/FormSchemaProvider.js +17 -0
  46. package/dist/forms/FormSchemaProvider.js.map +1 -0
  47. package/dist/forms/FormSelectField.js +1 -1
  48. package/dist/forms/FormSelectField.js.map +1 -1
  49. package/dist/forms/FormTextField.js +1 -1
  50. package/dist/forms/FormTextField.js.map +1 -1
  51. package/dist/forms/FormTransferListField.js +1 -1
  52. package/dist/forms/FormTransferListField.js.map +1 -1
  53. package/dist/forms/FormWizard.js +23 -6
  54. package/dist/forms/FormWizard.js.map +1 -1
  55. package/dist/forms/schemaRequired.js +33 -0
  56. package/dist/forms/schemaRequired.js.map +1 -0
  57. package/dist/forms/useFormikField.js +18 -5
  58. package/dist/forms/useFormikField.js.map +1 -1
  59. package/dist/forms.js +52 -46
  60. package/dist/forms.js.map +1 -1
  61. package/dist/hookForm/FormAttachmentsField.js +1 -1
  62. package/dist/hookForm/FormAttachmentsField.js.map +1 -1
  63. package/dist/hookForm/FormCatalogField.js +1 -1
  64. package/dist/hookForm/FormCatalogField.js.map +1 -1
  65. package/dist/hookForm/FormCheckboxGroupField.js +1 -1
  66. package/dist/hookForm/FormCheckboxGroupField.js.map +1 -1
  67. package/dist/hookForm/FormColorField.js +1 -1
  68. package/dist/hookForm/FormColorField.js.map +1 -1
  69. package/dist/hookForm/FormFileField.js +1 -1
  70. package/dist/hookForm/FormFileField.js.map +1 -1
  71. package/dist/hookForm/FormFileUploadField.js +1 -1
  72. package/dist/hookForm/FormFileUploadField.js.map +1 -1
  73. package/dist/hookForm/FormImageField.js +1 -1
  74. package/dist/hookForm/FormImageField.js.map +1 -1
  75. package/dist/hookForm/FormMaskedField.js +1 -1
  76. package/dist/hookForm/FormMaskedField.js.map +1 -1
  77. package/dist/hookForm/FormNumberField.js +1 -1
  78. package/dist/hookForm/FormNumberField.js.map +1 -1
  79. package/dist/hookForm/FormSelectField.js +1 -1
  80. package/dist/hookForm/FormSelectField.js.map +1 -1
  81. package/dist/hookForm/FormTextField.js +1 -1
  82. package/dist/hookForm/FormTextField.js.map +1 -1
  83. package/dist/hookForm/FormTransferListField.js +1 -1
  84. package/dist/hookForm/FormTransferListField.js.map +1 -1
  85. package/dist/hookForm/FormWizard.js +21 -5
  86. package/dist/hookForm/FormWizard.js.map +1 -1
  87. package/dist/hookForm/useHookFormField.js +9 -3
  88. package/dist/hookForm/useHookFormField.js.map +1 -1
  89. package/dist/index.js +12 -3
  90. package/dist/index.js.map +1 -1
  91. package/dist/inputs/CatalogInput.js +3 -2
  92. package/dist/inputs/CatalogInput.js.map +1 -1
  93. package/dist/inputs/CheckboxGroupInput.js +2 -1
  94. package/dist/inputs/CheckboxGroupInput.js.map +1 -1
  95. package/dist/inputs/ColorInput.js +3 -1
  96. package/dist/inputs/ColorInput.js.map +1 -1
  97. package/dist/inputs/FieldFrame.js +18 -3
  98. package/dist/inputs/FieldFrame.js.map +1 -1
  99. package/dist/inputs/FormSection.js +42 -11
  100. package/dist/inputs/FormSection.js.map +1 -1
  101. package/dist/inputs/NumberInput.js +3 -1
  102. package/dist/inputs/NumberInput.js.map +1 -1
  103. package/dist/inputs/SelectInput.js +3 -1
  104. package/dist/inputs/SelectInput.js.map +1 -1
  105. package/dist/inputs/TextInput.js +1 -1
  106. package/dist/inputs/TextInput.js.map +1 -1
  107. package/dist/inputs/TransferListInput.js +2 -1
  108. package/dist/inputs/TransferListInput.js.map +1 -1
  109. package/dist/inputs/WizardLayout.js +51 -17
  110. package/dist/inputs/WizardLayout.js.map +1 -1
  111. package/dist/inputs/formLayoutContext.js +7 -2
  112. package/dist/inputs/formLayoutContext.js.map +1 -1
  113. package/dist/inputs/requiredMark.js +17 -0
  114. package/dist/inputs/requiredMark.js.map +1 -0
  115. package/dist/inputs/useWizardSteps.js +36 -0
  116. package/dist/inputs/useWizardSteps.js.map +1 -0
  117. package/dist/inputs/wizardSteps.js +32 -0
  118. package/dist/inputs/wizardSteps.js.map +1 -0
  119. package/dist/locales/en.js +1 -0
  120. package/dist/locales/en.js.map +1 -1
  121. package/dist/locales/es.js +1 -0
  122. package/dist/locales/es.js.map +1 -1
  123. package/dist/richText/FormRichTextField.js +1 -1
  124. package/dist/richText/FormRichTextField.js.map +1 -1
  125. package/dist/richText/RichTextEditor.js +4 -3
  126. package/dist/richText/RichTextEditor.js.map +1 -1
  127. package/dist/toolkit/ToolkitProvider.js +3 -0
  128. package/dist/toolkit/ToolkitProvider.js.map +1 -1
  129. package/dist/toolkit/toolkitContext.js.map +1 -1
  130. package/dist/types/appShell/BarActionButton.d.ts +1 -0
  131. package/dist/types/datePickers/DateInput.d.ts +2 -0
  132. package/dist/types/datePickers/DateTimeInput.d.ts +2 -0
  133. package/dist/types/datePickers/FormDateField.d.ts +3 -3
  134. package/dist/types/datePickers/FormDateTimeField.d.ts +3 -3
  135. package/dist/types/datePickers/FormMonthField.d.ts +3 -3
  136. package/dist/types/datePickers/FormTimeField.d.ts +3 -3
  137. package/dist/types/datePickers/MonthInput.d.ts +2 -0
  138. package/dist/types/datePickers/PickerFrame.d.ts +3 -1
  139. package/dist/types/datePickers/TimeInput.d.ts +2 -0
  140. package/dist/types/feedback/Toaster.d.ts +15 -1
  141. package/dist/types/feedback/index.d.ts +1 -1
  142. package/dist/types/forms/CalcitaFormik.d.ts +6 -0
  143. package/dist/types/forms/FormAttachmentsField.d.ts +3 -3
  144. package/dist/types/forms/FormCatalogField.d.ts +2 -2
  145. package/dist/types/forms/FormCheckboxGroupField.d.ts +3 -3
  146. package/dist/types/forms/FormColorField.d.ts +2 -2
  147. package/dist/types/forms/FormFileField.d.ts +3 -3
  148. package/dist/types/forms/FormFileUploadField.d.ts +3 -3
  149. package/dist/types/forms/FormImageField.d.ts +3 -3
  150. package/dist/types/forms/FormMaskedField.d.ts +2 -2
  151. package/dist/types/forms/FormNumberField.d.ts +2 -2
  152. package/dist/types/forms/FormSchemaProvider.d.ts +13 -0
  153. package/dist/types/forms/FormSelectField.d.ts +2 -2
  154. package/dist/types/forms/FormTextField.d.ts +2 -2
  155. package/dist/types/forms/FormTransferListField.d.ts +3 -3
  156. package/dist/types/forms/FormWizard.d.ts +14 -1
  157. package/dist/types/forms/index.d.ts +1 -0
  158. package/dist/types/forms/schemaRequired.d.ts +14 -0
  159. package/dist/types/forms/useFormikField.d.ts +10 -4
  160. package/dist/types/forms.d.ts +4 -2
  161. package/dist/types/hookForm/FormAttachmentsField.d.ts +3 -3
  162. package/dist/types/hookForm/FormCatalogField.d.ts +3 -3
  163. package/dist/types/hookForm/FormCheckboxGroupField.d.ts +3 -3
  164. package/dist/types/hookForm/FormColorField.d.ts +3 -3
  165. package/dist/types/hookForm/FormFileField.d.ts +3 -3
  166. package/dist/types/hookForm/FormFileUploadField.d.ts +3 -3
  167. package/dist/types/hookForm/FormImageField.d.ts +3 -3
  168. package/dist/types/hookForm/FormMaskedField.d.ts +3 -3
  169. package/dist/types/hookForm/FormNumberField.d.ts +3 -3
  170. package/dist/types/hookForm/FormSelectField.d.ts +3 -3
  171. package/dist/types/hookForm/FormTextField.d.ts +3 -3
  172. package/dist/types/hookForm/FormTransferListField.d.ts +3 -3
  173. package/dist/types/hookForm/FormWizard.d.ts +14 -1
  174. package/dist/types/hookForm/useHookFormField.d.ts +8 -4
  175. package/dist/types/index.d.ts +4 -3
  176. package/dist/types/inputs/CatalogInput.d.ts +1 -0
  177. package/dist/types/inputs/CheckboxGroupInput.d.ts +1 -0
  178. package/dist/types/inputs/ColorInput.d.ts +1 -0
  179. package/dist/types/inputs/FieldFrame.d.ts +1 -0
  180. package/dist/types/inputs/FormSection.d.ts +13 -0
  181. package/dist/types/inputs/NumberInput.d.ts +1 -0
  182. package/dist/types/inputs/SelectInput.d.ts +1 -0
  183. package/dist/types/inputs/TextInput.d.ts +1 -1
  184. package/dist/types/inputs/TransferListInput.d.ts +1 -0
  185. package/dist/types/inputs/WizardLayout.d.ts +6 -2
  186. package/dist/types/inputs/formLayoutContext.d.ts +8 -1
  187. package/dist/types/inputs/requiredMark.d.ts +9 -0
  188. package/dist/types/inputs/useWizardSteps.d.ts +23 -0
  189. package/dist/types/inputs/wizardSteps.d.ts +65 -0
  190. package/dist/types/locales/en.d.ts +1 -0
  191. package/dist/types/locales/es.d.ts +1 -0
  192. package/dist/types/richText/FormRichTextField.d.ts +3 -3
  193. package/dist/types/richText/RichTextEditor.d.ts +1 -0
  194. package/dist/types/toolkit/ToolkitProvider.d.ts +5 -1
  195. package/dist/types/toolkit/toolkitContext.d.ts +2 -1
  196. package/dist/types/ui/cards/RecordCard.d.ts +12 -0
  197. package/dist/types/ui/index.d.ts +1 -1
  198. package/dist/types/ui/navigation/BackButton.d.ts +1 -0
  199. package/dist/types/upload/AttachmentsInput.d.ts +2 -0
  200. package/dist/types/upload/FileInput.d.ts +1 -0
  201. package/dist/types/upload/FileUploadInput.d.ts +1 -0
  202. package/dist/types/upload/ImageInput.d.ts +1 -0
  203. package/dist/ui/cards/RecordCard.js +6 -2
  204. package/dist/ui/cards/RecordCard.js.map +1 -1
  205. package/dist/ui/navigation/BackButton.js.map +1 -1
  206. package/dist/upload/AttachmentsInput.js +2 -1
  207. package/dist/upload/AttachmentsInput.js.map +1 -1
  208. package/dist/upload/FileInput.js +2 -1
  209. package/dist/upload/FileInput.js.map +1 -1
  210. package/dist/upload/FileUploadInput.js +2 -1
  211. package/dist/upload/FileUploadInput.js.map +1 -1
  212. package/dist/upload/ImageInput.js +2 -1
  213. package/dist/upload/ImageInput.js.map +1 -1
  214. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"useHookFormField.js","sources":["../../src/hookForm/useHookFormField.js"],"sourcesContent":["// La conexión de un campo suelto (inputs/) con react-hook-form: lo que la vista necesita, sacado de useController.\n//\n// El campo se busca en el formulario de la pantalla (FormProvider); sin él, se le pasa control.\n//\n// Cuándo se enseña el error lo decide react-hook-form (por omisión, al intentar enviar y después al corregir): la vista\n// solo pinta el que llegue. Los errores de yup llegan como llave ({key, values}) y la vista los traduce.\n//\n// Hook genérico del paquete: function (regla de la casa).\nimport {useController, useFormContext} from 'react-hook-form';\n\n/**\n * @param {string} name el campo del formulario\n * @param {object} [control] el de useForm, si la pantalla no usa FormProvider\n * @returns {{name: string, value: unknown, onChange: (value: unknown) => void, onBlur: () => void, inputRef: Function, error: unknown}}\n */\nexport function useHookFormField(name, control) {\n const formContext = useFormContext();\n const {field, fieldState} = useController({name, control: control ?? formContext?.control});\n\n return {\n name,\n value: field.value,\n onChange: field.onChange,\n onBlur: field.onBlur,\n // react-hook-form enfoca el primer campo con error al enviar: necesita el input de verdad\n inputRef: field.ref,\n error: fieldState.error?.message,\n };\n}\n"],"names":[],"mappings":";;AAeO,SAAS,iBAAiB,MAAM,SAAS;AAC5C,QAAM,cAAc,eAAc;AAClC,QAAM,EAAC,OAAO,WAAU,IAAI,cAAc,EAAC,MAAM,SAAS,WAAW,aAAa,QAAO,CAAC;AAE1F,SAAO;AAAA,IACH;AAAA,IACA,OAAO,MAAM;AAAA,IACb,UAAU,MAAM;AAAA,IAChB,QAAQ,MAAM;AAAA;AAAA,IAEd,UAAU,MAAM;AAAA,IAChB,OAAO,WAAW,OAAO;AAAA,EACjC;AACA;"}
1
+ {"version":3,"file":"useHookFormField.js","sources":["../../src/hookForm/useHookFormField.js"],"sourcesContent":["// La conexión de un campo suelto (inputs/) con react-hook-form: lo que la vista necesita, sacado de useController.\n//\n// El campo se busca en el formulario de la pantalla (FormProvider); sin él, se le pasa control.\n//\n// Lo que la pantalla le pase al campo en onChange y onBlur también corre, después del de react-hook-form y con el valor\n// nuevo. OJO: antes el de react-hook-form iba después y lo pisaba sin avisar.\n//\n// Cuándo se enseña el error lo decide react-hook-form (por omisión, al intentar enviar y después al corregir): la vista\n// solo pinta el que llegue. Los errores de yup llegan como llave ({key, values}) y la vista los traduce.\n//\n// Hook genérico del paquete: function (regla de la casa).\nimport {useController, useFormContext} from 'react-hook-form';\n\n/**\n * @param {string} name el campo del formulario\n * @param {object} [control] el de useForm, si la pantalla no usa FormProvider\n * @param {{onChange?: Function, onBlur?: Function}} [screen] lo que la pantalla le dijo al campo\n * @returns {{name: string, value: unknown, onChange: (value: unknown, ...rest: unknown[]) => void, onBlur: (...args: unknown[]) => void, inputRef: Function, error: unknown}}\n */\nexport function useHookFormField(name, control, screen = {}) {\n const formContext = useFormContext();\n const {field, fieldState} = useController({name, control: control ?? formContext?.control});\n\n return {\n name,\n value: field.value,\n onChange: (value, ...rest) => {\n field.onChange(value);\n screen.onChange?.(value, ...rest);\n },\n onBlur: (...args) => {\n field.onBlur();\n screen.onBlur?.(...args);\n },\n // react-hook-form enfoca el primer campo con error al enviar: necesita el input de verdad\n inputRef: field.ref,\n error: fieldState.error?.message,\n };\n}\n"],"names":[],"mappings":";;AAmBO,SAAS,iBAAiB,MAAM,SAAS,SAAS,CAAA,GAAI;AACzD,QAAM,cAAc,eAAc;AAClC,QAAM,EAAC,OAAO,WAAU,IAAI,cAAc,EAAC,MAAM,SAAS,WAAW,aAAa,QAAO,CAAC;AAE1F,SAAO;AAAA,IACH;AAAA,IACA,OAAO,MAAM;AAAA,IACb,UAAU,CAAC,UAAU,SAAS;AAC1B,YAAM,SAAS,KAAK;AACpB,aAAO,WAAW,OAAO,GAAG,IAAI;AAAA,IACpC;AAAA,IACA,QAAQ,IAAI,SAAS;AACjB,YAAM,OAAM;AACZ,aAAO,SAAS,GAAG,IAAI;AAAA,IAC3B;AAAA;AAAA,IAEA,UAAU,MAAM;AAAA,IAChB,OAAO,WAAW,OAAO;AAAA,EACjC;AACA;"}
package/dist/index.js CHANGED
@@ -14,7 +14,7 @@ import { default as default5 } from "./feedback/ConfirmDialog.js";
14
14
  import { default as default6 } from "./feedback/ModalWaiting.js";
15
15
  import { NEW_BUILD_NOTICE_VARIANT, default as default7 } from "./feedback/NewBuildNotice.js";
16
16
  import { default as default8 } from "./feedback/StaleBuildBoundary.js";
17
- import { default as default9 } from "./feedback/Toaster.js";
17
+ import { TOASTER_POSITION, default as default9 } from "./feedback/Toaster.js";
18
18
  import { confirmActionMachine } from "./feedback/confirmActionMachine.js";
19
19
  import { TOAST_DEFAULT_OPTIONS, dismissToast, errorsObjectToMessage, toastDefault, toastError, toastInfo, toastLoading, toastProblem, toastPromise, toastSuccess, toastWarn } from "./feedback/toast.js";
20
20
  import { useConfirmAction } from "./feedback/useConfirmAction.js";
@@ -95,7 +95,7 @@ import { default as default47 } from "./ui/navigation/PageBreadcrumbs.js";
95
95
  import { ROUTE_TABS_MODE, default as default48, useActiveRouteTab } from "./ui/navigation/RouteTabs.js";
96
96
  import { default as default49 } from "./ui/help/HelpButton.js";
97
97
  import { HELP_CLOSE_REASON, default as default50 } from "./ui/help/HelpCarouselDialog.js";
98
- import { default as default51 } from "./ui/cards/RecordCard.js";
98
+ import { RECORD_CARD_FOOTER_ALIGN, default as default51 } from "./ui/cards/RecordCard.js";
99
99
  import { default as default52 } from "./ui/cards/RecordCardSkeleton.js";
100
100
  import { useHelpCarousel } from "./ui/help/useHelpCarousel.js";
101
101
  import { default as default53 } from "./ui/filters/DateRangeFilter.js";
@@ -120,7 +120,7 @@ import { default as default69 } from "./inputs/ColorInput.js";
120
120
  import { default as default70 } from "./inputs/FieldFrame.js";
121
121
  import { default as default71 } from "./inputs/MaskedInput.js";
122
122
  import { INPUT_MASK, isMaskComplete, maskPatternOf, maskedTextOf, rawValueOf } from "./inputs/inputMasks.js";
123
- import { FORM_ROW_CLASS, FormRow, default as default72 } from "./inputs/FormSection.js";
123
+ import { FORM_LABEL_PLACEMENT, FORM_ROW_CLASS, FormRow, default as default72 } from "./inputs/FormSection.js";
124
124
  import { default as default73 } from "./inputs/CheckboxGroupInput.js";
125
125
  import { default as default74 } from "./inputs/NumberInput.js";
126
126
  import { default as default75 } from "./inputs/TransferListInput.js";
@@ -129,6 +129,7 @@ import { default as default76 } from "./inputs/RepeatingFields.js";
129
129
  import { useRowKeys } from "./inputs/useRowKeys.js";
130
130
  import { default as default77 } from "./inputs/SelectInput.js";
131
131
  import { default as default78 } from "./inputs/WizardLayout.js";
132
+ import { WIZARD_ORIENTATION, WIZARD_STEP_SELECTION, isStepCompleted, openingStepOf, stepSelectionActionOf } from "./inputs/wizardSteps.js";
132
133
  import { default as default79 } from "./inputs/TextInput.js";
133
134
  import { HEX_COLOR, MIN_CONTRAST, contrastOnWhite, readableVariantOf } from "./inputs/colorContrast.js";
134
135
  import { cleanNumberText, completeDecimals, groupThousands } from "./inputs/numberText.js";
@@ -235,6 +236,7 @@ export {
235
236
  FILTER_DRAWER_ANCHOR,
236
237
  FILTER_FIELD_KIND,
237
238
  FONT_SIZE_LEVELS,
239
+ FORM_LABEL_PLACEMENT,
238
240
  FORM_ROW_CLASS,
239
241
  default70 as FieldFrame,
240
242
  default25 as FileDropzone,
@@ -294,6 +296,7 @@ export {
294
296
  default21 as PermissionMatrix,
295
297
  default39 as ProfileHeader,
296
298
  default56 as QuickFilters,
299
+ RECORD_CARD_FOOTER_ALIGN,
297
300
  REDIRECT_PARAM,
298
301
  ROUTE_TABS_MODE,
299
302
  default51 as RecordCard,
@@ -317,6 +320,7 @@ export {
317
320
  default8 as StaleBuildBoundary,
318
321
  default59 as StatusChip,
319
322
  THEME_LOOK,
323
+ TOASTER_POSITION,
320
324
  TOAST_DEFAULT_OPTIONS,
321
325
  TOP_BAR,
322
326
  TREND_DIRECTION,
@@ -329,6 +333,8 @@ export {
329
333
  ToolkitProvider,
330
334
  default75 as TransferListInput,
331
335
  UNKNOWN_FILE_TYPE,
336
+ WIZARD_ORIENTATION,
337
+ WIZARD_STEP_SELECTION,
332
338
  default78 as WizardLayout,
333
339
  XLSX_CONTENT_TYPE,
334
340
  accentTextStylesOf,
@@ -415,6 +421,7 @@ export {
415
421
  isPasswordAccepted,
416
422
  isPlainLeftClick,
417
423
  isStaleChunkError,
424
+ isStepCompleted,
418
425
  labelOf,
419
426
  lookup,
420
427
  maskPatternOf,
@@ -436,6 +443,7 @@ export {
436
443
  mobileTabsOf,
437
444
  newIdentifier,
438
445
  normalizedValidationMessage,
446
+ openingStepOf,
439
447
  pageNameAt,
440
448
  parentMenuItemOf,
441
449
  parseFileSize,
@@ -475,6 +483,7 @@ export {
475
483
  stackcraftDarkPalette,
476
484
  stackcraftLightPalette,
477
485
  startDownload,
486
+ stepSelectionActionOf,
478
487
  sxListOf,
479
488
  tableRowTextOf,
480
489
  toastDefault,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -30,6 +30,7 @@ function CatalogInputContent(props) {
30
30
  helperText = " ",
31
31
  loading = false,
32
32
  disabled = false,
33
+ required = false,
33
34
  inputRef,
34
35
  placeholder,
35
36
  multiple = false,
@@ -63,7 +64,7 @@ function CatalogInputContent(props) {
63
64
  return selectAllState.allChosen ? t("form.unselectAll") : t("form.selectAll", { count: matchingCodes.length });
64
65
  })();
65
66
  const showsSelectAll = multiple && selectAll;
66
- return /* @__PURE__ */ jsx(FieldFrame, { inputId, label, children: /* @__PURE__ */ jsx(
67
+ return /* @__PURE__ */ jsx(FieldFrame, { inputId, label, required, children: /* @__PURE__ */ jsx(
67
68
  CatalogInputField,
68
69
  {
69
70
  id: inputId,
@@ -132,7 +133,7 @@ function CatalogInputContent(props) {
132
133
  );
133
134
  } : void 0,
134
135
  onBlur,
135
- renderInput: (inputProps) => /* @__PURE__ */ jsx(TextField, { ...inputProps, name, inputRef, placeholder, error: Boolean(errorText), helperText: errorText ?? helperText })
136
+ renderInput: (inputProps) => /* @__PURE__ */ jsx(TextField, { ...inputProps, inputProps: { ...inputProps.inputProps, "aria-required": required || void 0 }, name, inputRef, placeholder, error: Boolean(errorText), helperText: errorText ?? helperText })
136
137
  }
137
138
  ) });
138
139
  }
@@ -1 +1 @@
1
- {"version":3,"file":"CatalogInput.js","sources":["../../src/inputs/CatalogInput.jsx"],"sourcesContent":["// Campo suelto que elige una clave de un catálogo ([{code, label}]): actividad económica, giro, país, banco… Se busca por la\n// clave o por el texto, sin acentos ni mayúsculas, y al formulario llega solo la clave. Con `multiple` elige varias: cada una\n// queda como chip con su ×, la lista no se cierra al elegir y al formulario llega la lista de claves en el orden elegido.\n// Con varias, además (cada una se pide):\n// selectAll «Elegir todos» arriba de la lista, con su casilla a medias si hay algunos; con algo escrito en el buscador,\n// «Elegir los N que coinciden» (todos los que coinciden, no solo los que se ven)\n// checkboxes una casilla en cada opción (se ve de un vistazo qué está elegido)\n// limitTags cuántos chips se ven con el campo sin foco; los demás, «+3 más»\n//\n// Por qué no un <select>: con catálogos de cientos de opciones no se puede usar; y con texto libre la gente inventa claves.\n// Se enseñan a lo más MAX_VISIBLE_OPTIONS coincidencias: con miles, la lista completa congela la pantalla.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaCatalogInput con defaultProps, styleOverrides de\n// sus partes (field) y slots/slotProps para cambiarlas.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useId, useState} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport Autocomplete from '@mui/material/Autocomplete';\nimport Checkbox from '@mui/material/Checkbox';\nimport TextField from '@mui/material/TextField';\n\nimport {useValidationText} from '@/forms/useValidationText.js';\nimport FieldFrame from '@/inputs/FieldFrame.jsx';\nimport {selectAllStateOf} from '@/inputs/selectAllState.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\nimport {piecePart, usePieceProps, withPieceSlots} from '@/theme/themePieces.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaCatalogInput)\nconst THEME_NAME = 'CalcitaCatalogInput';\nconst CatalogInputField = piecePart(THEME_NAME, 'Field', Autocomplete);\n\nconst MAX_VISIBLE_OPTIONS = 100;\n// la opción de «Elegir todos»: no es del catálogo y nunca llega al formulario\nconst SELECT_ALL_CODE = '__calcita_select_all__';\n\n/**\n * Un texto sin acentos ni mayúsculas, para que «credito» encuentre «Crédito».\n *\n * @param {unknown} text\n * @returns {string}\n */\nfunction plainTextOf(text) {\n return String(text ?? '').normalize('NFD').replace(/\\p{Diacritic}/gu, '').toLowerCase();\n}\n\n/**\n * @param {object} props\n * @param {string} [props.name]\n * @param {string} props.label ya traducido\n * @param {string|string[]|null|undefined} props.value la clave elegida ('' = ninguna); con multiple, la lista ([] = ninguna)\n * @param {(code: any) => void} props.onChange recibe la clave ('' al borrar); con multiple, la lista de claves\n * @param {() => void} [props.onBlur]\n * @param {Array<{code: string, label: string}>} props.options\n * @param {(option: {code: string, label: string}) => string} [props.formatLabel] por omisión «Nombre (clave)»\n * @param {string|{key: string, values?: object}} [props.error]\n * @param {string} [props.helperText]\n * @param {boolean} [props.loading] el catálogo todavía no llega\n * @param {string} [props.placeholder] la pista con el campo vacío, ya traducida (p. ej. «Busque un estado»): sin ella, en un\n * formulario agrupado el campo vacío solo enseña su botón y no se entiende qué es\n * @param {boolean} [props.disabled]\n * @param {import('react').Ref<HTMLInputElement>} [props.inputRef]\n * @param {boolean} [props.multiple] elegir varias claves\n * @param {boolean} [props.selectAll] con multiple: «Elegir todos» (o los que coinciden con lo buscado) arriba de la lista\n * @param {boolean} [props.checkboxes] con multiple: una casilla en cada opción\n * @param {number} [props.limitTags] con multiple: cuántos chips se ven sin foco; los demás como «+3 más»\n * @param {Record<string, string|object>} [props.messages] el texto del error de este campo, por regla de validación\n * ({required: 'Escriba el RFC', matches: 'El RFC lleva 12 o 13 caracteres'}): gana al de Calcita y al de los textos\n * @param {object} [props.texts] los textos que cambian, con la forma de `common` en los textos del paquete\n */\nfunction CatalogInputContent(props) {\n const {\n name, label, value, onChange, onBlur, options = [], formatLabel, error, helperText = ' ', loading = false, disabled = false, inputRef, placeholder,\n multiple = false, selectAll = false, checkboxes = false, limitTags, messages, texts,\n ...unknownProps\n } = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const {t} = useToolkitTranslation('common', texts);\n // el texto del error: el de la pantalla (messages), el del proyecto para este campo o el de Calcita\n const validationTextOf = useValidationText({name, label, messages});\n const generatedId = useId();\n const inputId = `${name ?? 'catalog'}-${generatedId}`;\n const errorText = validationTextOf(error);\n // con varias, en el orden en que se eligieron (el de la lista de claves)\n const selectedOption = multiple\n ? (Array.isArray(value) ? value : []).map((code) => options.find((option) => option.code === code)).filter(Boolean)\n : options.find((option) => option.code === value) ?? null;\n const optionLabelOf = (option) => (formatLabel ? formatLabel(option) : `${option.label} (${option.code})`);\n // lo escrito en el buscador: «Elegir todos» abarca lo que coincide con eso\n const [searchText, setSearchText] = useState('');\n const matchesSearch = (option, text) => {\n const wantedText = plainTextOf(text);\n return !wantedText || plainTextOf(optionLabelOf(option)).includes(wantedText);\n };\n const selectedCodes = multiple && Array.isArray(value) ? value : [];\n const matchingCodes = options.filter((option) => matchesSearch(option, searchText)).map((option) => option.code);\n const selectAllState = selectAllStateOf(selectedCodes, matchingCodes);\n const selectAllLabel = (() => {\n if (searchText.trim()) {\n return selectAllState.allChosen ? t('form.unselectMatching', {count: matchingCodes.length}) : t('form.selectMatching', {count: matchingCodes.length});\n }\n return selectAllState.allChosen ? t('form.unselectAll') : t('form.selectAll', {count: matchingCodes.length});\n })();\n const showsSelectAll = multiple && selectAll;\n\n return (\n <FieldFrame inputId={inputId} label={label}>\n <CatalogInputField\n id={inputId}\n multiple={multiple}\n disableCloseOnSelect={multiple}\n fullWidth\n size=\"small\"\n options={options}\n value={selectedOption}\n loading={loading}\n disabled={disabled}\n noOptionsText={t('form.noOptions')}\n loadingText={t('form.loadingOptions')}\n getOptionLabel={(option) => (option.code === SELECT_ALL_CODE ? selectAllLabel : optionLabelOf(option))}\n isOptionEqualToValue={(option, currentOption) => option.code === currentOption.code}\n filterOptions={(allOptions, {inputValue}) => {\n const matchingOptions = allOptions.filter((option) => matchesSearch(option, inputValue)).slice(0, MAX_VISIBLE_OPTIONS);\n // «Elegir todos» va arriba solo si hay de dónde elegir\n return showsSelectAll && matchingOptions.length > 0 ? [{code: SELECT_ALL_CODE, label: selectAllLabel}, ...matchingOptions] : matchingOptions;\n }}\n // con «Elegir todos», lo buscado se queda al elegir (MUI lo borraba): así se eligen varios de la misma búsqueda.\n // Al salir del campo sí se borra. Sin «Elegir todos», el campo se porta como siempre\n inputValue={showsSelectAll ? searchText : undefined}\n onInputChange={(event, text, reason) => {\n // MUI 7 avisa con qué borra el texto: al elegir o quitar una opción ('selectOption', 'removeOption', 'reset');\n // al salir del campo es 'blur' y ese sí se borra\n const isResetByChoosing = ['selectOption', 'removeOption', 'reset'].includes(reason) && event?.type !== 'blur';\n if (!(showsSelectAll && isResetByChoosing)) {\n setSearchText(text);\n }\n }}\n onChange={(event, option, reason, details) => {\n if (!multiple) {\n onChange(option?.code ?? '');\n return;\n }\n if (details?.option?.code === SELECT_ALL_CODE) {\n onChange(selectAllState.toggled());\n return;\n }\n onChange(option.filter((chosenOption) => chosenOption.code !== SELECT_ALL_CODE).map((chosenOption) => chosenOption.code));\n }}\n limitTags={multiple ? limitTags : undefined}\n getLimitTagsText={(hiddenCount) => t('form.moreSelected', {count: hiddenCount})}\n renderOption={multiple && (checkboxes || selectAll) ? (optionProps, option, {selected}) => {\n // OJO: key va aparte; dentro de las props React avisa en la consola\n const {key, ...liProps} = optionProps;\n const isSelectAll = option.code === SELECT_ALL_CODE;\n const showsBox = checkboxes || isSelectAll;\n return (\n <li key={key} {...liProps} {...(isSelectAll && {'aria-selected': selectAllState.allChosen})}\n style={isSelectAll ? {fontWeight: 600} : undefined}>\n {showsBox && (\n <Checkbox size=\"small\" tabIndex={-1} disableRipple sx={{p: 0, mr: 1}}\n checked={isSelectAll ? selectAllState.allChosen : selected}\n indeterminate={isSelectAll && selectAllState.someChosen} />\n )}\n {isSelectAll ? selectAllLabel : optionLabelOf(option)}\n </li>\n );\n } : undefined}\n onBlur={onBlur}\n renderInput={(inputProps) => (\n <TextField {...inputProps} name={name} inputRef={inputRef} placeholder={placeholder} error={Boolean(errorText)} helperText={errorText ?? helperText} />\n )}\n />\n </FieldFrame>\n );\n}\n\n// lo que se exporta: la pieza con slots y slotProps para cambiar sus partes (themePieces.js)\nconst CatalogInput = withPieceSlots(CatalogInputContent, THEME_NAME);\nexport default CatalogInput;\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"CatalogInput.js","sources":["../../src/inputs/CatalogInput.jsx"],"sourcesContent":["// Campo suelto que elige una clave de un catálogo ([{code, label}]): actividad económica, giro, país, banco… Se busca por la\n// clave o por el texto, sin acentos ni mayúsculas, y al formulario llega solo la clave. Con `multiple` elige varias: cada una\n// queda como chip con su ×, la lista no se cierra al elegir y al formulario llega la lista de claves en el orden elegido.\n// Con varias, además (cada una se pide):\n// selectAll «Elegir todos» arriba de la lista, con su casilla a medias si hay algunos; con algo escrito en el buscador,\n// «Elegir los N que coinciden» (todos los que coinciden, no solo los que se ven)\n// checkboxes una casilla en cada opción (se ve de un vistazo qué está elegido)\n// limitTags cuántos chips se ven con el campo sin foco; los demás, «+3 más»\n//\n// Por qué no un <select>: con catálogos de cientos de opciones no se puede usar; y con texto libre la gente inventa claves.\n// Se enseñan a lo más MAX_VISIBLE_OPTIONS coincidencias: con miles, la lista completa congela la pantalla.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaCatalogInput con defaultProps, styleOverrides de\n// sus partes (field) y slots/slotProps para cambiarlas.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useId, useState} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport Autocomplete from '@mui/material/Autocomplete';\nimport Checkbox from '@mui/material/Checkbox';\nimport TextField from '@mui/material/TextField';\n\nimport {useValidationText} from '@/forms/useValidationText.js';\nimport FieldFrame from '@/inputs/FieldFrame.jsx';\nimport {selectAllStateOf} from '@/inputs/selectAllState.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\nimport {piecePart, usePieceProps, withPieceSlots} from '@/theme/themePieces.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaCatalogInput)\nconst THEME_NAME = 'CalcitaCatalogInput';\nconst CatalogInputField = piecePart(THEME_NAME, 'Field', Autocomplete);\n\nconst MAX_VISIBLE_OPTIONS = 100;\n// la opción de «Elegir todos»: no es del catálogo y nunca llega al formulario\nconst SELECT_ALL_CODE = '__calcita_select_all__';\n\n/**\n * Un texto sin acentos ni mayúsculas, para que «credito» encuentre «Crédito».\n *\n * @param {unknown} text\n * @returns {string}\n */\nfunction plainTextOf(text) {\n return String(text ?? '').normalize('NFD').replace(/\\p{Diacritic}/gu, '').toLowerCase();\n}\n\n/**\n * @param {object} props\n * @param {string} [props.name]\n * @param {string} props.label ya traducido\n * @param {string|string[]|null|undefined} props.value la clave elegida ('' = ninguna); con multiple, la lista ([] = ninguna)\n * @param {(code: any) => void} props.onChange recibe la clave ('' al borrar); con multiple, la lista de claves\n * @param {() => void} [props.onBlur]\n * @param {Array<{code: string, label: string}>} props.options\n * @param {(option: {code: string, label: string}) => string} [props.formatLabel] por omisión «Nombre (clave)»\n * @param {string|{key: string, values?: object}} [props.error]\n * @param {string} [props.helperText]\n * @param {boolean} [props.loading] el catálogo todavía no llega\n * @param {string} [props.placeholder] la pista con el campo vacío, ya traducida (p. ej. «Busque un estado»): sin ella, en un\n * formulario agrupado el campo vacío solo enseña su botón y no se entiende qué es\n * @param {boolean} [props.disabled]\n * @param {boolean} [props.required] obligatorio: el asterisco en la etiqueta y aria-required en el campo (sin required: con varias opciones elegidas el texto queda vacío y el navegador no dejaría enviar)\n * @param {import('react').Ref<HTMLInputElement>} [props.inputRef]\n * @param {boolean} [props.multiple] elegir varias claves\n * @param {boolean} [props.selectAll] con multiple: «Elegir todos» (o los que coinciden con lo buscado) arriba de la lista\n * @param {boolean} [props.checkboxes] con multiple: una casilla en cada opción\n * @param {number} [props.limitTags] con multiple: cuántos chips se ven sin foco; los demás como «+3 más»\n * @param {Record<string, string|object>} [props.messages] el texto del error de este campo, por regla de validación\n * ({required: 'Escriba el RFC', matches: 'El RFC lleva 12 o 13 caracteres'}): gana al de Calcita y al de los textos\n * @param {object} [props.texts] los textos que cambian, con la forma de `common` en los textos del paquete\n */\nfunction CatalogInputContent(props) {\n const {\n name, label, value, onChange, onBlur, options = [], formatLabel, error, helperText = ' ', loading = false, disabled = false, required = false, inputRef, placeholder,\n multiple = false, selectAll = false, checkboxes = false, limitTags, messages, texts,\n ...unknownProps\n } = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const {t} = useToolkitTranslation('common', texts);\n // el texto del error: el de la pantalla (messages), el del proyecto para este campo o el de Calcita\n const validationTextOf = useValidationText({name, label, messages});\n const generatedId = useId();\n const inputId = `${name ?? 'catalog'}-${generatedId}`;\n const errorText = validationTextOf(error);\n // con varias, en el orden en que se eligieron (el de la lista de claves)\n const selectedOption = multiple\n ? (Array.isArray(value) ? value : []).map((code) => options.find((option) => option.code === code)).filter(Boolean)\n : options.find((option) => option.code === value) ?? null;\n const optionLabelOf = (option) => (formatLabel ? formatLabel(option) : `${option.label} (${option.code})`);\n // lo escrito en el buscador: «Elegir todos» abarca lo que coincide con eso\n const [searchText, setSearchText] = useState('');\n const matchesSearch = (option, text) => {\n const wantedText = plainTextOf(text);\n return !wantedText || plainTextOf(optionLabelOf(option)).includes(wantedText);\n };\n const selectedCodes = multiple && Array.isArray(value) ? value : [];\n const matchingCodes = options.filter((option) => matchesSearch(option, searchText)).map((option) => option.code);\n const selectAllState = selectAllStateOf(selectedCodes, matchingCodes);\n const selectAllLabel = (() => {\n if (searchText.trim()) {\n return selectAllState.allChosen ? t('form.unselectMatching', {count: matchingCodes.length}) : t('form.selectMatching', {count: matchingCodes.length});\n }\n return selectAllState.allChosen ? t('form.unselectAll') : t('form.selectAll', {count: matchingCodes.length});\n })();\n const showsSelectAll = multiple && selectAll;\n\n return (\n <FieldFrame inputId={inputId} label={label} required={required}>\n <CatalogInputField\n id={inputId}\n multiple={multiple}\n disableCloseOnSelect={multiple}\n fullWidth\n size=\"small\"\n options={options}\n value={selectedOption}\n loading={loading}\n disabled={disabled}\n noOptionsText={t('form.noOptions')}\n loadingText={t('form.loadingOptions')}\n getOptionLabel={(option) => (option.code === SELECT_ALL_CODE ? selectAllLabel : optionLabelOf(option))}\n isOptionEqualToValue={(option, currentOption) => option.code === currentOption.code}\n filterOptions={(allOptions, {inputValue}) => {\n const matchingOptions = allOptions.filter((option) => matchesSearch(option, inputValue)).slice(0, MAX_VISIBLE_OPTIONS);\n // «Elegir todos» va arriba solo si hay de dónde elegir\n return showsSelectAll && matchingOptions.length > 0 ? [{code: SELECT_ALL_CODE, label: selectAllLabel}, ...matchingOptions] : matchingOptions;\n }}\n // con «Elegir todos», lo buscado se queda al elegir (MUI lo borraba): así se eligen varios de la misma búsqueda.\n // Al salir del campo sí se borra. Sin «Elegir todos», el campo se porta como siempre\n inputValue={showsSelectAll ? searchText : undefined}\n onInputChange={(event, text, reason) => {\n // MUI 7 avisa con qué borra el texto: al elegir o quitar una opción ('selectOption', 'removeOption', 'reset');\n // al salir del campo es 'blur' y ese sí se borra\n const isResetByChoosing = ['selectOption', 'removeOption', 'reset'].includes(reason) && event?.type !== 'blur';\n if (!(showsSelectAll && isResetByChoosing)) {\n setSearchText(text);\n }\n }}\n onChange={(event, option, reason, details) => {\n if (!multiple) {\n onChange(option?.code ?? '');\n return;\n }\n if (details?.option?.code === SELECT_ALL_CODE) {\n onChange(selectAllState.toggled());\n return;\n }\n onChange(option.filter((chosenOption) => chosenOption.code !== SELECT_ALL_CODE).map((chosenOption) => chosenOption.code));\n }}\n limitTags={multiple ? limitTags : undefined}\n getLimitTagsText={(hiddenCount) => t('form.moreSelected', {count: hiddenCount})}\n renderOption={multiple && (checkboxes || selectAll) ? (optionProps, option, {selected}) => {\n // OJO: key va aparte; dentro de las props React avisa en la consola\n const {key, ...liProps} = optionProps;\n const isSelectAll = option.code === SELECT_ALL_CODE;\n const showsBox = checkboxes || isSelectAll;\n return (\n <li key={key} {...liProps} {...(isSelectAll && {'aria-selected': selectAllState.allChosen})}\n style={isSelectAll ? {fontWeight: 600} : undefined}>\n {showsBox && (\n <Checkbox size=\"small\" tabIndex={-1} disableRipple sx={{p: 0, mr: 1}}\n checked={isSelectAll ? selectAllState.allChosen : selected}\n indeterminate={isSelectAll && selectAllState.someChosen} />\n )}\n {isSelectAll ? selectAllLabel : optionLabelOf(option)}\n </li>\n );\n } : undefined}\n onBlur={onBlur}\n renderInput={(inputProps) => (\n <TextField {...inputProps} inputProps={{...inputProps.inputProps, 'aria-required': required || undefined}} name={name} inputRef={inputRef} placeholder={placeholder} error={Boolean(errorText)} helperText={errorText ?? helperText} />\n )}\n />\n </FieldFrame>\n );\n}\n\n// lo que se exporta: la pieza con slots y slotProps para cambiar sus partes (themePieces.js)\nconst CatalogInput = withPieceSlots(CatalogInputContent, THEME_NAME);\nexport default CatalogInput;\n"],"names":[],"mappings":"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"}
@@ -30,6 +30,7 @@ function CheckboxGroupInputContent(props) {
30
30
  error,
31
31
  helperText = " ",
32
32
  disabled = false,
33
+ required = false,
33
34
  sx,
34
35
  messages,
35
36
  texts,
@@ -50,7 +51,7 @@ function CheckboxGroupInputContent(props) {
50
51
  onBlur?.();
51
52
  };
52
53
  const toggle = (optionValue) => change(checkedValues.includes(optionValue) ? checkedValues.filter((checkedValue) => checkedValue !== optionValue) : [...checkedValues, optionValue]);
53
- return /* @__PURE__ */ jsx(FieldFrame, { inputId: `${groupId}-first`, labelId, label, children: /* @__PURE__ */ jsxs("div", { children: [
54
+ return /* @__PURE__ */ jsx(FieldFrame, { inputId: `${groupId}-first`, labelId, label, required, children: /* @__PURE__ */ jsxs("div", { children: [
54
55
  /* @__PURE__ */ jsxs(CheckboxGroupRoot, { row, role: "group", "aria-labelledby": labelId, ownSx: { columnGap: 2 }, sx, children: [
55
56
  selectAll && /* @__PURE__ */ jsx(
56
57
  CheckboxGroupSelectAll,
@@ -1 +1 @@
1
- {"version":3,"file":"CheckboxGroupInput.js","sources":["../../src/inputs/CheckboxGroupInput.jsx"],"sourcesContent":["// El grupo de casillas: pocas opciones a la vista (los canales de aviso, los días que abre una sucursal), cada una con su\n// casilla y, si hace falta, una explicación abajo; al formulario llega la lista de las marcadas. Con pocas opciones es más\n// rápido que abrir una lista; con muchas, SelectInput o CatalogInput con `multiple`.\n//\n// selectAll pone «Elegir todos» arriba, con su casilla a medias si hay algunas marcadas. row las acomoda en renglón (en el\n// teléfono bajan solas). Dentro de una FormSection se vuelve renglón, como los demás campos.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaCheckboxGroupInput con defaultProps y\n// styleOverrides de sus partes: root, selectAll, option.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useId} from 'react';\nimport Checkbox from '@mui/material/Checkbox';\nimport FormControlLabel from '@mui/material/FormControlLabel';\nimport FormGroup from '@mui/material/FormGroup';\nimport FormHelperText from '@mui/material/FormHelperText';\nimport Stack from '@mui/material/Stack';\nimport Typography from '@mui/material/Typography';\n\nimport {useValidationText} from '@/forms/useValidationText.js';\nimport FieldFrame from '@/inputs/FieldFrame.jsx';\nimport {selectAllStateOf} from '@/inputs/selectAllState.js';\nimport {piecePart, usePieceProps, withPieceSlots} from '@/theme/themePieces.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaCheckboxGroupInput)\nconst THEME_NAME = 'CalcitaCheckboxGroupInput';\nconst CheckboxGroupRoot = piecePart(THEME_NAME, 'Root', FormGroup);\nconst CheckboxGroupSelectAll = piecePart(THEME_NAME, 'SelectAll', FormControlLabel);\nconst CheckboxGroupOption = piecePart(THEME_NAME, 'Option', FormControlLabel);\n\n/**\n * @param {object} props\n * @param {string} [props.name]\n * @param {import('react').ReactNode} props.label ya traducida\n * @param {Array<string|number>|null|undefined} props.value las marcadas ([] = ninguna)\n * @param {(values: Array<string|number>) => void} props.onChange la lista nueva, en el orden de las opciones\n * @param {() => void} [props.onBlur]\n * @param {Array<{value: string|number, label: string, description?: string, disabled?: boolean}>} props.options\n * @param {boolean} [props.row] en renglón en lugar de una debajo de otra\n * @param {boolean} [props.selectAll] «Elegir todos» arriba\n * @param {string|{key: string, values?: object}} [props.error]\n * @param {string} [props.helperText] ya traducida\n * @param {boolean} [props.disabled]\n * @param {object} [props.sx]\n * @param {Record<string, string|object>} [props.messages] el texto del error de este campo, por regla de validación\n * ({required: 'Escriba el RFC', matches: 'El RFC lleva 12 o 13 caracteres'}): gana al de Calcita y al de los textos\n * @param {object} [props.texts] los textos que cambian, con la forma de `common` en los textos del paquete\n */\nfunction CheckboxGroupInputContent(props) {\n const {\n name, label, value, onChange, onBlur, options, row = false, selectAll = false, error, helperText = ' ', disabled = false, sx, messages, texts,\n ...unknownProps\n } = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const {t} = useToolkitTranslation('common', texts);\n // el texto del error: el de la pantalla (messages), el del proyecto para este campo o el de Calcita\n const validationTextOf = useValidationText({name, label, messages});\n const generatedId = useId();\n const groupId = `${name ?? 'checkbox-group'}-${generatedId}`;\n const labelId = `${groupId}-label`;\n const errorText = validationTextOf(error);\n const checkedValues = Array.isArray(value) ? value : [];\n const selectAllState = selectAllStateOf(checkedValues, options.filter((option) => !option.disabled).map((option) => option.value));\n // la lista en el orden de las opciones, no en el que se fueron marcando (así viaja siempre igual)\n const inOptionsOrder = (values) => options.map((option) => option.value).filter((optionValue) => values.includes(optionValue));\n const change = (values) => {\n onChange(inOptionsOrder(values));\n onBlur?.();\n };\n const toggle = (optionValue) => change(checkedValues.includes(optionValue)\n ? checkedValues.filter((checkedValue) => checkedValue !== optionValue)\n : [...checkedValues, optionValue]);\n\n return (\n <FieldFrame inputId={`${groupId}-first`} labelId={labelId} label={label}>\n <div>\n <CheckboxGroupRoot row={row} role=\"group\" aria-labelledby={labelId} ownSx={{columnGap: 2}} sx={sx}>\n {selectAll && (\n <CheckboxGroupSelectAll\n disabled={disabled}\n label={selectAllState.allChosen ? t('form.unselectAll') : t('form.selectAll', {count: options.length})}\n control={(\n <Checkbox size=\"small\" checked={selectAllState.allChosen} indeterminate={selectAllState.someChosen}\n onChange={() => change(selectAllState.toggled())} />\n )}\n ownSx={{'& .MuiFormControlLabel-label': {fontWeight: 600}}}\n />\n )}\n {options.map((option, index) => (\n <CheckboxGroupOption\n key={option.value}\n disabled={disabled || option.disabled}\n ownSx={{alignItems: option.description ? 'flex-start' : 'center'}}\n control={(\n <Checkbox size=\"small\" name={name} id={index === 0 ? `${groupId}-first` : undefined}\n checked={checkedValues.includes(option.value)} onChange={() => toggle(option.value)}\n sx={option.description ? {mt: -0.5} : undefined} />\n )}\n label={option.description ? (\n <Stack component=\"span\">\n <Typography component=\"span\" variant=\"body2\">{option.label}</Typography>\n <Typography component=\"span\" variant=\"caption\" color=\"text.secondary\">{option.description}</Typography>\n </Stack>\n ) : option.label}\n />\n ))}\n </CheckboxGroupRoot>\n <FormHelperText error={Boolean(errorText)}>{errorText ?? helperText}</FormHelperText>\n </div>\n </FieldFrame>\n );\n}\n\n// lo que se exporta: la pieza con slots y slotProps para cambiar sus partes (themePieces.js)\nconst CheckboxGroupInput = withPieceSlots(CheckboxGroupInputContent, THEME_NAME);\nexport default CheckboxGroupInput;\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"CheckboxGroupInput.js","sources":["../../src/inputs/CheckboxGroupInput.jsx"],"sourcesContent":["// El grupo de casillas: pocas opciones a la vista (los canales de aviso, los días que abre una sucursal), cada una con su\n// casilla y, si hace falta, una explicación abajo; al formulario llega la lista de las marcadas. Con pocas opciones es más\n// rápido que abrir una lista; con muchas, SelectInput o CatalogInput con `multiple`.\n//\n// selectAll pone «Elegir todos» arriba, con su casilla a medias si hay algunas marcadas. row las acomoda en renglón (en el\n// teléfono bajan solas). Dentro de una FormSection se vuelve renglón, como los demás campos.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaCheckboxGroupInput con defaultProps y\n// styleOverrides de sus partes: root, selectAll, option.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useId} from 'react';\nimport Checkbox from '@mui/material/Checkbox';\nimport FormControlLabel from '@mui/material/FormControlLabel';\nimport FormGroup from '@mui/material/FormGroup';\nimport FormHelperText from '@mui/material/FormHelperText';\nimport Stack from '@mui/material/Stack';\nimport Typography from '@mui/material/Typography';\n\nimport {useValidationText} from '@/forms/useValidationText.js';\nimport FieldFrame from '@/inputs/FieldFrame.jsx';\nimport {selectAllStateOf} from '@/inputs/selectAllState.js';\nimport {piecePart, usePieceProps, withPieceSlots} from '@/theme/themePieces.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaCheckboxGroupInput)\nconst THEME_NAME = 'CalcitaCheckboxGroupInput';\nconst CheckboxGroupRoot = piecePart(THEME_NAME, 'Root', FormGroup);\nconst CheckboxGroupSelectAll = piecePart(THEME_NAME, 'SelectAll', FormControlLabel);\nconst CheckboxGroupOption = piecePart(THEME_NAME, 'Option', FormControlLabel);\n\n/**\n * @param {object} props\n * @param {string} [props.name]\n * @param {import('react').ReactNode} props.label ya traducida\n * @param {Array<string|number>|null|undefined} props.value las marcadas ([] = ninguna)\n * @param {(values: Array<string|number>) => void} props.onChange la lista nueva, en el orden de las opciones\n * @param {() => void} [props.onBlur]\n * @param {Array<{value: string|number, label: string, description?: string, disabled?: boolean}>} props.options\n * @param {boolean} [props.row] en renglón en lugar de una debajo de otra\n * @param {boolean} [props.selectAll] «Elegir todos» arriba\n * @param {string|{key: string, values?: object}} [props.error]\n * @param {string} [props.helperText] ya traducida\n * @param {boolean} [props.disabled]\n * @param {boolean} [props.required] obligatorio: el asterisco en la etiqueta (lo revisa el esquema del formulario)\n * @param {object} [props.sx]\n * @param {Record<string, string|object>} [props.messages] el texto del error de este campo, por regla de validación\n * ({required: 'Escriba el RFC', matches: 'El RFC lleva 12 o 13 caracteres'}): gana al de Calcita y al de los textos\n * @param {object} [props.texts] los textos que cambian, con la forma de `common` en los textos del paquete\n */\nfunction CheckboxGroupInputContent(props) {\n const {\n name, label, value, onChange, onBlur, options, row = false, selectAll = false, error, helperText = ' ', disabled = false, required = false, sx, messages, texts,\n ...unknownProps\n } = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const {t} = useToolkitTranslation('common', texts);\n // el texto del error: el de la pantalla (messages), el del proyecto para este campo o el de Calcita\n const validationTextOf = useValidationText({name, label, messages});\n const generatedId = useId();\n const groupId = `${name ?? 'checkbox-group'}-${generatedId}`;\n const labelId = `${groupId}-label`;\n const errorText = validationTextOf(error);\n const checkedValues = Array.isArray(value) ? value : [];\n const selectAllState = selectAllStateOf(checkedValues, options.filter((option) => !option.disabled).map((option) => option.value));\n // la lista en el orden de las opciones, no en el que se fueron marcando (así viaja siempre igual)\n const inOptionsOrder = (values) => options.map((option) => option.value).filter((optionValue) => values.includes(optionValue));\n const change = (values) => {\n onChange(inOptionsOrder(values));\n onBlur?.();\n };\n const toggle = (optionValue) => change(checkedValues.includes(optionValue)\n ? checkedValues.filter((checkedValue) => checkedValue !== optionValue)\n : [...checkedValues, optionValue]);\n\n return (\n <FieldFrame inputId={`${groupId}-first`} labelId={labelId} label={label} required={required}>\n <div>\n <CheckboxGroupRoot row={row} role=\"group\" aria-labelledby={labelId} ownSx={{columnGap: 2}} sx={sx}>\n {selectAll && (\n <CheckboxGroupSelectAll\n disabled={disabled}\n label={selectAllState.allChosen ? t('form.unselectAll') : t('form.selectAll', {count: options.length})}\n control={(\n <Checkbox size=\"small\" checked={selectAllState.allChosen} indeterminate={selectAllState.someChosen}\n onChange={() => change(selectAllState.toggled())} />\n )}\n ownSx={{'& .MuiFormControlLabel-label': {fontWeight: 600}}}\n />\n )}\n {options.map((option, index) => (\n <CheckboxGroupOption\n key={option.value}\n disabled={disabled || option.disabled}\n ownSx={{alignItems: option.description ? 'flex-start' : 'center'}}\n control={(\n <Checkbox size=\"small\" name={name} id={index === 0 ? `${groupId}-first` : undefined}\n checked={checkedValues.includes(option.value)} onChange={() => toggle(option.value)}\n sx={option.description ? {mt: -0.5} : undefined} />\n )}\n label={option.description ? (\n <Stack component=\"span\">\n <Typography component=\"span\" variant=\"body2\">{option.label}</Typography>\n <Typography component=\"span\" variant=\"caption\" color=\"text.secondary\">{option.description}</Typography>\n </Stack>\n ) : option.label}\n />\n ))}\n </CheckboxGroupRoot>\n <FormHelperText error={Boolean(errorText)}>{errorText ?? helperText}</FormHelperText>\n </div>\n </FieldFrame>\n );\n}\n\n// lo que se exporta: la pieza con slots y slotProps para cambiar sus partes (themePieces.js)\nconst CheckboxGroupInput = withPieceSlots(CheckboxGroupInputContent, THEME_NAME);\nexport default CheckboxGroupInput;\n"],"names":[],"mappings":"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"}
@@ -32,6 +32,7 @@ function ColorInputContent(props) {
32
32
  helperText = " ",
33
33
  checkContrast = true,
34
34
  disabled = false,
35
+ required = false,
35
36
  inputRef,
36
37
  messages,
37
38
  texts,
@@ -49,7 +50,7 @@ function ColorInputContent(props) {
49
50
  const errorText = validationTextOf(error);
50
51
  const contrastText = contrastRatio && checkContrast ? t("form.colorContrast", { ratio: contrastRatio.toFixed(RATIO_DECIMALS) }) : helperText;
51
52
  const choose = (nextColor) => onChange(nextColor.toUpperCase());
52
- return /* @__PURE__ */ jsx(FieldFrame, { inputId, label, children: /* @__PURE__ */ jsxs(Stack, { spacing: 1, children: [
53
+ return /* @__PURE__ */ jsx(FieldFrame, { inputId, label, required, children: /* @__PURE__ */ jsxs(Stack, { spacing: 1, children: [
53
54
  /* @__PURE__ */ jsx(
54
55
  TextField,
55
56
  {
@@ -60,6 +61,7 @@ function ColorInputContent(props) {
60
61
  fullWidth: true,
61
62
  value: color,
62
63
  disabled,
64
+ required,
63
65
  placeholder: "#RRGGBB",
64
66
  onChange: (event) => choose(event.target.value),
65
67
  onBlur,
@@ -1 +1 @@
1
- {"version":3,"file":"ColorInput.js","sources":["../../src/inputs/ColorInput.jsx"],"sourcesContent":["// Campo de COLOR suelto (viene de «Identidad de documentos» de PLD): la muestra redonda (el «color well» de Apple, que abre\n// el selector del sistema) dentro del campo del texto #RRGGBB, el contraste contra el blanco mientras se elige y, si el color casi no se leería, el mismo tono más oscuro con su botón\n// para usarlo.\n//\n// Para lo que se imprime o se lee sobre blanco (el color de una marca en un comprobante). El color llega al formulario en\n// mayúsculas (#2B3A67); '' = sin color.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaColorInput con defaultProps y\n// styleOverrides de sus partes: swatch, nativeInput.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useId} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport Alert from '@mui/material/Alert';\nimport Box from '@mui/material/Box';\nimport Button from '@mui/material/Button';\nimport InputAdornment from '@mui/material/InputAdornment';\nimport Stack from '@mui/material/Stack';\nimport TextField from '@mui/material/TextField';\n\nimport {useValidationText} from '@/forms/useValidationText.js';\nimport {HEX_COLOR, MIN_CONTRAST, contrastOnWhite, readableVariantOf} from '@/inputs/colorContrast.js';\nimport FieldFrame from '@/inputs/FieldFrame.jsx';\nimport {piecePart, usePieceProps, withPieceSlots} from '@/theme/themePieces.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaColorInput)\nconst THEME_NAME = 'CalcitaColorInput';\nconst ColorSwatch = piecePart(THEME_NAME, 'Swatch', Box);\nconst ColorNativeInput = piecePart(THEME_NAME, 'NativeInput', Box);\n\n// #RRGGBB\nconst COLOR_LENGTH = 7;\n// el color que enseña el selector del navegador mientras el texto no es un color\nconst NEUTRAL_COLOR = '#000000';\nconst RATIO_DECIMALS = 1;\n// la muestra redonda (px) y su orilla fina en claro y en oscuro\nconst SWATCH_PIXELS = 24;\nconst SWATCH_EDGE = Object.freeze({light: 'rgba(15, 23, 42, 0.18)', dark: 'rgba(255, 255, 255, 0.24)'});\n\n/**\n * @param {object} props\n * @param {string} [props.name]\n * @param {string} props.label ya traducido\n * @param {string|null|undefined} props.value '#RRGGBB' o ''\n * @param {(color: string) => void} props.onChange recibe el color en mayúsculas\n * @param {() => void} [props.onBlur]\n * @param {string|{key: string, values?: object}} [props.error]\n * @param {string} [props.helperText] cuando no hay color\n * @param {boolean} [props.checkContrast] avisa si casi no se lee sobre blanco (por omisión sí)\n * @param {boolean} [props.disabled]\n * @param {import('react').Ref<HTMLInputElement>} [props.inputRef]\n * @param {Record<string, string|object>} [props.messages] el texto del error de este campo, por regla de validación\n * ({required: 'Escriba el RFC', matches: 'El RFC lleva 12 o 13 caracteres'}): gana al de Calcita y al de los textos\n * @param {object} [props.texts] los textos que cambian, con la forma de `common` en los textos del paquete\n */\nfunction ColorInputContent(props) {\n // lo que el proyecto puso en su tema (components.CalcitaColorInput.defaultProps) y encima lo que llega aquí\n const {\n name, label, value, onChange, onBlur, error, helperText = ' ', checkContrast = true, disabled = false, inputRef, messages, texts,\n ...unknownProps\n} = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const {t} = useToolkitTranslation('common', texts);\n // el texto del error: el de la pantalla (messages), el del proyecto para este campo o el de Calcita\n const validationTextOf = useValidationText({name, label, messages});\n const generatedId = useId();\n const inputId = `${name ?? 'color'}-${generatedId}`;\n const color = value ?? '';\n const isValidColor = HEX_COLOR.test(color);\n const contrastRatio = isValidColor ? contrastOnWhite(color) : null;\n const suggestion = checkContrast && isValidColor && contrastRatio < MIN_CONTRAST ? readableVariantOf(color) : null;\n const errorText = validationTextOf(error);\n const contrastText = contrastRatio && checkContrast ? t('form.colorContrast', {ratio: contrastRatio.toFixed(RATIO_DECIMALS)}) : helperText;\n const choose = (nextColor) => onChange(nextColor.toUpperCase());\n\n return (\n <FieldFrame inputId={inputId} label={label}>\n <Stack spacing={1}>\n {/* un solo campo: la muestra redonda al inicio (el «color well» de Apple) y el código al lado. Picar la muestra\n abre el selector de colores del sistema (un input type=color invisible encima de ella). Antes eran dos cajas\n sueltas (el selector del navegador en la suya y el código en otra) y se veían como piezas sueltas */}\n <TextField\n name={name}\n id={inputId}\n inputRef={inputRef}\n size=\"small\"\n fullWidth\n value={color}\n disabled={disabled}\n placeholder=\"#RRGGBB\"\n onChange={(event) => choose(event.target.value)}\n onBlur={onBlur}\n error={Boolean(errorText)}\n helperText={errorText ?? contrastText}\n slotProps={{\n input: {\n startAdornment: (\n <InputAdornment position=\"start\">\n <ColorSwatch component=\"label\"\n ownSx={(theme) => ({\n position: 'relative', display: 'block', flexShrink: 0, width: SWATCH_PIXELS, height: SWATCH_PIXELS,\n borderRadius: '50%', cursor: disabled ? 'default' : 'pointer',\n backgroundColor: isValidColor ? color : 'transparent',\n // la orilla de la muestra: que se vea aunque el color sea el del fondo (blanco en claro, negro en oscuro)\n boxShadow: `inset 0 0 0 1px ${SWATCH_EDGE.light}`,\n ...theme.applyStyles('dark', {boxShadow: `inset 0 0 0 1px ${SWATCH_EDGE.dark}`}),\n })}>\n <ColorNativeInput component=\"input\" type=\"color\" value={isValidColor ? color : NEUTRAL_COLOR} disabled={disabled}\n aria-label={t('form.pickColor')} onChange={(event) => choose(event.target.value)}\n ownSx={{position: 'absolute', inset: 0, width: '100%', height: '100%', opacity: 0, border: 0, p: 0, cursor: 'inherit'}} />\n </ColorSwatch>\n </InputAdornment>\n ),\n },\n htmlInput: {maxLength: COLOR_LENGTH, spellCheck: false, style: {fontVariantNumeric: 'tabular-nums'}},\n }}\n />\n {suggestion && (\n <Alert severity=\"warning\"\n action={<Button color=\"inherit\" size=\"small\" onClick={() => choose(suggestion)}>{t('form.colorUseSuggestion', {color: suggestion})}</Button>}>\n {t('form.colorLowContrast', {suggestion})}\n </Alert>\n )}\n </Stack>\n </FieldFrame>\n );\n}\n\n// lo que se exporta: la pieza con slots y slotProps para cambiar sus partes (themePieces.js)\nconst ColorInput = withPieceSlots(ColorInputContent, THEME_NAME);\nexport default ColorInput;\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ColorInput.js","sources":["../../src/inputs/ColorInput.jsx"],"sourcesContent":["// Campo de COLOR suelto (viene de «Identidad de documentos» de PLD): la muestra redonda (el «color well» de Apple, que abre\n// el selector del sistema) dentro del campo del texto #RRGGBB, el contraste contra el blanco mientras se elige y, si el color casi no se leería, el mismo tono más oscuro con su botón\n// para usarlo.\n//\n// Para lo que se imprime o se lee sobre blanco (el color de una marca en un comprobante). El color llega al formulario en\n// mayúsculas (#2B3A67); '' = sin color.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaColorInput con defaultProps y\n// styleOverrides de sus partes: swatch, nativeInput.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useId} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport Alert from '@mui/material/Alert';\nimport Box from '@mui/material/Box';\nimport Button from '@mui/material/Button';\nimport InputAdornment from '@mui/material/InputAdornment';\nimport Stack from '@mui/material/Stack';\nimport TextField from '@mui/material/TextField';\n\nimport {useValidationText} from '@/forms/useValidationText.js';\nimport {HEX_COLOR, MIN_CONTRAST, contrastOnWhite, readableVariantOf} from '@/inputs/colorContrast.js';\nimport FieldFrame from '@/inputs/FieldFrame.jsx';\nimport {piecePart, usePieceProps, withPieceSlots} from '@/theme/themePieces.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaColorInput)\nconst THEME_NAME = 'CalcitaColorInput';\nconst ColorSwatch = piecePart(THEME_NAME, 'Swatch', Box);\nconst ColorNativeInput = piecePart(THEME_NAME, 'NativeInput', Box);\n\n// #RRGGBB\nconst COLOR_LENGTH = 7;\n// el color que enseña el selector del navegador mientras el texto no es un color\nconst NEUTRAL_COLOR = '#000000';\nconst RATIO_DECIMALS = 1;\n// la muestra redonda (px) y su orilla fina en claro y en oscuro\nconst SWATCH_PIXELS = 24;\nconst SWATCH_EDGE = Object.freeze({light: 'rgba(15, 23, 42, 0.18)', dark: 'rgba(255, 255, 255, 0.24)'});\n\n/**\n * @param {object} props\n * @param {string} [props.name]\n * @param {string} props.label ya traducido\n * @param {string|null|undefined} props.value '#RRGGBB' o ''\n * @param {(color: string) => void} props.onChange recibe el color en mayúsculas\n * @param {() => void} [props.onBlur]\n * @param {string|{key: string, values?: object}} [props.error]\n * @param {string} [props.helperText] cuando no hay color\n * @param {boolean} [props.checkContrast] avisa si casi no se lee sobre blanco (por omisión sí)\n * @param {boolean} [props.disabled]\n * @param {boolean} [props.required] obligatorio: el asterisco en la etiqueta y, como en MUI, required en el campo\n * @param {import('react').Ref<HTMLInputElement>} [props.inputRef]\n * @param {Record<string, string|object>} [props.messages] el texto del error de este campo, por regla de validación\n * ({required: 'Escriba el RFC', matches: 'El RFC lleva 12 o 13 caracteres'}): gana al de Calcita y al de los textos\n * @param {object} [props.texts] los textos que cambian, con la forma de `common` en los textos del paquete\n */\nfunction ColorInputContent(props) {\n // lo que el proyecto puso en su tema (components.CalcitaColorInput.defaultProps) y encima lo que llega aquí\n const {\n name, label, value, onChange, onBlur, error, helperText = ' ', checkContrast = true, disabled = false, required = false, inputRef, messages, texts,\n ...unknownProps\n} = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const {t} = useToolkitTranslation('common', texts);\n // el texto del error: el de la pantalla (messages), el del proyecto para este campo o el de Calcita\n const validationTextOf = useValidationText({name, label, messages});\n const generatedId = useId();\n const inputId = `${name ?? 'color'}-${generatedId}`;\n const color = value ?? '';\n const isValidColor = HEX_COLOR.test(color);\n const contrastRatio = isValidColor ? contrastOnWhite(color) : null;\n const suggestion = checkContrast && isValidColor && contrastRatio < MIN_CONTRAST ? readableVariantOf(color) : null;\n const errorText = validationTextOf(error);\n const contrastText = contrastRatio && checkContrast ? t('form.colorContrast', {ratio: contrastRatio.toFixed(RATIO_DECIMALS)}) : helperText;\n const choose = (nextColor) => onChange(nextColor.toUpperCase());\n\n return (\n <FieldFrame inputId={inputId} label={label} required={required}>\n <Stack spacing={1}>\n {/* un solo campo: la muestra redonda al inicio (el «color well» de Apple) y el código al lado. Picar la muestra\n abre el selector de colores del sistema (un input type=color invisible encima de ella). Antes eran dos cajas\n sueltas (el selector del navegador en la suya y el código en otra) y se veían como piezas sueltas */}\n <TextField\n name={name}\n id={inputId}\n inputRef={inputRef}\n size=\"small\"\n fullWidth\n value={color}\n disabled={disabled}\n required={required}\n placeholder=\"#RRGGBB\"\n onChange={(event) => choose(event.target.value)}\n onBlur={onBlur}\n error={Boolean(errorText)}\n helperText={errorText ?? contrastText}\n slotProps={{\n input: {\n startAdornment: (\n <InputAdornment position=\"start\">\n <ColorSwatch component=\"label\"\n ownSx={(theme) => ({\n position: 'relative', display: 'block', flexShrink: 0, width: SWATCH_PIXELS, height: SWATCH_PIXELS,\n borderRadius: '50%', cursor: disabled ? 'default' : 'pointer',\n backgroundColor: isValidColor ? color : 'transparent',\n // la orilla de la muestra: que se vea aunque el color sea el del fondo (blanco en claro, negro en oscuro)\n boxShadow: `inset 0 0 0 1px ${SWATCH_EDGE.light}`,\n ...theme.applyStyles('dark', {boxShadow: `inset 0 0 0 1px ${SWATCH_EDGE.dark}`}),\n })}>\n <ColorNativeInput component=\"input\" type=\"color\" value={isValidColor ? color : NEUTRAL_COLOR} disabled={disabled}\n aria-label={t('form.pickColor')} onChange={(event) => choose(event.target.value)}\n ownSx={{position: 'absolute', inset: 0, width: '100%', height: '100%', opacity: 0, border: 0, p: 0, cursor: 'inherit'}} />\n </ColorSwatch>\n </InputAdornment>\n ),\n },\n htmlInput: {maxLength: COLOR_LENGTH, spellCheck: false, style: {fontVariantNumeric: 'tabular-nums'}},\n }}\n />\n {suggestion && (\n <Alert severity=\"warning\"\n action={<Button color=\"inherit\" size=\"small\" onClick={() => choose(suggestion)}>{t('form.colorUseSuggestion', {color: suggestion})}</Button>}>\n {t('form.colorLowContrast', {suggestion})}\n </Alert>\n )}\n </Stack>\n </FieldFrame>\n );\n}\n\n// lo que se exporta: la pieza con slots y slotProps para cambiar sus partes (themePieces.js)\nconst ColorInput = withPieceSlots(ColorInputContent, THEME_NAME);\nexport default ColorInput;\n"],"names":[],"mappings":"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"}
@@ -4,20 +4,35 @@ import Stack from "@mui/material/Stack";
4
4
  import Typography from "@mui/material/Typography";
5
5
  import { FormRow } from "./FormSection.js";
6
6
  import { useIsInsideFormSection } from "./formLayoutContext.js";
7
+ import { requiredMarkSxOf, REQUIRED_LABEL_ATTRIBUTE } from "./requiredMark.js";
7
8
  import { piecePart, withPieceSlots, usePieceProps } from "../theme/themePieces.js";
8
9
  import { warnUnknownProps } from "../toolkit/unknownProps.js";
10
+ import { useToolkitTranslation } from "../toolkit/useToolkitTranslation.js";
9
11
  const THEME_NAME = "CalcitaFieldFrame";
10
12
  const FieldFrameRoot = piecePart(THEME_NAME, "Root", Stack);
11
13
  const FieldFrameLabel = piecePart(THEME_NAME, "Label", Typography);
12
14
  function FieldFrameContent(props) {
13
- const { inputId, labelId, label, children, ...unknownProps } = usePieceProps(props, THEME_NAME);
15
+ const { inputId, labelId, label, required = false, children, ...unknownProps } = usePieceProps(props, THEME_NAME);
14
16
  warnUnknownProps(THEME_NAME, unknownProps);
15
17
  const isRow = useIsInsideFormSection();
18
+ const { t } = useToolkitTranslation("common");
16
19
  if (isRow) {
17
- return /* @__PURE__ */ jsx(FormRow, { label, htmlFor: inputId, labelId, children });
20
+ return /* @__PURE__ */ jsx(FormRow, { label, htmlFor: inputId, labelId, required, children });
18
21
  }
19
22
  return /* @__PURE__ */ jsxs(FieldFrameRoot, { spacing: 0.75, children: [
20
- /* @__PURE__ */ jsx(FieldFrameLabel, { component: "label", id: labelId, htmlFor: inputId, variant: "overline", color: "text.secondary", children: label }),
23
+ /* @__PURE__ */ jsx(
24
+ FieldFrameLabel,
25
+ {
26
+ component: "label",
27
+ id: labelId,
28
+ htmlFor: inputId,
29
+ variant: "overline",
30
+ color: "text.secondary",
31
+ ...required && { [REQUIRED_LABEL_ATTRIBUTE]: "true" },
32
+ ownSx: required ? requiredMarkSxOf(t("form.requiredMark")) : void 0,
33
+ children: label
34
+ }
35
+ ),
21
36
  children
22
37
  ] });
23
38
  }
@@ -1 +1 @@
1
- {"version":3,"file":"FieldFrame.js","sources":["../../src/inputs/FieldFrame.jsx"],"sourcesContent":["// El marco de todos los campos del paquete. Fuera de una FormSection, la etiqueta va ARRIBA del campo, ligada a él por su\n// id; dentro de una FormSection (el formulario agrupado de Apple), el campo se vuelve un renglón: la etiqueta a la\n// izquierda y el valor a la derecha (FormRow).\n//\n// La etiqueta va afuera y no flotando adentro del campo: se lee igual con el campo vacío o lleno, y no brinca al escribir.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaFieldFrame con styleOverrides de sus\n// partes root (la columna) y label (la etiqueta de arriba), y slots/slotProps para cambiarlas. Dentro de una FormSection el\n// renglón es CalcitaFormRow.\n//\n// Genérico del paquete: function (regla de la casa).\nimport Stack from '@mui/material/Stack';\nimport Typography from '@mui/material/Typography';\n\nimport {FormRow} from '@/inputs/FormSection.jsx';\nimport {useIsInsideFormSection} from '@/inputs/formLayoutContext.js';\nimport {piecePart, usePieceProps, withPieceSlots} from '@/theme/themePieces.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaFieldFrame)\nconst THEME_NAME = 'CalcitaFieldFrame';\nconst FieldFrameRoot = piecePart(THEME_NAME, 'Root', Stack);\nconst FieldFrameLabel = piecePart(THEME_NAME, 'Label', Typography);\n\n/**\n * @param {object} props\n * @param {string} props.inputId el id del campo (la etiqueta lo nombra para el lector de pantalla)\n * @param {string} [props.labelId] el id de la etiqueta, para un campo que no es un <input> (la lista desplegable de MUI es un\n * div: `for` no la une con su etiqueta y el campo la nombra con aria-labelledby)\n * @param {import('react').ReactNode} props.label ya traducida\n * @param {import('react').ReactNode} props.children el campo\n */\nfunction FieldFrameContent(props) {\n const {inputId, labelId, label, children, ...unknownProps} = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const isRow = useIsInsideFormSection();\n\n if (isRow) {\n return <FormRow label={label} htmlFor={inputId} labelId={labelId}>{children}</FormRow>;\n }\n\n return (\n <FieldFrameRoot spacing={0.75}>\n <FieldFrameLabel component=\"label\" id={labelId} htmlFor={inputId} variant=\"overline\" color=\"text.secondary\">{label}</FieldFrameLabel>\n {children}\n </FieldFrameRoot>\n );\n}\n\n// lo que se exporta: la pieza con slots y slotProps para cambiar sus partes (themePieces.js)\nconst FieldFrame = withPieceSlots(FieldFrameContent, THEME_NAME);\nexport default FieldFrame;\n"],"names":[],"mappings":";;;;;;;;AAoBA,MAAM,aAAa;AACnB,MAAM,iBAAiB,UAAU,YAAY,QAAQ,KAAK;AAC1D,MAAM,kBAAkB,UAAU,YAAY,SAAS,UAAU;AAUjE,SAAS,kBAAkB,OAAO;AAC9B,QAAM,EAAC,SAAS,SAAS,OAAO,UAAU,GAAG,iBAAgB,cAAc,OAAO,UAAU;AAC5F,mBAAiB,YAAY,YAAY;AACzC,QAAM,QAAQ,uBAAA;AAEd,MAAI,OAAO;AACP,+BAAQ,SAAA,EAAQ,OAAc,SAAS,SAAS,SAAmB,UAAS;AAAA,EAChF;AAEA,SACI,qBAAC,gBAAA,EAAe,SAAS,MACrB,UAAA;AAAA,IAAA,oBAAC,iBAAA,EAAgB,WAAU,SAAQ,IAAI,SAAS,SAAS,SAAS,SAAQ,YAAW,OAAM,kBAAkB,UAAA,OAAM;AAAA,IAClH;AAAA,EAAA,GACL;AAER;AAGA,MAAM,aAAa,eAAe,mBAAmB,UAAU;"}
1
+ {"version":3,"file":"FieldFrame.js","sources":["../../src/inputs/FieldFrame.jsx"],"sourcesContent":["// El marco de todos los campos del paquete. Fuera de una FormSection, la etiqueta va ARRIBA del campo, ligada a él por su\n// id; dentro de una FormSection (el formulario agrupado de Apple), el campo se vuelve un renglón: la etiqueta a la\n// izquierda y el valor a la derecha (FormRow).\n//\n// La etiqueta va afuera y no flotando adentro del campo: se lee igual con el campo vacío o lleno, y no brinca al escribir.\n//\n// Un campo obligatorio (required) lleva un asterisco en la etiqueta, como en MUI (requiredMark.js: va en el estilo y no como\n// texto, para que la etiqueta siga diciendo lo mismo). Antes no se veía por ningún lado: la etiqueta la pinta este marco y no\n// la de MUI, que es la que ponía el asterisco.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaFieldFrame con styleOverrides de sus\n// partes root (la columna) y label (la etiqueta de arriba; el asterisco es su '&[data-required]::after'), y slots/slotProps\n// para cambiarlas. Dentro de una FormSection el\n// renglón es CalcitaFormRow.\n//\n// Genérico del paquete: function (regla de la casa).\nimport Stack from '@mui/material/Stack';\nimport Typography from '@mui/material/Typography';\n\nimport {FormRow} from '@/inputs/FormSection.jsx';\nimport {useIsInsideFormSection} from '@/inputs/formLayoutContext.js';\nimport {REQUIRED_LABEL_ATTRIBUTE, requiredMarkSxOf} from '@/inputs/requiredMark.js';\nimport {piecePart, usePieceProps, withPieceSlots} from '@/theme/themePieces.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaFieldFrame)\nconst THEME_NAME = 'CalcitaFieldFrame';\nconst FieldFrameRoot = piecePart(THEME_NAME, 'Root', Stack);\nconst FieldFrameLabel = piecePart(THEME_NAME, 'Label', Typography);\n\n/**\n * @param {object} props\n * @param {string} props.inputId el id del campo (la etiqueta lo nombra para el lector de pantalla)\n * @param {string} [props.labelId] el id de la etiqueta, para un campo que no es un <input> (la lista desplegable de MUI es un\n * div: `for` no la une con su etiqueta y el campo la nombra con aria-labelledby)\n * @param {import('react').ReactNode} props.label ya traducida\n * @param {boolean} [props.required] el asterisco en la etiqueta\n * @param {import('react').ReactNode} props.children el campo\n */\nfunction FieldFrameContent(props) {\n const {inputId, labelId, label, required = false, children, ...unknownProps} = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const isRow = useIsInsideFormSection();\n const {t} = useToolkitTranslation('common');\n\n if (isRow) {\n return <FormRow label={label} htmlFor={inputId} labelId={labelId} required={required}>{children}</FormRow>;\n }\n\n return (\n <FieldFrameRoot spacing={0.75}>\n <FieldFrameLabel component=\"label\" id={labelId} htmlFor={inputId} variant=\"overline\" color=\"text.secondary\"\n {...(required && {[REQUIRED_LABEL_ATTRIBUTE]: 'true'})} ownSx={required ? requiredMarkSxOf(t('form.requiredMark')) : undefined}>\n {label}\n </FieldFrameLabel>\n {children}\n </FieldFrameRoot>\n );\n}\n\n// lo que se exporta: la pieza con slots y slotProps para cambiar sus partes (themePieces.js)\nconst FieldFrame = withPieceSlots(FieldFrameContent, THEME_NAME);\nexport default FieldFrame;\n"],"names":[],"mappings":";;;;;;;;;;AA2BA,MAAM,aAAa;AACnB,MAAM,iBAAiB,UAAU,YAAY,QAAQ,KAAK;AAC1D,MAAM,kBAAkB,UAAU,YAAY,SAAS,UAAU;AAWjE,SAAS,kBAAkB,OAAO;AAC9B,QAAM,EAAC,SAAS,SAAS,OAAO,WAAW,OAAO,UAAU,GAAG,aAAA,IAAgB,cAAc,OAAO,UAAU;AAC9G,mBAAiB,YAAY,YAAY;AACzC,QAAM,QAAQ,uBAAA;AACd,QAAM,EAAC,EAAA,IAAK,sBAAsB,QAAQ;AAE1C,MAAI,OAAO;AACP,+BAAQ,SAAA,EAAQ,OAAc,SAAS,SAAS,SAAkB,UAAqB,UAAS;AAAA,EACpG;AAEA,SACI,qBAAC,gBAAA,EAAe,SAAS,MACrB,UAAA;AAAA,IAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QAAgB,WAAU;AAAA,QAAQ,IAAI;AAAA,QAAS,SAAS;AAAA,QAAS,SAAQ;AAAA,QAAW,OAAM;AAAA,QACzE,GAAI,YAAY,EAAC,CAAC,wBAAwB,GAAG,OAAA;AAAA,QAAU,OAAO,WAAW,iBAAiB,EAAE,mBAAmB,CAAC,IAAI;AAAA,QACjI,UAAA;AAAA,MAAA;AAAA,IAAA;AAAA,IAEJ;AAAA,EAAA,GACL;AAER;AAGA,MAAM,aAAa,eAAe,mBAAmB,UAAU;"}
@@ -1,13 +1,16 @@
1
1
  "use client";
2
2
  import { jsxs, jsx } from "react/jsx-runtime";
3
- import { useId } from "react";
3
+ import { useId, useMemo } from "react";
4
4
  import Box from "@mui/material/Box";
5
5
  import Paper from "@mui/material/Paper";
6
6
  import Typography from "@mui/material/Typography";
7
7
  import { styled, useThemeProps } from "@mui/material/styles";
8
- import { FormSectionContext } from "./formLayoutContext.js";
8
+ import { useFormSectionLayout, FormSectionContext } from "./formLayoutContext.js";
9
+ import { requiredMarkSxOf, REQUIRED_LABEL_ATTRIBUTE } from "./requiredMark.js";
9
10
  import { fieldVariablesOf, accentTextStylesOf, PANEL_RADIUS, fieldWellFromVariablesOf } from "../theme/glassSurface.js";
10
11
  import { warnUnknownProps } from "../toolkit/unknownProps.js";
12
+ import { useToolkitTranslation } from "../toolkit/useToolkitTranslation.js";
13
+ const FORM_LABEL_PLACEMENT = Object.freeze({ side: "side", topOnPhone: "topOnPhone" });
11
14
  const FORM_ROW_CLASS = "calcita-form-row";
12
15
  const ROW_MIN_HEIGHT_PIXELS = 52;
13
16
  const ROW_PADDING_BLOCK_PIXELS = 8;
@@ -37,8 +40,10 @@ function rowControlSxOf(theme) {
37
40
  }
38
41
  const SECTION_THEME_NAME = "CalcitaFormSection";
39
42
  const ROW_THEME_NAME = "CalcitaFormRow";
40
- const FormRowRoot = styled(Box, { name: ROW_THEME_NAME, slot: "Root" })(({ theme }) => {
43
+ const isPartProp = (propName) => propName !== "ownerState";
44
+ const FormRowRoot = styled(Box, { name: ROW_THEME_NAME, slot: "Root", shouldForwardProp: isPartProp })(({ theme, ownerState }) => {
41
45
  const palette = (theme.vars ?? theme).palette;
46
+ const isTopOnPhone = ownerState?.labelPlacement === FORM_LABEL_PLACEMENT.topOnPhone;
42
47
  return theme.unstable_sx({
43
48
  position: "relative",
44
49
  display: "flex",
@@ -51,15 +56,22 @@ const FormRowRoot = styled(Box, { name: ROW_THEME_NAME, slot: "Root" })(({ theme
51
56
  "&:has(.Mui-error) > label": { color: palette.error.main },
52
57
  // los tonos de los campos (los del proyecto con createToolkitTheme fields, o los de Apple) para sus campos
53
58
  ...fieldVariablesOf(theme),
54
- ...rowControlSxOf(theme)
59
+ ...rowControlSxOf(theme),
60
+ // en el teléfono, la etiqueta arriba y el campo a todo lo ancho debajo
61
+ ...isTopOnPhone && { [theme.breakpoints.down("sm")]: { flexDirection: "column", alignItems: "stretch", gap: 0.5 } }
55
62
  });
56
63
  });
57
- const FormRowLabel = styled(Typography, { name: ROW_THEME_NAME, slot: "Label" })(({ theme }) => theme.unstable_sx({
64
+ const FormRowLabel = styled(Typography, { name: ROW_THEME_NAME, slot: "Label", shouldForwardProp: isPartProp })(({ theme, ownerState }) => theme.unstable_sx({
58
65
  flex: `0 0 ${LABEL_COLUMN.share}`,
59
66
  maxWidth: LABEL_COLUMN.maxPixels,
60
67
  pt: `${LABEL_TOP_PIXELS}px`,
61
68
  lineHeight: 1.5,
62
- color: "text.primary"
69
+ color: "text.primary",
70
+ ...ownerState?.labelPlacement === FORM_LABEL_PLACEMENT.topOnPhone && {
71
+ [theme.breakpoints.down("sm")]: { flex: "none", maxWidth: "none", pt: 0 }
72
+ },
73
+ // el asterisco de lo obligatorio: aquí, debajo de lo que el proyecto ponga en su tema
74
+ ...ownerState?.requiredMark && requiredMarkSxOf(ownerState.requiredMark)
63
75
  }));
64
76
  const FormRowControl = styled(Box, { name: ROW_THEME_NAME, slot: "Control" })({
65
77
  flex: 1,
@@ -86,24 +98,43 @@ const FormSectionCard = styled(Paper, { name: SECTION_THEME_NAME, slot: "Card" }
86
98
  }));
87
99
  const FormSectionFooter = styled(Typography, { name: SECTION_THEME_NAME, slot: "Footer" })(({ theme }) => theme.unstable_sx({ px: 2, mt: 0.75 }));
88
100
  function FormRow(props) {
89
- const { label, htmlFor, labelId, children, ...unknownProps } = useThemeProps({ props, name: ROW_THEME_NAME });
101
+ const { label, htmlFor, labelId, required = false, children, ...unknownProps } = useThemeProps({ props, name: ROW_THEME_NAME });
90
102
  warnUnknownProps(ROW_THEME_NAME, unknownProps);
91
- return /* @__PURE__ */ jsxs(FormRowRoot, { className: FORM_ROW_CLASS, children: [
92
- /* @__PURE__ */ jsx(FormRowLabel, { component: "label", id: labelId, htmlFor, variant: "body1", children: label }),
103
+ const { t } = useToolkitTranslation("common");
104
+ const sectionLayout = useFormSectionLayout();
105
+ const ownerState = {
106
+ labelPlacement: sectionLayout ? sectionLayout.labelPlacement : FORM_LABEL_PLACEMENT.side,
107
+ requiredMark: required ? t("form.requiredMark") : null
108
+ };
109
+ return /* @__PURE__ */ jsxs(FormRowRoot, { className: FORM_ROW_CLASS, ownerState, children: [
110
+ /* @__PURE__ */ jsx(
111
+ FormRowLabel,
112
+ {
113
+ component: "label",
114
+ id: labelId,
115
+ htmlFor,
116
+ variant: "body1",
117
+ ownerState,
118
+ ...required && { [REQUIRED_LABEL_ATTRIBUTE]: "true" },
119
+ children: label
120
+ }
121
+ ),
93
122
  /* @__PURE__ */ jsx(FormRowControl, { children })
94
123
  ] });
95
124
  }
96
125
  function FormSection(props) {
97
- const { title, footer, children, ...unknownProps } = useThemeProps({ props, name: SECTION_THEME_NAME });
126
+ const { title, footer, labelPlacement = FORM_LABEL_PLACEMENT.side, children, ...unknownProps } = useThemeProps({ props, name: SECTION_THEME_NAME });
98
127
  warnUnknownProps(SECTION_THEME_NAME, unknownProps);
99
128
  const titleId = useId();
100
- return /* @__PURE__ */ jsx(FormSectionContext, { value: true, children: /* @__PURE__ */ jsxs(FormSectionRoot, { component: "section", "aria-labelledby": title ? titleId : void 0, children: [
129
+ const layout = useMemo(() => ({ labelPlacement }), [labelPlacement]);
130
+ return /* @__PURE__ */ jsx(FormSectionContext, { value: layout, children: /* @__PURE__ */ jsxs(FormSectionRoot, { component: "section", "aria-labelledby": title ? titleId : void 0, children: [
101
131
  title && /* @__PURE__ */ jsx(FormSectionTitle, { id: titleId, variant: "subtitle2", component: "h2", color: "text.secondary", children: title }),
102
132
  /* @__PURE__ */ jsx(FormSectionCard, { variant: "outlined", children: /* @__PURE__ */ jsx(Box, { sx: { mt: "-1px" }, children }) }),
103
133
  footer && /* @__PURE__ */ jsx(FormSectionFooter, { variant: "caption", component: "p", color: "text.secondary", children: footer })
104
134
  ] }) });
105
135
  }
106
136
  export {
137
+ FORM_LABEL_PLACEMENT,
107
138
  FORM_ROW_CLASS,
108
139
  FormRow,
109
140
  FormSection as default