flysoft-react-ui 0.5.0 → 0.5.3

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 (206) hide show
  1. package/dist/App.d.ts.map +1 -1
  2. package/dist/App.js +19 -7
  3. package/dist/components/form-controls/AutocompleteInput.d.ts +11 -3
  4. package/dist/components/form-controls/AutocompleteInput.d.ts.map +1 -1
  5. package/dist/components/form-controls/AutocompleteInput.js +411 -31
  6. package/dist/components/form-controls/Button.d.ts +3 -0
  7. package/dist/components/form-controls/Button.d.ts.map +1 -1
  8. package/dist/components/form-controls/Button.js +160 -19
  9. package/dist/components/form-controls/Checkbox.d.ts +14 -0
  10. package/dist/components/form-controls/Checkbox.d.ts.map +1 -0
  11. package/dist/components/form-controls/Checkbox.js +79 -0
  12. package/dist/components/form-controls/DateInput.d.ts +24 -4
  13. package/dist/components/form-controls/DateInput.d.ts.map +1 -1
  14. package/dist/components/form-controls/DateInput.js +492 -70
  15. package/dist/components/form-controls/DatePicker.d.ts +4 -3
  16. package/dist/components/form-controls/DatePicker.d.ts.map +1 -1
  17. package/dist/components/form-controls/DatePicker.js +26 -30
  18. package/dist/components/form-controls/Input.d.ts +10 -1
  19. package/dist/components/form-controls/Input.d.ts.map +1 -1
  20. package/dist/components/form-controls/Input.js +17 -10
  21. package/dist/components/form-controls/LinkButton.d.ts +15 -0
  22. package/dist/components/form-controls/LinkButton.d.ts.map +1 -0
  23. package/dist/components/form-controls/LinkButton.js +248 -0
  24. package/dist/components/form-controls/Pagination.d.ts +1 -0
  25. package/dist/components/form-controls/Pagination.d.ts.map +1 -1
  26. package/dist/components/form-controls/Pagination.js +3 -40
  27. package/dist/components/form-controls/RadioButtonGroup.d.ts +62 -0
  28. package/dist/components/form-controls/RadioButtonGroup.d.ts.map +1 -0
  29. package/dist/components/form-controls/RadioButtonGroup.js +220 -0
  30. package/dist/components/form-controls/SearchSelectInput-OLD.d.ts +68 -0
  31. package/dist/components/form-controls/SearchSelectInput-OLD.d.ts.map +1 -0
  32. package/dist/components/form-controls/SearchSelectInput-OLD.js +963 -0
  33. package/dist/components/form-controls/SearchSelectInput.d.ts +70 -0
  34. package/dist/components/form-controls/SearchSelectInput.d.ts.map +1 -0
  35. package/dist/components/form-controls/SearchSelectInput.js +336 -0
  36. package/dist/components/form-controls/index.d.ts +9 -1
  37. package/dist/components/form-controls/index.d.ts.map +1 -1
  38. package/dist/components/form-controls/index.js +4 -0
  39. package/dist/components/layout/Accordion.d.ts +13 -0
  40. package/dist/components/layout/Accordion.d.ts.map +1 -0
  41. package/dist/components/layout/Accordion.js +67 -0
  42. package/dist/components/layout/AppLayout.d.ts +3 -2
  43. package/dist/components/layout/AppLayout.d.ts.map +1 -1
  44. package/dist/components/layout/AppLayout.js +104 -31
  45. package/dist/components/layout/Card.d.ts +8 -3
  46. package/dist/components/layout/Card.d.ts.map +1 -1
  47. package/dist/components/layout/Card.js +18 -19
  48. package/dist/components/layout/Collection.js +1 -1
  49. package/dist/components/layout/DataTable.d.ts +3 -1
  50. package/dist/components/layout/DataTable.d.ts.map +1 -1
  51. package/dist/components/layout/DataTable.js +34 -29
  52. package/dist/components/layout/index.d.ts +2 -0
  53. package/dist/components/layout/index.d.ts.map +1 -1
  54. package/dist/components/layout/index.js +1 -0
  55. package/dist/components/utils/Avatar.d.ts +49 -0
  56. package/dist/components/utils/Avatar.d.ts.map +1 -0
  57. package/dist/components/utils/Avatar.js +93 -0
  58. package/dist/components/utils/Badge.d.ts +3 -0
  59. package/dist/components/utils/Badge.d.ts.map +1 -1
  60. package/dist/components/utils/Badge.js +131 -26
  61. package/dist/components/utils/Dialog.d.ts.map +1 -1
  62. package/dist/components/utils/Dialog.js +6 -1
  63. package/dist/components/utils/Filter.d.ts +57 -0
  64. package/dist/components/utils/Filter.d.ts.map +1 -0
  65. package/dist/components/utils/Filter.js +581 -0
  66. package/dist/components/utils/FiltersDialog.d.ts +21 -0
  67. package/dist/components/utils/FiltersDialog.d.ts.map +1 -0
  68. package/dist/components/utils/FiltersDialog.js +104 -0
  69. package/dist/components/utils/Loader.js +2 -2
  70. package/dist/components/utils/RoadMap.d.ts +59 -0
  71. package/dist/components/utils/RoadMap.d.ts.map +1 -0
  72. package/dist/components/utils/RoadMap.js +139 -0
  73. package/dist/components/utils/Snackbar.d.ts +13 -0
  74. package/dist/components/utils/Snackbar.d.ts.map +1 -0
  75. package/dist/components/utils/Snackbar.js +122 -0
  76. package/dist/components/utils/SnackbarContainer.d.ts +7 -0
  77. package/dist/components/utils/SnackbarContainer.d.ts.map +1 -0
  78. package/dist/components/utils/SnackbarContainer.js +25 -0
  79. package/dist/components/utils/iconUtils.d.ts +16 -0
  80. package/dist/components/utils/iconUtils.d.ts.map +1 -0
  81. package/dist/components/utils/iconUtils.js +40 -0
  82. package/dist/components/utils/index.d.ts +12 -0
  83. package/dist/components/utils/index.d.ts.map +1 -1
  84. package/dist/components/utils/index.js +6 -0
  85. package/dist/contexts/AppLayoutContext.d.ts +40 -0
  86. package/dist/contexts/AppLayoutContext.d.ts.map +1 -0
  87. package/dist/contexts/AppLayoutContext.js +98 -0
  88. package/dist/contexts/ListCrudContext.d.ts +50 -0
  89. package/dist/contexts/ListCrudContext.d.ts.map +1 -0
  90. package/dist/contexts/ListCrudContext.js +253 -0
  91. package/dist/contexts/SnackbarContext.d.ts +26 -0
  92. package/dist/contexts/SnackbarContext.d.ts.map +1 -0
  93. package/dist/contexts/SnackbarContext.js +34 -0
  94. package/dist/contexts/index.d.ts +6 -0
  95. package/dist/contexts/index.d.ts.map +1 -1
  96. package/dist/contexts/index.js +6 -0
  97. package/dist/contexts/presets.js +6 -6
  98. package/dist/docs/AccordionDocs.d.ts +4 -0
  99. package/dist/docs/AccordionDocs.d.ts.map +1 -0
  100. package/dist/docs/AccordionDocs.js +21 -0
  101. package/dist/docs/AuthDocs.tsx/AuthDocsContent.js +3 -5
  102. package/dist/docs/AutocompleteInputDocs.js +1 -1
  103. package/dist/docs/AvatarDocs.d.ts +4 -0
  104. package/dist/docs/AvatarDocs.d.ts.map +1 -0
  105. package/dist/docs/AvatarDocs.js +7 -0
  106. package/dist/docs/BadgeDocs.d.ts.map +1 -1
  107. package/dist/docs/BadgeDocs.js +4 -2
  108. package/dist/docs/ButtonDocs.d.ts.map +1 -1
  109. package/dist/docs/ButtonDocs.js +1 -1
  110. package/dist/docs/CardDocs.d.ts.map +1 -1
  111. package/dist/docs/CardDocs.js +17 -8
  112. package/dist/docs/CheckboxDocs.d.ts +4 -0
  113. package/dist/docs/CheckboxDocs.d.ts.map +1 -0
  114. package/dist/docs/CheckboxDocs.js +7 -0
  115. package/dist/docs/DataTableDocs.d.ts.map +1 -1
  116. package/dist/docs/DataTableDocs.js +9 -5
  117. package/dist/docs/DateInputDocs.d.ts +1 -0
  118. package/dist/docs/DateInputDocs.d.ts.map +1 -1
  119. package/dist/docs/DateInputDocs.js +7 -9
  120. package/dist/docs/DatePickerDocs.d.ts +1 -0
  121. package/dist/docs/DatePickerDocs.d.ts.map +1 -1
  122. package/dist/docs/DatePickerDocs.js +6 -8
  123. package/dist/docs/DialogDocs.js +1 -1
  124. package/dist/docs/DocAdmin.d.ts +4 -0
  125. package/dist/docs/DocAdmin.d.ts.map +1 -0
  126. package/dist/docs/DocAdmin.js +68 -0
  127. package/dist/docs/DocsMenu.d.ts.map +1 -1
  128. package/dist/docs/DocsMenu.js +3 -3
  129. package/dist/docs/DocsRouter.d.ts.map +1 -1
  130. package/dist/docs/DocsRouter.js +13 -1
  131. package/dist/docs/DropdownMenuDocs.js +1 -1
  132. package/dist/docs/ExampleFormDocs.d.ts +4 -0
  133. package/dist/docs/ExampleFormDocs.d.ts.map +1 -0
  134. package/dist/docs/ExampleFormDocs.js +148 -0
  135. package/dist/docs/FilterDocs.d.ts +4 -0
  136. package/dist/docs/FilterDocs.d.ts.map +1 -0
  137. package/dist/docs/FilterDocs.js +112 -0
  138. package/dist/docs/InputDocs.d.ts.map +1 -1
  139. package/dist/docs/InputDocs.js +11 -1
  140. package/dist/docs/LinkButtonDocs.d.ts +4 -0
  141. package/dist/docs/LinkButtonDocs.d.ts.map +1 -0
  142. package/dist/docs/LinkButtonDocs.js +7 -0
  143. package/dist/docs/ListCrudDocs.tsx/ListCrudDocs.d.ts +2 -0
  144. package/dist/docs/ListCrudDocs.tsx/ListCrudDocs.d.ts.map +1 -0
  145. package/dist/docs/ListCrudDocs.tsx/ListCrudDocs.js +29 -0
  146. package/dist/docs/ListCrudDocs.tsx/ListCrudDocsContentEmpresas.d.ts +2 -0
  147. package/dist/docs/ListCrudDocs.tsx/ListCrudDocsContentEmpresas.d.ts.map +1 -0
  148. package/dist/docs/ListCrudDocs.tsx/ListCrudDocsContentEmpresas.js +7 -0
  149. package/dist/docs/ListCrudDocs.tsx/ListCrudDocsContentPersonas.d.ts +2 -0
  150. package/dist/docs/ListCrudDocs.tsx/ListCrudDocsContentPersonas.d.ts.map +1 -0
  151. package/dist/docs/ListCrudDocs.tsx/ListCrudDocsContentPersonas.js +57 -0
  152. package/dist/docs/ListCrudDocs.tsx/ListCrudDocsEditDialog.d.ts +9 -0
  153. package/dist/docs/ListCrudDocs.tsx/ListCrudDocsEditDialog.d.ts.map +1 -0
  154. package/dist/docs/ListCrudDocs.tsx/ListCrudDocsEditDialog.js +30 -0
  155. package/dist/docs/PaginationDocs.js +6 -6
  156. package/dist/docs/RadioButtonGroupDocs.d.ts +4 -0
  157. package/dist/docs/RadioButtonGroupDocs.d.ts.map +1 -0
  158. package/dist/docs/RadioButtonGroupDocs.js +46 -0
  159. package/dist/docs/RoadMapDocs.d.ts +4 -0
  160. package/dist/docs/RoadMapDocs.d.ts.map +1 -0
  161. package/dist/docs/RoadMapDocs.js +171 -0
  162. package/dist/docs/SearchSelectInputDocs.d.ts +4 -0
  163. package/dist/docs/SearchSelectInputDocs.d.ts.map +1 -0
  164. package/dist/docs/SearchSelectInputDocs.js +168 -0
  165. package/dist/docs/SnackbarDocs.d.ts +4 -0
  166. package/dist/docs/SnackbarDocs.d.ts.map +1 -0
  167. package/dist/docs/SnackbarDocs.js +50 -0
  168. package/dist/docs/TabsGroupDocs.d.ts.map +1 -1
  169. package/dist/docs/TabsGroupDocs.js +12 -1
  170. package/dist/docs/docMockServices/empresaService.d.ts +38 -0
  171. package/dist/docs/docMockServices/empresaService.d.ts.map +1 -0
  172. package/dist/docs/docMockServices/empresaService.js +117 -0
  173. package/dist/docs/docMockServices/index.d.ts +9 -0
  174. package/dist/docs/docMockServices/index.d.ts.map +1 -0
  175. package/dist/docs/docMockServices/index.js +8 -0
  176. package/dist/docs/docMockServices/initialData.d.ts +6 -0
  177. package/dist/docs/docMockServices/initialData.d.ts.map +1 -0
  178. package/dist/docs/docMockServices/initialData.js +132 -0
  179. package/dist/docs/docMockServices/interfaces.d.ts +26 -0
  180. package/dist/docs/docMockServices/interfaces.d.ts.map +1 -0
  181. package/dist/docs/docMockServices/interfaces.js +1 -0
  182. package/dist/docs/docMockServices/personaEmpresaService.d.ts +43 -0
  183. package/dist/docs/docMockServices/personaEmpresaService.d.ts.map +1 -0
  184. package/dist/docs/docMockServices/personaEmpresaService.js +113 -0
  185. package/dist/docs/docMockServices/personaService.d.ts +39 -0
  186. package/dist/docs/docMockServices/personaService.d.ts.map +1 -0
  187. package/dist/docs/docMockServices/personaService.js +181 -0
  188. package/dist/hooks/index.d.ts +2 -0
  189. package/dist/hooks/index.d.ts.map +1 -1
  190. package/dist/hooks/index.js +1 -0
  191. package/dist/hooks/useAsyncRequest.d.ts +17 -0
  192. package/dist/hooks/useAsyncRequest.d.ts.map +1 -0
  193. package/dist/hooks/useAsyncRequest.js +70 -0
  194. package/dist/index.css +1 -1
  195. package/dist/index.d.ts +23 -1
  196. package/dist/index.d.ts.map +1 -1
  197. package/dist/index.js +11 -0
  198. package/dist/index.js.map +1 -1
  199. package/dist/templates/forms/ContactForm.js +2 -2
  200. package/dist/templates/forms/LoginForm.js +1 -1
  201. package/dist/templates/forms/RegistrationForm.js +1 -1
  202. package/dist/templates/layouts/SidebarLayout.d.ts.map +1 -1
  203. package/dist/templates/layouts/SidebarLayout.js +3 -2
  204. package/dist/templates/patterns/FormPattern.d.ts.map +1 -1
  205. package/dist/templates/patterns/FormPattern.js +4 -3
  206. package/package.json +5 -2
@@ -0,0 +1,963 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import React from "react";
3
+ import { useFormContext } from "react-hook-form";
4
+ import { Input } from "./Input";
5
+ import { Button } from "./Button";
6
+ import { Dialog } from "../utils/Dialog";
7
+ import { normalizeIconClass } from "../utils/iconUtils";
8
+ function SearchSelectInputInner({ value, onChange, onSearchPromiseFn, onSingleSearchPromiseFn, onSelectOption, dialogTitle = "Seleccione una opción", searchButtonPosition = "right", noResultsText = "Sin resultados", getOptionLabel, getOptionValue, getOptionDescription, renderOption, className = "", size = "md", ...inputProps }, ref) {
9
+ // Extraer onBlur de inputProps para manejarlo por separado
10
+ const { onBlur: registerOnBlur, ...restInputProps } = inputProps;
11
+ // Detectar si estamos en modo register: si viene 'name' de register, estamos en modo register
12
+ // register siempre pasa 'name', 'onChange', 'onBlur', y 'ref'
13
+ const isRegisterMode = React.useMemo(() => {
14
+ // Si viene 'name' en inputProps, es porque viene de register
15
+ return "name" in inputProps && inputProps.name !== undefined;
16
+ }, [inputProps]);
17
+ const fieldName = isRegisterMode && "name" in restInputProps
18
+ ? restInputProps.name
19
+ : undefined;
20
+ // Obtener setValue del contexto del formulario
21
+ // Para usar objetos complejos con register, el formulario debe estar dentro de FormProvider
22
+ // useFormContext debe llamarse incondicionalmente (requisito de React Hooks)
23
+ // Si no hay FormProvider y se usa en modo register, useFormContext lanzará un error
24
+ // Para usar sin FormProvider, usar Controller en lugar de register
25
+ const formContext = useFormContext();
26
+ const setValue = formContext?.setValue;
27
+ // Estado interno para el texto mostrado en el input (siempre string)
28
+ const [internalDisplayValue, setInternalDisplayValue] = React.useState("");
29
+ const [displayValue, setDisplayValue] = React.useState("");
30
+ // Sincronizar el ref con el estado
31
+ React.useEffect(() => {
32
+ displayValueRef.current = displayValue;
33
+ }, [displayValue]);
34
+ const [isDialogOpen, setIsDialogOpen] = React.useState(false);
35
+ const [options, setOptions] = React.useState([]);
36
+ // Sincronizar el ref con el estado de options
37
+ React.useEffect(() => {
38
+ optionsRef.current = options;
39
+ }, [options]);
40
+ const [isLoading, setIsLoading] = React.useState(false);
41
+ const [dialogSearchText, setDialogSearchText] = React.useState("");
42
+ const [hasSearched, setHasSearched] = React.useState(false);
43
+ const inputRef = React.useRef(null);
44
+ // Guardar la última opción seleccionada para poder mostrar su label después
45
+ const [lastSelectedOption, setLastSelectedOption] = React.useState(null);
46
+ // Ref para evitar múltiples búsquedas simultáneas del mismo valor
47
+ const searchingValueRef = React.useRef(null);
48
+ // Ref para evitar sincronización cuando acabamos de seleccionar una opción
49
+ const isSelectingRef = React.useRef(false);
50
+ // Ref para leer el displayValue actual sin incluirlo en dependencias
51
+ const displayValueRef = React.useRef("");
52
+ // Ref para rastrear el último valor del formulario sincronizado
53
+ const lastSyncedFormValueRef = React.useRef(undefined);
54
+ // Ref para mantener una referencia estable a syncDisplayValue
55
+ const syncDisplayValueRef = React.useRef(undefined);
56
+ // Ref para leer options sin incluirlo en dependencias
57
+ const optionsRef = React.useRef([]);
58
+ // Ref para rastrear el último value procesado exitosamente en modo Controller
59
+ const lastProcessedValueRef = React.useRef(undefined);
60
+ const labelGetter = React.useCallback((item) => {
61
+ if (getOptionLabel)
62
+ return getOptionLabel(item);
63
+ const anyItem = item;
64
+ return (anyItem.label ?? "").toString();
65
+ }, [getOptionLabel]);
66
+ const valueGetter = React.useCallback((item) => {
67
+ if (getOptionValue)
68
+ return getOptionValue(item);
69
+ const anyItem = item;
70
+ return anyItem.value ?? undefined;
71
+ }, [getOptionValue]);
72
+ const descriptionGetter = React.useCallback((item) => {
73
+ if (getOptionDescription)
74
+ return getOptionDescription(item);
75
+ const anyItem = item;
76
+ return anyItem.description;
77
+ }, [getOptionDescription]);
78
+ // Función helper para sincronizar displayValue con el valor del formulario en modo register
79
+ const syncDisplayValue = React.useCallback(() => {
80
+ // Evitar sincronización si acabamos de seleccionar una opción
81
+ if (isSelectingRef.current) {
82
+ return false;
83
+ }
84
+ if (isRegisterMode) {
85
+ // Si tenemos setValue, el valor del formulario es un objeto complejo (T | K)
86
+ if (fieldName && formContext) {
87
+ const formValue = formContext.watch(fieldName);
88
+ // Evitar sincronización si el valor no ha cambiado
89
+ if (lastSyncedFormValueRef.current === formValue) {
90
+ return true;
91
+ }
92
+ if (formValue !== undefined && formValue !== null && formValue !== "") {
93
+ // Actualizar el ref del último valor sincronizado
94
+ lastSyncedFormValueRef.current = formValue;
95
+ // Primero verificar si la última opción seleccionada coincide
96
+ if (lastSelectedOption) {
97
+ const lastValue = valueGetter(lastSelectedOption);
98
+ if (lastValue === formValue ||
99
+ (typeof formValue === "object" &&
100
+ formValue === lastSelectedOption)) {
101
+ setDisplayValue(labelGetter(lastSelectedOption));
102
+ return true;
103
+ }
104
+ }
105
+ // Buscar en las opciones ya cargadas (usar ref para evitar dependencias)
106
+ const matchingOption = optionsRef.current.find((opt) => valueGetter(opt) === formValue);
107
+ if (matchingOption) {
108
+ setDisplayValue(labelGetter(matchingOption));
109
+ setLastSelectedOption(matchingOption);
110
+ return true;
111
+ }
112
+ // Si no encontramos, intentar usar getOptionLabel si formValue es un objeto
113
+ if (getOptionLabel &&
114
+ typeof formValue === "object" &&
115
+ formValue !== null) {
116
+ try {
117
+ const label = getOptionLabel(formValue);
118
+ if (label) {
119
+ setDisplayValue(label);
120
+ return true;
121
+ }
122
+ }
123
+ catch {
124
+ // Si falla, continuar
125
+ }
126
+ }
127
+ // Si no encontramos nada y tenemos onSingleSearchPromiseFn, intentar buscar el elemento individual
128
+ if (onSingleSearchPromiseFn) {
129
+ // Si ya estamos buscando este valor, preservar el displayValue actual
130
+ if (searchingValueRef.current === formValue) {
131
+ // Ya estamos buscando este valor, preservar el displayValue actual
132
+ return (displayValueRef.current.trim() !== "" ||
133
+ lastSelectedOption !== null);
134
+ }
135
+ // Si ya tenemos lastSelectedOption con este valor, preservar el displayValue
136
+ // Esto evita buscar de nuevo cuando el valor ya se cargó previamente
137
+ if (lastSelectedOption) {
138
+ const lastValue = valueGetter(lastSelectedOption);
139
+ if (lastValue === formValue) {
140
+ // Ya tenemos la opción correcta, asegurarnos de que esté en options y preservar displayValue
141
+ if (!optionsRef.current.find((opt) => valueGetter(opt) === formValue)) {
142
+ setOptions((prev) => [...prev, lastSelectedOption]);
143
+ }
144
+ // El displayValue ya debería estar establecido, pero asegurémonos
145
+ setDisplayValue(labelGetter(lastSelectedOption));
146
+ return true;
147
+ }
148
+ }
149
+ // Iniciar búsqueda solo si no hemos encontrado la opción todavía
150
+ searchingValueRef.current = formValue;
151
+ onSingleSearchPromiseFn(formValue)
152
+ .then((foundOption) => {
153
+ // Verificar que el valor sigue siendo el mismo (por si cambió mientras buscábamos)
154
+ if (fieldName && formContext) {
155
+ const currentFormValue = formContext.watch(fieldName);
156
+ if (currentFormValue === formValue) {
157
+ if (foundOption) {
158
+ // Si se encontró la opción, actualizar el displayValue
159
+ const label = labelGetter(foundOption);
160
+ setDisplayValue(label);
161
+ setLastSelectedOption(foundOption);
162
+ // Agregar la opción a las opciones disponibles si no está ya
163
+ setOptions((prev) => {
164
+ if (!prev.find((opt) => valueGetter(opt) === valueGetter(foundOption))) {
165
+ return [...prev, foundOption];
166
+ }
167
+ return prev;
168
+ });
169
+ }
170
+ else {
171
+ // Si no se encontró (undefined), dejar displayValue vacío solo si realmente no hay valor
172
+ // Pero como formValue existe, no deberíamos limpiarlo aquí
173
+ // En realidad, si formValue existe pero no se encontró, dejar vacío es correcto
174
+ setDisplayValue("");
175
+ setLastSelectedOption(null);
176
+ }
177
+ }
178
+ }
179
+ searchingValueRef.current = null;
180
+ })
181
+ .catch((error) => {
182
+ console.error("Error buscando elemento individual:", error);
183
+ searchingValueRef.current = null;
184
+ });
185
+ // Retornar true si ya tenemos un displayValue para preservarlo mientras buscamos
186
+ return (displayValueRef.current.trim() !== "" ||
187
+ lastSelectedOption !== null);
188
+ }
189
+ // Si no encontramos nada y no hay onSingleSearchPromiseFn, pero ya tenemos un displayValue
190
+ // (probablemente de una búsqueda anterior), preservarlo
191
+ if (displayValueRef.current.trim() !== "" ||
192
+ lastSelectedOption !== null) {
193
+ return true;
194
+ }
195
+ return false;
196
+ }
197
+ else {
198
+ // Actualizar el ref del último valor sincronizado
199
+ lastSyncedFormValueRef.current = formValue;
200
+ setDisplayValue("");
201
+ setLastSelectedOption(null);
202
+ return false;
203
+ }
204
+ }
205
+ else if (inputRef.current) {
206
+ // Fallback: leer del input nativo (valor string o serializado)
207
+ const formValue = inputRef.current.value;
208
+ if (formValue) {
209
+ // Buscar en las opciones para mostrar el label (usar ref para evitar dependencias)
210
+ const matchingOption = optionsRef.current.find((opt) => String(valueGetter(opt)) === String(formValue));
211
+ if (matchingOption) {
212
+ setDisplayValue(labelGetter(matchingOption));
213
+ }
214
+ else {
215
+ setDisplayValue(formValue);
216
+ }
217
+ return true;
218
+ }
219
+ else {
220
+ setDisplayValue("");
221
+ setLastSelectedOption(null);
222
+ return false;
223
+ }
224
+ }
225
+ }
226
+ return false;
227
+ }, [
228
+ isRegisterMode,
229
+ fieldName,
230
+ formContext,
231
+ // Removido options - usar optionsRef.current en su lugar
232
+ labelGetter,
233
+ valueGetter,
234
+ getOptionLabel,
235
+ lastSelectedOption,
236
+ onSingleSearchPromiseFn,
237
+ ]);
238
+ // Mantener el ref actualizado con la función syncDisplayValue
239
+ React.useEffect(() => {
240
+ syncDisplayValueRef.current = syncDisplayValue;
241
+ }, [syncDisplayValue]);
242
+ // Sincronizar displayValue cuando value cambia externamente (modo Controller)
243
+ React.useEffect(() => {
244
+ if (!isRegisterMode) {
245
+ if (value !== undefined && value !== null && value !== "") {
246
+ let displayValueSet = false;
247
+ // Si value es un string, siempre buscar el label correspondiente
248
+ if (typeof value === "string") {
249
+ // Función helper para obtener el label de una opción
250
+ const getLabelFromOption = (option) => {
251
+ if (getOptionLabel) {
252
+ return getOptionLabel(option);
253
+ }
254
+ // Si no hay getOptionLabel, asumir que la opción tiene una propiedad "label"
255
+ const anyOption = option;
256
+ return anyOption?.label ?? String(value);
257
+ };
258
+ // Primero verificar si lastSelectedOption coincide con este valor
259
+ if (lastSelectedOption) {
260
+ const lastValue = valueGetter(lastSelectedOption);
261
+ if (String(lastValue) === value) {
262
+ const label = getLabelFromOption(lastSelectedOption);
263
+ setInternalDisplayValue(label);
264
+ lastProcessedValueRef.current = value;
265
+ displayValueSet = true;
266
+ }
267
+ }
268
+ // Si no encontramos en lastSelectedOption, buscar en las opciones ya cargadas
269
+ if (!displayValueSet) {
270
+ const matchingOption = optionsRef.current.find((opt) => String(valueGetter(opt)) === value);
271
+ if (matchingOption) {
272
+ const label = getLabelFromOption(matchingOption);
273
+ setInternalDisplayValue(label);
274
+ setLastSelectedOption(matchingOption);
275
+ lastProcessedValueRef.current = value;
276
+ displayValueSet = true;
277
+ }
278
+ }
279
+ // Si aún no encontramos y tenemos onSingleSearchPromiseFn, buscar el elemento individual
280
+ if (!displayValueSet && onSingleSearchPromiseFn) {
281
+ // Solo buscar si no estamos buscando ya este valor
282
+ if (searchingValueRef.current !== value) {
283
+ const currentValue = value; // Capturar el valor actual
284
+ searchingValueRef.current = value;
285
+ onSingleSearchPromiseFn(value)
286
+ .then((foundOption) => {
287
+ // Verificar que el valor sigue siendo el mismo
288
+ if (currentValue === value) {
289
+ if (foundOption) {
290
+ const label = getLabelFromOption(foundOption);
291
+ setInternalDisplayValue(label);
292
+ setLastSelectedOption(foundOption);
293
+ lastProcessedValueRef.current = value;
294
+ // Agregar la opción a las opciones disponibles si no está ya
295
+ setOptions((prev) => {
296
+ if (!prev.find((opt) => valueGetter(opt) === valueGetter(foundOption))) {
297
+ return [...prev, foundOption];
298
+ }
299
+ return prev;
300
+ });
301
+ }
302
+ else {
303
+ // Si no se encontró, mostrar el string directamente
304
+ setInternalDisplayValue(currentValue);
305
+ lastProcessedValueRef.current = value;
306
+ }
307
+ }
308
+ searchingValueRef.current = null;
309
+ })
310
+ .catch((error) => {
311
+ console.error("Error buscando elemento individual:", error);
312
+ searchingValueRef.current = null;
313
+ // En caso de error, mostrar el string directamente
314
+ setInternalDisplayValue(value);
315
+ lastProcessedValueRef.current = value;
316
+ });
317
+ // Mientras buscamos, no establecer displayValueSet para que no se sobrescriba
318
+ // El displayValue se actualizará cuando la promesa se resuelva
319
+ }
320
+ else {
321
+ // Ya estamos buscando este valor, mantener el displayValue actual
322
+ displayValueSet = true;
323
+ }
324
+ }
325
+ else if (!displayValueSet) {
326
+ // Si no hay onSingleSearchPromiseFn y no encontramos nada, mostrar el string directamente
327
+ setInternalDisplayValue(value);
328
+ lastProcessedValueRef.current = value;
329
+ displayValueSet = true;
330
+ }
331
+ }
332
+ else {
333
+ // Si value es T | K, obtener el label usando getOptionLabel o la propiedad label
334
+ // Si hay getOptionValue, el value puede ser K (no tiene label), así que necesitamos
335
+ // buscar el objeto original en las opciones recientes o usar getOptionLabel si está disponible
336
+ if (getOptionLabel) {
337
+ // Si hay getOptionLabel, intentar usarlo (puede que value sea K, no T)
338
+ // En ese caso, necesitamos buscar el objeto T correspondiente
339
+ // Por ahora, intentamos usar getOptionLabel directamente
340
+ try {
341
+ setInternalDisplayValue(getOptionLabel(value));
342
+ lastProcessedValueRef.current = value;
343
+ displayValueSet = true;
344
+ }
345
+ catch {
346
+ // Si falla, value es probablemente K, buscar en las opciones (usar ref para evitar dependencias)
347
+ const matchingOption = optionsRef.current.find((opt) => valueGetter(opt) === value);
348
+ if (matchingOption) {
349
+ setInternalDisplayValue(labelGetter(matchingOption));
350
+ setLastSelectedOption(matchingOption);
351
+ lastProcessedValueRef.current = value;
352
+ displayValueSet = true;
353
+ }
354
+ }
355
+ }
356
+ else {
357
+ // Intentar obtener la propiedad label directamente
358
+ const anyValue = value;
359
+ if (anyValue?.label) {
360
+ setInternalDisplayValue(anyValue.label);
361
+ lastProcessedValueRef.current = value;
362
+ displayValueSet = true;
363
+ }
364
+ else {
365
+ // Si no tiene label, puede ser K, buscar en las opciones (usar ref para evitar dependencias)
366
+ const matchingOption = optionsRef.current.find((opt) => valueGetter(opt) === value);
367
+ if (matchingOption) {
368
+ setInternalDisplayValue(labelGetter(matchingOption));
369
+ setLastSelectedOption(matchingOption);
370
+ lastProcessedValueRef.current = value;
371
+ displayValueSet = true;
372
+ }
373
+ }
374
+ }
375
+ // Si no se encontró una opción coincidente, mostrar el valor como string
376
+ // (útil cuando hay un valor por defecto pero las opciones aún no se han cargado)
377
+ if (!displayValueSet) {
378
+ setInternalDisplayValue(String(value));
379
+ lastProcessedValueRef.current = value;
380
+ }
381
+ }
382
+ }
383
+ else {
384
+ // Resetear el estado interno cuando value es undefined (por ejemplo, después de un reset)
385
+ setInternalDisplayValue("");
386
+ lastProcessedValueRef.current = undefined;
387
+ }
388
+ }
389
+ }, [
390
+ value,
391
+ getOptionLabel,
392
+ // Removido options - usar optionsRef.current en su lugar para evitar bucles infinitos
393
+ labelGetter,
394
+ valueGetter,
395
+ isRegisterMode,
396
+ onSingleSearchPromiseFn,
397
+ lastSelectedOption,
398
+ ]);
399
+ // Ref para rastrear el último value procesado cuando options cambia
400
+ const lastValueProcessedRef = React.useRef(undefined);
401
+ const lastOptionsLengthRef = React.useRef(0);
402
+ // Actualizar internalDisplayValue cuando options cambia y hay un value que coincide
403
+ // Esto es necesario para cuando las opciones se cargan después de que se establece el value
404
+ React.useEffect(() => {
405
+ if (!isRegisterMode &&
406
+ value !== undefined &&
407
+ value !== null &&
408
+ value !== "") {
409
+ if (typeof value !== "string") {
410
+ // Solo procesar si el value cambió o si las options cambiaron (aumentaron)
411
+ const valueChanged = lastValueProcessedRef.current !== value;
412
+ const optionsChanged = options.length > lastOptionsLengthRef.current;
413
+ // Solo buscar si el value cambió o si las options aumentaron
414
+ if (valueChanged || optionsChanged) {
415
+ const matchingOption = options.find((opt) => valueGetter(opt) === value);
416
+ if (matchingOption) {
417
+ const newDisplay = labelGetter(matchingOption);
418
+ // Verificar que el displayValue actual no sea el mismo para evitar actualizaciones innecesarias
419
+ setInternalDisplayValue((prev) => {
420
+ if (prev !== newDisplay) {
421
+ lastValueProcessedRef.current = value;
422
+ lastOptionsLengthRef.current = options.length;
423
+ return newDisplay;
424
+ }
425
+ return prev;
426
+ });
427
+ }
428
+ else {
429
+ // Actualizar el ref del length incluso si no encontramos coincidencia
430
+ lastOptionsLengthRef.current = options.length;
431
+ }
432
+ }
433
+ }
434
+ }
435
+ }, [options, value, isRegisterMode, valueGetter, labelGetter]);
436
+ // Sincronizar displayValue con el valor del formulario en modo register
437
+ React.useEffect(() => {
438
+ if (isRegisterMode) {
439
+ let attempts = 0;
440
+ const maxAttempts = 50;
441
+ const trySync = () => {
442
+ // Usar el ref en lugar de la función directamente para evitar dependencias
443
+ return syncDisplayValueRef.current?.() ?? false;
444
+ };
445
+ if (trySync()) {
446
+ return;
447
+ }
448
+ const intervalId = window.setInterval(() => {
449
+ attempts++;
450
+ if (trySync() || attempts >= maxAttempts) {
451
+ clearInterval(intervalId);
452
+ }
453
+ }, 100);
454
+ const timeouts = [];
455
+ [0, 50, 100, 200, 500, 1000].forEach((delay) => {
456
+ const timeoutId = window.setTimeout(() => {
457
+ trySync();
458
+ }, delay);
459
+ timeouts.push(timeoutId);
460
+ });
461
+ return () => {
462
+ clearInterval(intervalId);
463
+ timeouts.forEach(clearTimeout);
464
+ };
465
+ }
466
+ }, [isRegisterMode]); // Removido syncDisplayValue de las dependencias
467
+ // También sincronizar cuando cambia el valor del formulario
468
+ React.useEffect(() => {
469
+ if (isRegisterMode && fieldName && formContext) {
470
+ const subscription = formContext.watch((_data, { name }) => {
471
+ // Solo sincronizar cuando cambia el campo específico, no en cada cambio del formulario
472
+ if (name === fieldName) {
473
+ // Cuando cambia el valor del formulario, sincronizar el displayValue
474
+ // Usar el ref en lugar de la función directamente para evitar dependencias
475
+ syncDisplayValueRef.current?.();
476
+ }
477
+ });
478
+ return () => subscription.unsubscribe();
479
+ }
480
+ }, [isRegisterMode, fieldName, formContext]); // Removido syncDisplayValue de las dependencias
481
+ // También escuchar cambios en el input nativo para sincronizar cuando cambie
482
+ React.useEffect(() => {
483
+ if (isRegisterMode && inputRef.current) {
484
+ const input = inputRef.current;
485
+ const handleInputSync = () => {
486
+ // Usar el ref en lugar de la función directamente para evitar dependencias
487
+ syncDisplayValueRef.current?.();
488
+ };
489
+ input.addEventListener("input", handleInputSync);
490
+ input.addEventListener("change", handleInputSync);
491
+ const observer = new MutationObserver(() => {
492
+ // Usar el ref en lugar de la función directamente para evitar dependencias
493
+ syncDisplayValueRef.current?.();
494
+ });
495
+ observer.observe(input, {
496
+ attributes: true,
497
+ attributeFilter: ["value"],
498
+ });
499
+ return () => {
500
+ input.removeEventListener("input", handleInputSync);
501
+ input.removeEventListener("change", handleInputSync);
502
+ observer.disconnect();
503
+ };
504
+ }
505
+ }, [isRegisterMode]); // Removido syncDisplayValue de las dependencias
506
+ const handleDialogSearch = React.useCallback(async () => {
507
+ const textToSearch = dialogSearchText.trim();
508
+ if (!textToSearch)
509
+ return;
510
+ setIsLoading(true);
511
+ setHasSearched(true);
512
+ try {
513
+ const result = await onSearchPromiseFn(textToSearch);
514
+ // Si es PaginationInterface, extraer el array de list
515
+ if (result && typeof result === "object" && "list" in result) {
516
+ setOptions(result.list);
517
+ }
518
+ else {
519
+ setOptions(result);
520
+ }
521
+ }
522
+ catch (error) {
523
+ console.error("Error en búsqueda:", error);
524
+ setOptions([]);
525
+ }
526
+ finally {
527
+ setIsLoading(false);
528
+ }
529
+ }, [dialogSearchText, onSearchPromiseFn]);
530
+ const handleInputChange = (event) => {
531
+ // El input principal ahora es readonly, así que esto no debería llamarse
532
+ // Pero lo mantenemos por compatibilidad
533
+ const newValue = event.target.value;
534
+ if (isRegisterMode) {
535
+ setDisplayValue(newValue);
536
+ }
537
+ else {
538
+ if (value === undefined || typeof value === "string") {
539
+ setInternalDisplayValue(newValue);
540
+ }
541
+ }
542
+ if (onChange) {
543
+ const standardHandler = onChange;
544
+ standardHandler(event);
545
+ }
546
+ };
547
+ // Handler para cuando se hace foco en el input principal
548
+ // Abre el dialog automáticamente
549
+ const handleInputFocus = () => {
550
+ // Abrir el dialog cuando se hace foco
551
+ setIsDialogOpen(true);
552
+ setDialogSearchText(inputValue.trim());
553
+ };
554
+ // Función para hacer búsqueda desde un valor dado
555
+ const handleDialogSearchFromValue = React.useCallback(async (textToSearch) => {
556
+ if (!textToSearch.trim())
557
+ return;
558
+ setIsLoading(true);
559
+ setHasSearched(true);
560
+ try {
561
+ const result = await onSearchPromiseFn(textToSearch.trim());
562
+ if (result && typeof result === "object" && "list" in result) {
563
+ setOptions(result.list);
564
+ }
565
+ else {
566
+ setOptions(result);
567
+ }
568
+ }
569
+ catch (error) {
570
+ console.error("Error en búsqueda:", error);
571
+ setOptions([]);
572
+ }
573
+ finally {
574
+ setIsLoading(false);
575
+ }
576
+ }, [onSearchPromiseFn]);
577
+ const handleKeyDown = (event) => {
578
+ // Cuando se presiona Enter, abrir el dialog
579
+ if (event.key === "Enter") {
580
+ event.preventDefault();
581
+ setIsDialogOpen(true);
582
+ setDialogSearchText(inputValue.trim());
583
+ // Si hay texto en el input, hacer búsqueda automática
584
+ if (inputValue.trim()) {
585
+ handleDialogSearchFromValue(inputValue.trim());
586
+ }
587
+ }
588
+ };
589
+ const handleSelect = (option) => {
590
+ const label = labelGetter(option);
591
+ // Marcar que estamos seleccionando para evitar sincronización
592
+ isSelectingRef.current = true;
593
+ // Resetear el ref del último valor sincronizado para forzar la próxima sincronización
594
+ lastSyncedFormValueRef.current = undefined;
595
+ // Guardar la opción seleccionada para poder mostrar su label después
596
+ setLastSelectedOption(option);
597
+ // Agregar la opción a las opciones disponibles si no está ya
598
+ if (!options.find((opt) => valueGetter(opt) === valueGetter(option))) {
599
+ setOptions((prev) => [...prev, option]);
600
+ }
601
+ // Determinar el valor a asignar: opción completa (T) o valor extraído (K)
602
+ const valueToAssign = getOptionValue ? valueGetter(option) : option;
603
+ if (isRegisterMode) {
604
+ // Actualizar el displayValue inmediatamente con el label
605
+ setDisplayValue(label);
606
+ // En modo register
607
+ if (fieldName && setValue) {
608
+ // Usar setValue para guardar el objeto completo directamente
609
+ setValue(fieldName, valueToAssign, {
610
+ shouldValidate: true,
611
+ shouldDirty: true,
612
+ });
613
+ }
614
+ else {
615
+ // Fallback: actualizar el input nativo con el valor serializado
616
+ if (inputRef.current) {
617
+ const nativeInput = inputRef.current;
618
+ const valueString = typeof valueToAssign === "object" && valueToAssign !== null
619
+ ? JSON.stringify(valueToAssign)
620
+ : String(valueToAssign);
621
+ const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value")?.set;
622
+ if (nativeInputValueSetter) {
623
+ nativeInputValueSetter.call(nativeInput, valueString);
624
+ }
625
+ else {
626
+ nativeInput.value = valueString;
627
+ }
628
+ if (onChange) {
629
+ const changeEvent = {
630
+ target: nativeInput,
631
+ currentTarget: nativeInput,
632
+ };
633
+ onChange(changeEvent);
634
+ }
635
+ const inputEvent = new Event("input", {
636
+ bubbles: true,
637
+ cancelable: true,
638
+ });
639
+ nativeInput.dispatchEvent(inputEvent);
640
+ const changeEventNative = new Event("change", {
641
+ bubbles: true,
642
+ cancelable: true,
643
+ });
644
+ nativeInput.dispatchEvent(changeEventNative);
645
+ }
646
+ }
647
+ }
648
+ else {
649
+ // Modo Controller o API personalizada
650
+ // Establecer internalDisplayValue inmediatamente para respuesta visual rápida
651
+ setInternalDisplayValue(label);
652
+ // Resetear lastProcessedValueRef para que el useEffect valide cuando el value cambie
653
+ lastProcessedValueRef.current = undefined;
654
+ if (onChange) {
655
+ // Intentar primero como onChange personalizado (acepta T | K directamente)
656
+ const customHandler = onChange;
657
+ if (typeof customHandler === "function") {
658
+ customHandler(valueToAssign);
659
+ }
660
+ else {
661
+ // Si no es función personalizada, es ChangeEventHandler
662
+ const serializedValue = typeof valueToAssign === "object" && valueToAssign !== null
663
+ ? JSON.stringify(valueToAssign)
664
+ : String(valueToAssign);
665
+ const syntheticEvent = {
666
+ target: {
667
+ value: serializedValue,
668
+ name: restInputProps.name || "",
669
+ },
670
+ currentTarget: {
671
+ value: serializedValue,
672
+ name: restInputProps.name || "",
673
+ },
674
+ type: "change",
675
+ bubbles: true,
676
+ cancelable: true,
677
+ defaultPrevented: false,
678
+ eventPhase: 0,
679
+ isTrusted: false,
680
+ nativeEvent: {},
681
+ preventDefault: () => { },
682
+ isDefaultPrevented: () => false,
683
+ stopPropagation: () => { },
684
+ isPropagationStopped: () => false,
685
+ persist: () => { },
686
+ timeStamp: Date.now(),
687
+ };
688
+ const standardHandler = onChange;
689
+ standardHandler(syntheticEvent);
690
+ }
691
+ }
692
+ }
693
+ onSelectOption?.(option, valueGetter(option));
694
+ setIsDialogOpen(false);
695
+ // Resetear el flag después de un breve delay para permitir que los efectos se ejecuten
696
+ setTimeout(() => {
697
+ isSelectingRef.current = false;
698
+ }, 100);
699
+ };
700
+ const handleIconClick = (event) => {
701
+ event.preventDefault();
702
+ // Si hay valor seleccionado, limpiarlo
703
+ if (hasSelectedValue) {
704
+ handleClear(event);
705
+ }
706
+ else {
707
+ // Si no hay valor, abrir el dialog (similar a handleInputFocus)
708
+ setIsDialogOpen(true);
709
+ setDialogSearchText(inputValue.trim());
710
+ // Si hay texto en el input, hacer búsqueda automática
711
+ if (inputValue.trim()) {
712
+ handleDialogSearchFromValue(inputValue.trim());
713
+ }
714
+ }
715
+ };
716
+ // Detectar si hay un valor seleccionado
717
+ const hasSelectedValue = React.useMemo(() => {
718
+ const currentDisplayValue = isRegisterMode
719
+ ? displayValue
720
+ : internalDisplayValue;
721
+ const displayValueStr = isRegisterMode
722
+ ? displayValue ?? ""
723
+ : internalDisplayValue ?? "";
724
+ return ((isRegisterMode
725
+ ? displayValueStr.trim() !== ""
726
+ : value !== undefined && value !== null && value !== "") &&
727
+ typeof currentDisplayValue === "string" &&
728
+ currentDisplayValue.trim() !== "");
729
+ }, [value, internalDisplayValue, displayValue, isRegisterMode]);
730
+ // Función para limpiar el valor
731
+ const handleClear = React.useCallback((event) => {
732
+ event.preventDefault();
733
+ event.stopPropagation();
734
+ if (isRegisterMode) {
735
+ setDisplayValue("");
736
+ setLastSelectedOption(null);
737
+ if (fieldName && setValue) {
738
+ // Usar setValue para limpiar el valor
739
+ setValue(fieldName, undefined, {
740
+ shouldValidate: true,
741
+ shouldDirty: true,
742
+ });
743
+ }
744
+ else {
745
+ // Fallback: limpiar el input nativo
746
+ if (inputRef.current) {
747
+ const nativeInput = inputRef.current;
748
+ const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value")?.set;
749
+ setter?.call(nativeInput, "");
750
+ const inputEvent = new Event("input", { bubbles: true });
751
+ nativeInput.dispatchEvent(inputEvent);
752
+ const changeEvent = new Event("change", { bubbles: true });
753
+ nativeInput.dispatchEvent(changeEvent);
754
+ }
755
+ if (onChange && inputRef.current) {
756
+ const changeEvent = {
757
+ target: inputRef.current,
758
+ currentTarget: inputRef.current,
759
+ };
760
+ onChange(changeEvent);
761
+ }
762
+ }
763
+ }
764
+ else {
765
+ setInternalDisplayValue("");
766
+ }
767
+ // Resetear el valor llamando a onChange
768
+ if (!isRegisterMode && onChange) {
769
+ const customHandler = onChange;
770
+ if (typeof customHandler === "function") {
771
+ // Si hay getOptionValue, pasar undefined, sino pasar undefined también
772
+ customHandler(undefined);
773
+ }
774
+ else {
775
+ // Si es ChangeEventHandler, crear un evento sintético
776
+ const syntheticEvent = {
777
+ target: {
778
+ value: "",
779
+ name: inputProps.name || "",
780
+ },
781
+ currentTarget: {
782
+ value: "",
783
+ name: inputProps.name || "",
784
+ },
785
+ type: "change",
786
+ bubbles: true,
787
+ cancelable: true,
788
+ defaultPrevented: false,
789
+ eventPhase: 0,
790
+ isTrusted: false,
791
+ nativeEvent: {},
792
+ preventDefault: () => { },
793
+ isDefaultPrevented: () => false,
794
+ stopPropagation: () => { },
795
+ isPropagationStopped: () => false,
796
+ persist: () => { },
797
+ timeStamp: Date.now(),
798
+ };
799
+ const standardHandler = onChange;
800
+ standardHandler(syntheticEvent);
801
+ }
802
+ }
803
+ }, [onChange, isRegisterMode, setValue, fieldName, inputProps.name]);
804
+ // Determinar qué ícono mostrar: si hay valor seleccionado, mostrar "X", sino mostrar el ícono de búsqueda
805
+ const displayIcon = hasSelectedValue
806
+ ? "fa-times"
807
+ : inputProps.icon || "fa-search";
808
+ const displayIconPosition = searchButtonPosition;
809
+ // handleIconClick ya maneja tanto limpiar como abrir el dialog
810
+ const displayOnIconClick = handleIconClick;
811
+ // Resetear el texto de búsqueda del dialog cuando se cierra
812
+ // Y buscar la opción individual si el valor del control no está en las opciones cargadas
813
+ React.useEffect(() => {
814
+ if (!isDialogOpen) {
815
+ setDialogSearchText("");
816
+ setHasSearched(false);
817
+ setOptions([]);
818
+ // Cuando se cierra el dialog, verificar si necesitamos buscar la opción individual
819
+ if (isRegisterMode &&
820
+ fieldName &&
821
+ onSingleSearchPromiseFn &&
822
+ formContext) {
823
+ const formValue = formContext.watch(fieldName);
824
+ if (formValue !== undefined && formValue !== null && formValue !== "") {
825
+ // Verificar si ya tenemos la opción en lastSelectedOption o en options
826
+ // Usar optionsRef.current para evitar dependencias y bucles infinitos
827
+ const hasOption = (lastSelectedOption &&
828
+ valueGetter(lastSelectedOption) === formValue) ||
829
+ optionsRef.current.some((opt) => valueGetter(opt) === formValue);
830
+ // Si no tenemos la opción y no estamos buscando ya, buscarla
831
+ if (!hasOption && searchingValueRef.current !== formValue) {
832
+ searchingValueRef.current = formValue;
833
+ onSingleSearchPromiseFn(formValue)
834
+ .then((foundOption) => {
835
+ // Verificar que el valor sigue siendo el mismo
836
+ if (fieldName && formContext) {
837
+ const currentFormValue = formContext.watch(fieldName);
838
+ if (currentFormValue === formValue) {
839
+ if (foundOption) {
840
+ // Si se encontró la opción, actualizar el displayValue
841
+ const label = labelGetter(foundOption);
842
+ setDisplayValue(label);
843
+ setLastSelectedOption(foundOption);
844
+ // Agregar la opción a las opciones disponibles si no está ya
845
+ setOptions((prev) => {
846
+ if (!prev.find((opt) => valueGetter(opt) === valueGetter(foundOption))) {
847
+ return [...prev, foundOption];
848
+ }
849
+ return prev;
850
+ });
851
+ }
852
+ }
853
+ }
854
+ searchingValueRef.current = null;
855
+ })
856
+ .catch((error) => {
857
+ console.error("Error buscando elemento individual al cerrar dialog:", error);
858
+ searchingValueRef.current = null;
859
+ });
860
+ }
861
+ }
862
+ }
863
+ }
864
+ }, [
865
+ isDialogOpen,
866
+ isRegisterMode,
867
+ setValue,
868
+ fieldName,
869
+ formContext,
870
+ onSingleSearchPromiseFn,
871
+ lastSelectedOption,
872
+ // Removido options - usar optionsRef.current en su lugar para evitar bucles infinitos
873
+ valueGetter,
874
+ labelGetter,
875
+ ]);
876
+ // Combinar refs: el ref del componente y el ref interno
877
+ const combinedRef = React.useCallback((node) => {
878
+ inputRef.current = node;
879
+ if (typeof ref === "function") {
880
+ ref(node);
881
+ }
882
+ else if (ref) {
883
+ ref.current = node;
884
+ }
885
+ // Cuando el ref se establece en modo register, sincronizar el displayValue
886
+ if (isRegisterMode && node) {
887
+ [0, 10, 50, 100, 200, 500].forEach((delay) => {
888
+ setTimeout(() => {
889
+ if (node && inputRef.current === node) {
890
+ // Usar el ref en lugar de la función directamente para evitar dependencias
891
+ syncDisplayValueRef.current?.();
892
+ }
893
+ }, delay);
894
+ });
895
+ }
896
+ }, [ref, isRegisterMode] // Removido syncDisplayValue de las dependencias
897
+ );
898
+ // Valor que se muestra en el input principal - completamente desacoplado del dialog
899
+ // Solo muestra el label de la opción seleccionada
900
+ const inputValue = React.useMemo(() => {
901
+ if (isRegisterMode) {
902
+ // En modo register, usar displayValue (que contiene el label de la opción seleccionada)
903
+ // displayValue se sincroniza automáticamente con el valor del formulario
904
+ return displayValue ?? "";
905
+ }
906
+ else {
907
+ // Modo Controller o API personalizada
908
+ if (value !== undefined && value !== null && value !== "") {
909
+ // Si el valor es una opción completa (T), mostrar su label
910
+ if (getOptionLabel && typeof value === "object") {
911
+ try {
912
+ return getOptionLabel(value);
913
+ }
914
+ catch {
915
+ // Si falla, puede ser K, buscar en las opciones
916
+ const matchingOption = options.find((opt) => valueGetter(opt) === value);
917
+ if (matchingOption) {
918
+ return labelGetter(matchingOption);
919
+ }
920
+ }
921
+ }
922
+ // Si el valor es K (el valor extraído), buscar en las opciones
923
+ const matchingOption = options.find((opt) => valueGetter(opt) === value);
924
+ if (matchingOption) {
925
+ return labelGetter(matchingOption);
926
+ }
927
+ // Si no se encuentra, intentar mostrar el valor como string
928
+ return String(value);
929
+ }
930
+ return internalDisplayValue;
931
+ }
932
+ }, [
933
+ isRegisterMode,
934
+ displayValue,
935
+ value,
936
+ internalDisplayValue,
937
+ options,
938
+ labelGetter,
939
+ valueGetter,
940
+ getOptionLabel,
941
+ ]);
942
+ return (_jsxs(_Fragment, { children: [_jsx("div", { className: `relative w-full ${className}`, children: _jsx(Input, { ...restInputProps, ref: combinedRef, value: inputValue, onChange: handleInputChange, onFocus: (e) => {
943
+ // Llamar al onFocus original si existe
944
+ if (restInputProps.onFocus) {
945
+ restInputProps.onFocus(e);
946
+ }
947
+ handleInputFocus(e);
948
+ }, onBlur: registerOnBlur, onKeyDown: handleKeyDown, size: size, icon: displayIcon, iconPosition: displayIconPosition, onIconClick: displayOnIconClick, readOnly: true }) }), _jsx(Dialog, { isOpen: isDialogOpen, title: dialogTitle, dialogBody: _jsxs("div", { className: "space-y-2", children: [_jsx(Input, { value: dialogSearchText, onChange: (e) => setDialogSearchText(e.target.value), onKeyDown: (e) => {
949
+ if (e.key === "Enter") {
950
+ e.preventDefault();
951
+ handleDialogSearch();
952
+ }
953
+ }, icon: "fa-search", iconPosition: "right", onIconClick: dialogSearchText.trim() ? handleDialogSearch : undefined, size: size, placeholder: "Buscar...", autoFocus: true }), isLoading ? (_jsx("div", { className: "flex items-center justify-center py-8", children: _jsx("i", { className: "fal fa-spinner fa-spin text-2xl text-[var(--color-primary)]" }) })) : options.length > 0 ? (_jsx("ul", { className: "space-y-1 max-h-96 overflow-y-auto", children: options.map((option, index) => {
954
+ const label = labelGetter(option);
955
+ const description = descriptionGetter(option);
956
+ const anyOption = option;
957
+ return (_jsx("li", { className: "px-3 py-2 cursor-pointer rounded-md flex items-start gap-2 text-sm\r\n text-[var(--color-text-primary)] hover:bg-[var(--color-bg-secondary)] transition-colors", onClick: () => handleSelect(option), children: renderOption ? (renderOption(option)) : (_jsxs(_Fragment, { children: [anyOption.icon && (_jsx("i", { className: `${normalizeIconClass(anyOption.icon)} mt-0.5 text-[var(--color-text-muted)]` })), _jsxs("div", { className: "flex flex-col flex-1", children: [_jsx("span", { className: "font-[var(--font-default)]", children: label }), description !== undefined &&
958
+ description !== null && (_jsx("span", { className: "text-xs text-[var(--color-text-secondary)]", children: description }))] })] })) }, String(valueGetter(option) ?? label ?? index)));
959
+ }) })) : hasSearched ? (_jsx("div", { className: "px-3 py-8 text-center text-sm text-[var(--color-text-secondary)]", children: noResultsText })) : null] }), dialogActions: _jsx(Button, { variant: "outline", onClick: () => setIsDialogOpen(false), children: "Cerrar" }), onClose: () => setIsDialogOpen(false), closeOnOverlayClick: true })] }));
960
+ }
961
+ const SearchSelectInputForwarded = React.forwardRef(SearchSelectInputInner);
962
+ SearchSelectInputForwarded.displayName = "SearchSelectInput";
963
+ export const SearchSelectInput = SearchSelectInputForwarded;