flysoft-react-ui 0.4.0 → 0.5.2

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