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,581 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React, { useState, useEffect, useRef } from "react";
3
+ import { createPortal } from "react-dom";
4
+ import { useSearchParams } from "react-router-dom";
5
+ import dayjs, {} from "dayjs";
6
+ import { Button } from "../form-controls/Button";
7
+ import { Input } from "../form-controls/Input";
8
+ import { DateInput } from "../form-controls/DateInput";
9
+ import { AutocompleteInput } from "../form-controls/AutocompleteInput";
10
+ import { SearchSelectInput } from "../form-controls/SearchSelectInput";
11
+ import { DataField } from "../layout/DataField";
12
+ import { normalizeIconClass } from "./iconUtils";
13
+ export const Filter = (props) => {
14
+ const { paramName, label, staticOptions, inputWidth, value: propValue, onChange, hideEmpty = false, } = props;
15
+ const filterType = props.filterType || "text";
16
+ // Calcular el ancho por defecto según el tipo de filtro
17
+ const defaultInputWidth = filterType === "date" || filterType === "autocomplete" ? "160px" : "200px";
18
+ const finalInputWidth = inputWidth || defaultInputWidth;
19
+ const [searchParams, setSearchParams] = useSearchParams();
20
+ const urlValue = paramName
21
+ ? searchParams.get(paramName) || undefined
22
+ : undefined;
23
+ // Usar propValue si está presente, sino usar urlValue
24
+ const currentValue = propValue !== undefined ? propValue : urlValue;
25
+ const [inputValue, setInputValue] = useState(currentValue || "");
26
+ const [searchValue, setSearchValue] = useState(currentValue || "");
27
+ const [dateValue, setDateValue] = useState(null);
28
+ const [autocompleteValue, setAutocompleteValue] = useState(currentValue || "");
29
+ const [searchSelectValue, setSearchSelectValue] = useState(currentValue || undefined);
30
+ const [searchSelectLabel, setSearchSelectLabel] = useState("");
31
+ const [isUserTyping, setIsUserTyping] = useState(false);
32
+ const [isOpen, setIsOpen] = useState(false);
33
+ const [panelPosition, setPanelPosition] = useState(null);
34
+ const containerRef = useRef(null);
35
+ const panelRef = useRef(null);
36
+ const inputRef = useRef(null);
37
+ const searchInputRef = useRef(null);
38
+ const searchSelectInputRef = useRef(null);
39
+ const dateInputRef = useRef(null);
40
+ const autocompleteInputRef = useRef(null);
41
+ // Sincronizar el input con el valor actual (propValue o urlValue)
42
+ useEffect(() => {
43
+ if (filterType !== "autocomplete" &&
44
+ filterType !== "search" &&
45
+ filterType !== "searchSelect") {
46
+ setInputValue(currentValue || "");
47
+ }
48
+ }, [currentValue, filterType]);
49
+ // Sincronizar el searchValue con el valor actual (propValue o urlValue)
50
+ useEffect(() => {
51
+ if (filterType === "search") {
52
+ setSearchValue(currentValue || "");
53
+ }
54
+ }, [currentValue, filterType]);
55
+ // Sincronizar el searchSelectValue con el valor actual (propValue o urlValue)
56
+ // y buscar el label si no está disponible
57
+ useEffect(() => {
58
+ if (filterType === "searchSelect" && currentValue) {
59
+ const searchSelectProps = props;
60
+ const getOptionLabel = searchSelectProps.getOptionLabel || ((item) => item.label || "");
61
+ const getOptionValue = searchSelectProps.getOptionValue || ((item) => item.value || "");
62
+ const onSingleSearchPromiseFn = searchSelectProps.onSingleSearchPromiseFn;
63
+ // Si searchSelectValue es un objeto, extraer el label directamente
64
+ if (searchSelectValue && typeof searchSelectValue === "object") {
65
+ const label = getOptionLabel(searchSelectValue);
66
+ const value = String(getOptionValue(searchSelectValue));
67
+ if (value === String(currentValue)) {
68
+ setSearchSelectLabel(label);
69
+ return;
70
+ }
71
+ }
72
+ // Si no tenemos el objeto completo, usar onSingleSearchPromiseFn para obtenerlo
73
+ if (onSingleSearchPromiseFn) {
74
+ onSingleSearchPromiseFn(currentValue)
75
+ .then((option) => {
76
+ if (option) {
77
+ setSearchSelectValue(option);
78
+ setSearchSelectLabel(getOptionLabel(option));
79
+ }
80
+ else {
81
+ setSearchSelectLabel(String(currentValue));
82
+ }
83
+ })
84
+ .catch(() => {
85
+ setSearchSelectLabel(String(currentValue));
86
+ });
87
+ }
88
+ else {
89
+ setSearchSelectLabel(String(currentValue));
90
+ }
91
+ }
92
+ else if (filterType === "searchSelect" && !currentValue) {
93
+ setSearchSelectValue(undefined);
94
+ setSearchSelectLabel("");
95
+ }
96
+ // eslint-disable-next-line react-hooks/exhaustive-deps
97
+ }, [currentValue, filterType]);
98
+ // Sincronizar el dateValue con el valor actual (propValue o urlValue)
99
+ useEffect(() => {
100
+ if (filterType === "date") {
101
+ if (currentValue) {
102
+ const date = dayjs(currentValue, "YYYY-MM-DD");
103
+ setDateValue(date.isValid() ? date : null);
104
+ }
105
+ else {
106
+ setDateValue(null);
107
+ }
108
+ }
109
+ }, [currentValue, filterType]);
110
+ // Sincronizar el autocompleteValue con el valor actual (propValue o urlValue)
111
+ // Para autocomplete, mantener el value (no el label) para que AutocompleteInput pueda encontrar la opción
112
+ useEffect(() => {
113
+ if (filterType === "autocomplete") {
114
+ // Mantener el value, el AutocompleteInput se encargará de mostrar el label
115
+ setAutocompleteValue(currentValue || "");
116
+ setIsUserTyping(false);
117
+ }
118
+ }, [currentValue, filterType]);
119
+ // Cerrar el panel al hacer clic fuera
120
+ useEffect(() => {
121
+ if (!isOpen)
122
+ return;
123
+ const handleClickOutside = (event) => {
124
+ const target = event.target;
125
+ const isClickInsideContainer = containerRef.current?.contains(target);
126
+ const isClickInsidePanel = panelRef.current?.contains(target);
127
+ // Verificar si el click está dentro de un elemento con portal (DatePicker, AutocompleteInput, etc.)
128
+ // Estos elementos tienen z-[2001] y están posicionados fixed
129
+ let isClickInsidePortal = false;
130
+ if (target instanceof Element) {
131
+ let element = target;
132
+ // Subir por el árbol DOM buscando elementos con z-index alto que sean portales
133
+ while (element && element !== document.body) {
134
+ const computedStyle = window.getComputedStyle(element);
135
+ const zIndex = computedStyle.zIndex;
136
+ const position = computedStyle.position;
137
+ // Si encontramos un elemento fixed con z-index alto, probablemente es un portal
138
+ if (position === "fixed" &&
139
+ (zIndex === "2001" || parseInt(zIndex) >= 2001)) {
140
+ isClickInsidePortal = true;
141
+ break;
142
+ }
143
+ element = element.parentElement;
144
+ }
145
+ }
146
+ if (!isClickInsideContainer &&
147
+ !isClickInsidePanel &&
148
+ !isClickInsidePortal) {
149
+ setIsOpen(false);
150
+ }
151
+ };
152
+ // Pequeño delay para asegurar que el portal esté montado
153
+ const timer = setTimeout(() => {
154
+ document.addEventListener("mousedown", handleClickOutside);
155
+ }, 0);
156
+ return () => {
157
+ clearTimeout(timer);
158
+ document.removeEventListener("mousedown", handleClickOutside);
159
+ };
160
+ }, [isOpen]);
161
+ const handleSetFilter = () => {
162
+ const newValue = inputValue.trim() || undefined;
163
+ // Si hay onChange, llamarlo con el nuevo valor
164
+ if (onChange) {
165
+ onChange(newValue);
166
+ }
167
+ // Si hay paramName, actualizar el query param
168
+ if (paramName) {
169
+ const newSearchParams = new URLSearchParams(searchParams);
170
+ if (newValue) {
171
+ newSearchParams.set(paramName, newValue);
172
+ }
173
+ else {
174
+ newSearchParams.delete(paramName);
175
+ }
176
+ // Usar replace: true para reemplazar la URL sin agregar una nueva entrada al historial
177
+ setSearchParams(newSearchParams, { replace: true });
178
+ }
179
+ setIsOpen(false);
180
+ };
181
+ const handleSearchChange = (e) => {
182
+ setSearchValue(e.target.value);
183
+ };
184
+ const handleSearchKeyDown = (e) => {
185
+ if (e.key === "Enter") {
186
+ handleSearchSubmit();
187
+ }
188
+ };
189
+ const handleSearchSubmit = () => {
190
+ const newValue = searchValue.trim() || undefined;
191
+ // Si hay onChange, llamarlo con el nuevo valor
192
+ if (onChange) {
193
+ onChange(newValue);
194
+ }
195
+ // Si hay paramName, actualizar el query param
196
+ if (paramName) {
197
+ const newSearchParams = new URLSearchParams(searchParams);
198
+ if (newValue) {
199
+ newSearchParams.set(paramName, newValue);
200
+ }
201
+ else {
202
+ newSearchParams.delete(paramName);
203
+ }
204
+ setSearchParams(newSearchParams, { replace: true });
205
+ }
206
+ };
207
+ const handleSearchIconClick = () => {
208
+ // Verificar si el texto actual coincide con el valor actual
209
+ const valueMatches = currentValue && searchValue.trim() === currentValue;
210
+ if (valueMatches) {
211
+ // Si coincide, limpiar
212
+ const newValue = undefined;
213
+ // Si hay onChange, llamarlo con undefined
214
+ if (onChange) {
215
+ onChange(newValue);
216
+ }
217
+ // Si hay paramName, actualizar el query param
218
+ if (paramName) {
219
+ const newSearchParams = new URLSearchParams(searchParams);
220
+ newSearchParams.delete(paramName);
221
+ setSearchParams(newSearchParams, { replace: true });
222
+ }
223
+ setSearchValue("");
224
+ }
225
+ else {
226
+ // Si no coincide, hacer submit
227
+ handleSearchSubmit();
228
+ }
229
+ };
230
+ const handleAutocompleteChange = () => {
231
+ // Marcar que el usuario está escribiendo para que el AutocompleteInput pueda manejar el texto libremente
232
+ setIsUserTyping(true);
233
+ };
234
+ const handleAutocompleteSelect = () => {
235
+ // Obtener el valor actual del input del AutocompleteInput
236
+ const currentInputValue = autocompleteInputRef.current?.value || "";
237
+ // Buscar si el texto actual coincide con alguna opción
238
+ if (filterType === "autocomplete") {
239
+ const autocompleteProps = props;
240
+ const getOptionLabel = autocompleteProps.getOptionLabel || ((item) => item.label || "");
241
+ const getOptionValue = autocompleteProps.getOptionValue || ((item) => item.value || "");
242
+ // Buscar opción por label
243
+ const matchingOption = autocompleteProps.options.find((opt) => getOptionLabel(opt).toLowerCase() === currentInputValue.toLowerCase());
244
+ let newValue;
245
+ if (matchingOption) {
246
+ newValue = String(getOptionValue(matchingOption));
247
+ setAutocompleteValue(newValue);
248
+ setIsUserTyping(false);
249
+ }
250
+ else if (currentInputValue.trim()) {
251
+ // Si no coincide con ninguna opción, guardar el texto tal cual
252
+ newValue = currentInputValue.trim();
253
+ setAutocompleteValue(newValue);
254
+ setIsUserTyping(false);
255
+ }
256
+ else {
257
+ newValue = undefined;
258
+ setAutocompleteValue("");
259
+ setIsUserTyping(false);
260
+ }
261
+ // Si hay onChange, llamarlo con el nuevo valor
262
+ if (onChange) {
263
+ onChange(newValue);
264
+ }
265
+ // Si hay paramName, actualizar el query param
266
+ if (paramName) {
267
+ const newSearchParams = new URLSearchParams(searchParams);
268
+ if (newValue) {
269
+ newSearchParams.set(paramName, newValue);
270
+ }
271
+ else {
272
+ newSearchParams.delete(paramName);
273
+ }
274
+ setSearchParams(newSearchParams, { replace: true });
275
+ }
276
+ setIsOpen(false);
277
+ }
278
+ };
279
+ const handleAutocompleteOptionSelect = (option) => {
280
+ if (filterType === "autocomplete") {
281
+ const autocompleteProps = props;
282
+ const getOptionValue = autocompleteProps.getOptionValue || ((item) => item.value || "");
283
+ const newValue = String(getOptionValue(option));
284
+ setAutocompleteValue(newValue);
285
+ setIsUserTyping(false);
286
+ // Si hay onChange, llamarlo con el nuevo valor
287
+ if (onChange) {
288
+ onChange(newValue);
289
+ }
290
+ // Si hay paramName, actualizar el query param
291
+ if (paramName) {
292
+ const newSearchParams = new URLSearchParams(searchParams);
293
+ newSearchParams.set(paramName, newValue);
294
+ setSearchParams(newSearchParams, { replace: true });
295
+ }
296
+ setIsOpen(false);
297
+ }
298
+ };
299
+ const handleSearchSelectChange = (value) => {
300
+ if (filterType === "searchSelect") {
301
+ const searchSelectProps = props;
302
+ const getOptionValue = searchSelectProps.getOptionValue || ((item) => item.value || "");
303
+ const getOptionLabel = searchSelectProps.getOptionLabel || ((item) => item.label || "");
304
+ // Si value es un objeto, extraer el valor usando getOptionValue
305
+ // Si value es un string/number, usarlo directamente
306
+ let newValue;
307
+ if (value === undefined || value === null) {
308
+ newValue = undefined;
309
+ setSearchSelectValue(undefined);
310
+ setSearchSelectLabel("");
311
+ }
312
+ else if (typeof value === "object") {
313
+ newValue = String(getOptionValue(value));
314
+ setSearchSelectValue(value);
315
+ setSearchSelectLabel(getOptionLabel(value));
316
+ }
317
+ else {
318
+ newValue = String(value);
319
+ setSearchSelectValue(value);
320
+ // Si solo tenemos el valor, el useEffect se encargará de buscar el label
321
+ setSearchSelectLabel("");
322
+ }
323
+ // Si hay onChange, llamarlo con el nuevo valor
324
+ if (onChange) {
325
+ onChange(newValue);
326
+ }
327
+ // Si hay paramName, actualizar el query param
328
+ if (paramName) {
329
+ const newSearchParams = new URLSearchParams(searchParams);
330
+ if (newValue) {
331
+ newSearchParams.set(paramName, newValue);
332
+ }
333
+ else {
334
+ newSearchParams.delete(paramName);
335
+ }
336
+ setSearchParams(newSearchParams, { replace: true });
337
+ }
338
+ setIsOpen(false);
339
+ }
340
+ };
341
+ const handleDateChange = (date) => {
342
+ // Solo actualizar el estado temporal, no setear en la URL
343
+ setDateValue(date);
344
+ };
345
+ const handleSetDateFilter = () => {
346
+ let newValue;
347
+ if (dateValue && dateValue.isValid()) {
348
+ // Guardar como yyyy-mm-dd
349
+ newValue = dateValue.format("YYYY-MM-DD");
350
+ }
351
+ else {
352
+ newValue = undefined;
353
+ }
354
+ // Si hay onChange, llamarlo con el nuevo valor
355
+ if (onChange) {
356
+ onChange(newValue);
357
+ }
358
+ // Si hay paramName, actualizar el query param
359
+ if (paramName) {
360
+ const newSearchParams = new URLSearchParams(searchParams);
361
+ if (newValue) {
362
+ newSearchParams.set(paramName, newValue);
363
+ }
364
+ else {
365
+ newSearchParams.delete(paramName);
366
+ }
367
+ setSearchParams(newSearchParams, { replace: true });
368
+ }
369
+ setIsOpen(false);
370
+ };
371
+ const handleStaticOptionSelect = (option) => {
372
+ const newValue = option.value;
373
+ // Si hay onChange, llamarlo con el nuevo valor
374
+ if (onChange) {
375
+ onChange(newValue);
376
+ }
377
+ // Si hay paramName, actualizar el query param
378
+ if (paramName) {
379
+ const newSearchParams = new URLSearchParams(searchParams);
380
+ newSearchParams.set(paramName, newValue);
381
+ setSearchParams(newSearchParams, { replace: true });
382
+ }
383
+ setIsOpen(false);
384
+ };
385
+ // Para staticOptions: mostrar el texto de la opción si el valor coincide (prioridad)
386
+ // Para date: convertir yyyy-mm-dd a Dayjs y formatear para mostrar como dd/mm/yyyy
387
+ // Para autocomplete: mostrar el label de la opción si el valor coincide
388
+ const getDisplayValue = () => {
389
+ // Primero verificar si el valor coincide con alguna opción estática
390
+ if (staticOptions && currentValue) {
391
+ const option = staticOptions.find((opt) => opt.value === currentValue);
392
+ if (option) {
393
+ return option.text;
394
+ }
395
+ }
396
+ // Para autocomplete, buscar el label de la opción
397
+ if (filterType === "autocomplete" && currentValue) {
398
+ const autocompleteProps = props;
399
+ if (autocompleteProps.options) {
400
+ const getOptionLabel = autocompleteProps.getOptionLabel || ((item) => item.label || "");
401
+ const getOptionValue = autocompleteProps.getOptionValue || ((item) => item.value || "");
402
+ const option = autocompleteProps.options.find((opt) => String(getOptionValue(opt)) === String(currentValue));
403
+ if (option) {
404
+ return getOptionLabel(option);
405
+ }
406
+ }
407
+ }
408
+ // Para searchSelect, mostrar el label guardado o buscar el label del objeto
409
+ if (filterType === "searchSelect" && currentValue) {
410
+ if (searchSelectLabel) {
411
+ return searchSelectLabel;
412
+ }
413
+ // Si tenemos el objeto completo, extraer el label
414
+ if (searchSelectValue && typeof searchSelectValue === "object") {
415
+ const searchSelectProps = props;
416
+ const getOptionLabel = searchSelectProps.getOptionLabel || ((item) => item.label || "");
417
+ return getOptionLabel(searchSelectValue);
418
+ }
419
+ }
420
+ // Si no hay opción estática que coincida, formatear según el tipo
421
+ if (filterType === "date" && currentValue) {
422
+ const date = dayjs(currentValue, "YYYY-MM-DD");
423
+ if (date.isValid()) {
424
+ return date.format("DD/MM/YYYY");
425
+ }
426
+ }
427
+ return currentValue || "";
428
+ };
429
+ const handleClearFilter = (e) => {
430
+ e.stopPropagation();
431
+ // Si hay onChange, llamarlo con undefined para limpiar
432
+ if (onChange) {
433
+ onChange(undefined);
434
+ }
435
+ // Si hay paramName, actualizar el query param
436
+ if (paramName) {
437
+ const newSearchParams = new URLSearchParams(searchParams);
438
+ newSearchParams.delete(paramName);
439
+ setSearchParams(newSearchParams, { replace: true });
440
+ }
441
+ };
442
+ const handleTogglePanel = () => {
443
+ if (!isOpen) {
444
+ // Calcular posición antes de abrir
445
+ if (containerRef.current) {
446
+ const rect = containerRef.current.getBoundingClientRect();
447
+ setPanelPosition({
448
+ top: rect.bottom + window.scrollY + 4,
449
+ left: rect.left + window.scrollX,
450
+ width: rect.width,
451
+ });
452
+ }
453
+ }
454
+ setIsOpen(!isOpen);
455
+ };
456
+ // Actualizar posición cuando se abre o cuando cambia el scroll
457
+ useEffect(() => {
458
+ if (isOpen && containerRef.current) {
459
+ const updatePosition = () => {
460
+ const rect = containerRef.current?.getBoundingClientRect();
461
+ if (rect) {
462
+ setPanelPosition({
463
+ top: rect.bottom + window.scrollY + 4,
464
+ left: rect.left + window.scrollX,
465
+ width: rect.width,
466
+ });
467
+ }
468
+ };
469
+ updatePosition();
470
+ window.addEventListener("scroll", updatePosition, true);
471
+ window.addEventListener("resize", updatePosition);
472
+ return () => {
473
+ window.removeEventListener("scroll", updatePosition, true);
474
+ window.removeEventListener("resize", updatePosition);
475
+ };
476
+ }
477
+ }, [isOpen]);
478
+ // Hacer foco en el input cuando se abre el panel
479
+ useEffect(() => {
480
+ if (isOpen) {
481
+ // Pequeño delay para asegurar que el DOM esté actualizado
482
+ const timer = setTimeout(() => {
483
+ if (filterType === "text" || filterType === "number") {
484
+ inputRef.current?.focus();
485
+ }
486
+ else if (filterType === "date") {
487
+ dateInputRef.current?.focus();
488
+ }
489
+ else if (filterType === "autocomplete") {
490
+ autocompleteInputRef.current?.focus();
491
+ }
492
+ // searchSelect no necesita focus porque usa un Dialog interno
493
+ }, 50);
494
+ return () => clearTimeout(timer);
495
+ }
496
+ }, [isOpen, filterType]);
497
+ // Si hideEmpty es true y no hay valor, no renderizar el componente
498
+ if (hideEmpty && !currentValue) {
499
+ return null;
500
+ }
501
+ // Contenedor tipo badge con diseño similar al Input
502
+ // Altura ajustada para coincidir con input sm: py-1.5 (6px arriba y abajo) + text-sm (14px línea) = ~26px total
503
+ const badgeContainer = (_jsxs("div", { className: "inline-flex items-center gap-2 px-3 py-1.5 h-[2.1rem] rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-default)] text-[var(--color-text-primary)] font-[var(--font-default)] cursor-pointer text-sm transition-colors", onClick: handleTogglePanel, children: [_jsx("span", { className: "text-sm min-w-[1rem]", children: getDisplayValue() || "\u00A0" }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx("span", { className: "p-0.5 hover:bg-[var(--color-bg-secondary)] rounded transition-colors flex items-center justify-center", children: _jsx("i", { className: `${normalizeIconClass("fa-chevron-down")} text-xs text-[var(--color-text-muted)] hover:text-[var(--color-primary)] transition-all ${isOpen ? "rotate-180" : ""}` }) }), currentValue && (_jsx("button", { onClick: handleClearFilter, className: "p-0.5 hover:bg-[var(--color-bg-secondary)] rounded transition-colors flex items-center justify-center", "aria-label": "Borrar filtro", type: "button", children: _jsx("i", { className: `${normalizeIconClass("fa-times")} text-xs text-[var(--color-text-muted)] hover:text-[var(--color-primary)] transition-colors` }) }))] })] }));
504
+ // Renderizar según el tipo de filtro
505
+ if (filterType === "autocomplete") {
506
+ return (_jsxs("div", { ref: containerRef, className: "relative inline-block", children: [_jsx(DataField, { label: label, value: badgeContainer, className: "inline-block" }), isOpen &&
507
+ panelPosition &&
508
+ typeof document !== "undefined" &&
509
+ document.body &&
510
+ createPortal(_jsx("div", { ref: panelRef, className: "fixed z-[2001] w-fit rounded-md border border-[var(--color-border-default)] bg-[var(--color-bg-default)] shadow-[var(--shadow-lg)] p-4", style: {
511
+ top: `${panelPosition.top}px`,
512
+ left: `${panelPosition.left}px`,
513
+ }, children: _jsxs("div", { className: "space-y-3", children: [staticOptions && staticOptions.length > 0 && (_jsx("ul", { className: "py-1 max-h-60 overflow-auto", children: staticOptions.map((option) => (_jsx("li", { className: `px-3 py-2 cursor-pointer flex items-center gap-2 text-sm rounded transition-colors ${currentValue === option.value
514
+ ? "bg-[var(--color-primary-soft)] text-[var(--color-primary)]"
515
+ : "text-[var(--color-text-primary)] hover:bg-[var(--color-bg-secondary)]"}`, onMouseDown: (e) => {
516
+ e.preventDefault();
517
+ handleStaticOptionSelect(option);
518
+ }, children: _jsx("span", { className: "font-[var(--font-default)]", children: option.text }) }, option.value))) })), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { style: { width: finalInputWidth }, children: _jsx(AutocompleteInput, { ref: autocompleteInputRef, options: props.options, value: isUserTyping ? undefined : autocompleteValue, onChange: handleAutocompleteChange, getOptionLabel: props.getOptionLabel, getOptionValue: props.getOptionValue, renderOption: props.renderOption, noResultsText: props.noResultsText, onSelectOption: handleAutocompleteOptionSelect }) }), _jsx(Button, { onClick: handleAutocompleteSelect, icon: "fa-arrow-right", variant: "ghost" })] })] }) }), document.body)] }));
519
+ }
520
+ if (filterType === "date") {
521
+ return (_jsxs("div", { ref: containerRef, className: "relative inline-block", children: [_jsx(DataField, { label: label, value: badgeContainer, className: "inline-block" }), isOpen &&
522
+ panelPosition &&
523
+ typeof document !== "undefined" &&
524
+ document.body &&
525
+ createPortal(_jsx("div", { ref: panelRef, className: "fixed z-[2001] w-fit rounded-md border border-[var(--color-border-default)] bg-[var(--color-bg-default)] shadow-[var(--shadow-lg)] p-4", style: {
526
+ top: `${panelPosition.top}px`,
527
+ left: `${panelPosition.left}px`,
528
+ }, children: _jsxs("div", { className: "space-y-3", children: [staticOptions && staticOptions.length > 0 && (_jsx("ul", { className: "py-1 max-h-60 overflow-auto", children: staticOptions.map((option) => (_jsx("li", { className: `px-3 py-2 cursor-pointer flex items-center gap-2 text-sm rounded transition-colors ${currentValue === option.value
529
+ ? "bg-[var(--color-primary-soft)] text-[var(--color-primary)]"
530
+ : "text-[var(--color-text-primary)] hover:bg-[var(--color-bg-secondary)]"}`, onMouseDown: (e) => {
531
+ e.preventDefault();
532
+ handleStaticOptionSelect(option);
533
+ }, children: _jsx("span", { className: "font-[var(--font-default)]", children: option.text }) }, option.value))) })), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { style: { width: finalInputWidth }, children: _jsx(DateInput, { ref: dateInputRef, value: dateValue, onChange: handleDateChange, format: "dd/mm/yyyy" }) }), _jsx(Button, { onClick: handleSetDateFilter, icon: "fa-arrow-right", variant: "ghost" })] })] }) }), document.body)] }));
534
+ }
535
+ // Para searchSelect
536
+ if (filterType === "searchSelect") {
537
+ const searchSelectProps = props;
538
+ return (_jsxs("div", { ref: containerRef, className: "relative inline-block", children: [_jsx(DataField, { label: label, value: badgeContainer, className: "inline-block" }), isOpen &&
539
+ panelPosition &&
540
+ typeof document !== "undefined" &&
541
+ document.body &&
542
+ createPortal(_jsx("div", { ref: panelRef, className: "fixed z-[2001] w-fit rounded-md border border-[var(--color-border-default)] bg-[var(--color-bg-default)] shadow-[var(--shadow-lg)] p-4", style: {
543
+ top: `${panelPosition.top}px`,
544
+ left: `${panelPosition.left}px`,
545
+ }, children: _jsxs("div", { className: "space-y-3", children: [staticOptions && staticOptions.length > 0 && (_jsx("ul", { className: "py-1 max-h-60 overflow-auto", children: staticOptions.map((option) => (_jsx("li", { className: `px-3 py-2 cursor-pointer flex items-center gap-2 text-sm rounded transition-colors ${currentValue === option.value
546
+ ? "bg-[var(--color-primary-soft)] text-[var(--color-primary)]"
547
+ : "text-[var(--color-text-primary)] hover:bg-[var(--color-bg-secondary)]"}`, onMouseDown: (e) => {
548
+ e.preventDefault();
549
+ handleStaticOptionSelect(option);
550
+ }, children: _jsx("span", { className: "font-[var(--font-default)]", children: option.text }) }, option.value))) })), _jsx("div", { style: { width: finalInputWidth }, children: _jsx(SearchSelectInput, { ref: searchSelectInputRef, value: searchSelectValue, onChange: handleSearchSelectChange, onSearchPromiseFn: searchSelectProps.onSearchPromiseFn, onSingleSearchPromiseFn: searchSelectProps.onSingleSearchPromiseFn, getOptionLabel: searchSelectProps.getOptionLabel, getOptionValue: searchSelectProps.getOptionValue, renderOption: searchSelectProps.renderOption, dialogTitle: searchSelectProps.dialogTitle, noResultsText: searchSelectProps.noResultsText }) })] }) }), document.body)] }));
551
+ }
552
+ // Para search
553
+ if (filterType === "search") {
554
+ // Mostrar X solo si hay un valor actual Y el texto del input coincide con ese valor
555
+ const hasValue = !!currentValue;
556
+ const valueMatches = hasValue && searchValue.trim() === currentValue;
557
+ return (_jsx("div", { ref: containerRef, className: "relative inline-block", children: _jsx(DataField, { label: label, value: _jsx("div", { style: { width: finalInputWidth }, children: _jsx(Input, { ref: searchInputRef, type: "text", value: searchValue, onChange: handleSearchChange, onKeyDown: handleSearchKeyDown, icon: valueMatches ? "fa-times" : "fa-search", iconPosition: "right", onIconClick: handleSearchIconClick, placeholder: "Buscar...", size: "sm" }) }), className: "inline-block" }) }));
558
+ }
559
+ // Para text y number
560
+ return (_jsxs("div", { ref: containerRef, className: "relative inline-block", children: [_jsx(DataField, { label: label, value: badgeContainer, className: "inline-block" }), isOpen &&
561
+ panelPosition &&
562
+ typeof document !== "undefined" &&
563
+ document.body &&
564
+ createPortal(_jsx("div", { ref: panelRef, className: "fixed z-[2001] w-fit rounded-md border border-[var(--color-border-default)] bg-[var(--color-bg-default)] shadow-[var(--shadow-lg)] p-4", style: {
565
+ top: `${panelPosition.top}px`,
566
+ left: `${panelPosition.left}px`,
567
+ }, children: _jsxs("div", { className: "space-y-3", children: [staticOptions && staticOptions.length > 0 && (_jsx("ul", { className: "py-1 max-h-60 overflow-auto", children: staticOptions.map((option) => (_jsx("li", { className: `px-3 py-2 cursor-pointer flex items-center gap-2 text-sm rounded transition-colors ${urlValue === option.value
568
+ ? "bg-[var(--color-primary-soft)] text-[var(--color-primary)]"
569
+ : "text-[var(--color-text-primary)] hover:bg-[var(--color-bg-secondary)]"}`, onMouseDown: (e) => {
570
+ e.preventDefault();
571
+ handleStaticOptionSelect(option);
572
+ }, children: _jsx("span", { className: "font-[var(--font-default)]", children: option.text }) }, option.value))) })), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { style: { width: finalInputWidth }, children: _jsx(Input, { ref: inputRef, type: filterType === "number" ? "number" : "text", value: inputValue, onChange: (e) => setInputValue(e.target.value), placeholder: "Ingresa un valor", min: filterType === "number"
573
+ ? props.min
574
+ : undefined, max: filterType === "number"
575
+ ? props.max
576
+ : undefined, onKeyDown: (e) => {
577
+ if (e.key === "Enter") {
578
+ handleSetFilter();
579
+ }
580
+ } }) }), _jsx(Button, { onClick: handleSetFilter, icon: "fa-arrow-right", variant: "ghost" })] })] }) }), document.body)] }));
581
+ };
@@ -0,0 +1,21 @@
1
+ import React from "react";
2
+ import { type FilterProps } from "./Filter";
3
+ export interface FilterConfig {
4
+ filterType: FilterProps["filterType"];
5
+ paramName: string;
6
+ label?: string;
7
+ staticOptions?: FilterProps["staticOptions"];
8
+ inputWidth?: string;
9
+ min?: number;
10
+ max?: number;
11
+ options?: any[];
12
+ getOptionLabel?: (item: any) => string;
13
+ getOptionValue?: (item: any) => any;
14
+ renderOption?: (item: any) => React.ReactNode;
15
+ noResultsText?: string;
16
+ }
17
+ export interface FiltersDialogProps {
18
+ filters: FilterConfig[];
19
+ }
20
+ export declare const FiltersDialog: React.FC<FiltersDialogProps>;
21
+ //# sourceMappingURL=FiltersDialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FiltersDialog.d.ts","sourceRoot":"","sources":["../../../src/components/utils/FiltersDialog.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8B,MAAM,OAAO,CAAC;AAInD,OAAO,EAAU,KAAK,WAAW,EAAE,MAAM,UAAU,CAAC;AAGpD,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,WAAW,CAAC,YAAY,CAAC,CAAC;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,WAAW,CAAC,eAAe,CAAC,CAAC;IAC7C,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,GAAG,EAAE,CAAC;IAChB,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,MAAM,CAAC;IACvC,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,GAAG,CAAC;IACpC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,KAAK,CAAC,SAAS,CAAC;IAC9C,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,YAAY,EAAE,CAAC;CACzB;AAED,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAgJtD,CAAC"}