@zauru-sdk/components 6.5.1 → 6.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.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,17 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [6.5.2](https://github.com/intuitiva/zauru-typescript-sdk/compare/@zauru-sdk/components@6.5.1...@zauru-sdk/components@6.5.2) (2026-09-16)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+ * sincronizar defaults async de SelectField ([5a06089](https://github.com/intuitiva/zauru-typescript-sdk/commit/5a06089bd2bbda775418123ed983ab98bf8cbef0))
12
+
13
+
14
+
15
+
16
+
6
17
  ## [6.5.1](https://github.com/intuitiva/zauru-typescript-sdk/compare/@zauru-sdk/components@6.5.0...@zauru-sdk/components@6.5.1) (2026-09-16)
7
18
 
8
19
  **Note:** Version bump only for package @zauru-sdk/components
@@ -24,6 +24,10 @@ export const SelectField = (props) => {
24
24
  const [highlightedIndex, setHighlightedIndex] = useState(-1);
25
25
  const selectRef = useRef(null);
26
26
  const optionsRef = useRef(null);
27
+ const hasManualSingleValue = useRef(false);
28
+ const hasManualMultiValue = useRef(false);
29
+ const lastSyncedDefaultValue = useRef(defaultValue?.value);
30
+ const lastSyncedDefaultValues = useRef(defaultValueMulti.map((option) => option.value));
27
31
  const [isTabPressed, setIsTabPressed] = useState(false);
28
32
  const [isSearching, setIsSearching] = useState(false);
29
33
  // Use refs to store stable references to callbacks
@@ -52,18 +56,51 @@ export const SelectField = (props) => {
52
56
  const bgColor = isReadOnly ? "bg-gray-200" : `bg-${color}-50`;
53
57
  const textColor = isReadOnly ? "text-gray-500" : `text-${color}-900`;
54
58
  const borderColor = isReadOnly ? "border-gray-300" : `border-${color}-200`;
55
- // Sincronizar estado interno cuando cambien las props
59
+ const defaultValueMultiKey = defaultValueMulti
60
+ .map((option) => String(option.value))
61
+ .join("\u0000");
62
+ // Sincronizar defaults asíncronos con el estado visual y React Hook Form.
63
+ //
64
+ // Es importante depender del valor escalar y no del objeto completo: muchas
65
+ // pantallas construyen el option con `find()` en cada render. También solo
66
+ // reemplazamos el valor actual si todavía está vacío o conserva el último
67
+ // default sincronizado; así una selección manual no se pierde cuando el
68
+ // catálogo termina de cargar o se recrea el objeto de opciones.
56
69
  useEffect(() => {
57
- if (defaultValue && (!value || value.value !== defaultValue.value)) {
58
- setValue(defaultValue);
59
- setInputValue(defaultValue.label);
70
+ if (!defaultValue || isMulti || hasManualSingleValue.current) {
71
+ return;
72
+ }
73
+ const currentValue = value?.value;
74
+ const canSyncDefault = currentValue === undefined ||
75
+ currentValue === null ||
76
+ currentValue === lastSyncedDefaultValue.current;
77
+ if (canSyncDefault) {
78
+ if (currentValue !== defaultValue.value) {
79
+ setValue(defaultValue);
80
+ setInputValue(defaultValue.label);
81
+ }
82
+ setFormValue?.(name || "", defaultValue.value);
60
83
  }
61
- }, [defaultValue]);
84
+ lastSyncedDefaultValue.current = defaultValue.value;
85
+ }, [defaultValue?.value, isMulti, name, setFormValue]);
62
86
  useEffect(() => {
63
- if (!areOptionArraysEqual(defaultValueMulti, valueMulti)) {
64
- setValueMulti(defaultValueMulti);
87
+ if (!isMulti ||
88
+ defaultValueMulti.length === 0 ||
89
+ hasManualMultiValue.current) {
90
+ return;
91
+ }
92
+ const currentValues = valueMulti.map((option) => option.value);
93
+ const previousDefaultValues = lastSyncedDefaultValues.current;
94
+ const canSyncDefault = currentValues.length === 0 ||
95
+ areOptionArraysEqual(valueMulti, previousDefaultValues.map((value) => ({ value, label: "" })));
96
+ if (canSyncDefault) {
97
+ if (!areOptionArraysEqual(defaultValueMulti, valueMulti)) {
98
+ setValueMulti(defaultValueMulti);
99
+ }
100
+ setFormValue?.(name || "", defaultValueMulti.map((option) => option.value));
65
101
  }
66
- }, [defaultValueMulti]);
102
+ lastSyncedDefaultValues.current = defaultValueMulti.map((option) => option.value);
103
+ }, [defaultValueMultiKey, isMulti, name, setFormValue]);
67
104
  useEffect(() => {
68
105
  setFilteredOptions(options);
69
106
  }, [options]);
@@ -80,6 +117,7 @@ export const SelectField = (props) => {
80
117
  };
81
118
  }, []);
82
119
  const handleSetSingleValue = useCallback((value) => {
120
+ hasManualSingleValue.current = true;
83
121
  if (value) {
84
122
  setValue(value);
85
123
  setInputValue(value.label);
@@ -97,6 +135,7 @@ export const SelectField = (props) => {
97
135
  onChangeRef.current && onChangeRef.current(value ?? null);
98
136
  }, [name, setFormValue]);
99
137
  const handleAddMultiValue = useCallback((value) => {
138
+ hasManualMultiValue.current = true;
100
139
  if (value) {
101
140
  const existValue = options.some((v) => v.value === value.value);
102
141
  const noEstaYaSeleccionado = !valueMulti.some((v) => v.value === value.value);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zauru-sdk/components",
3
- "version": "6.5.1",
3
+ "version": "6.5.2",
4
4
  "description": "Componentes reutilizables en las WebApps de Zauru.",
5
5
  "main": "./dist/esm/index.js",
6
6
  "module": "./dist/esm/index.js",
@@ -62,5 +62,5 @@
62
62
  "styled-components": "^5.3.5",
63
63
  "zod": "^3.23.8"
64
64
  },
65
- "gitHead": "99cc7bef84a3d3c919d336a95eebe82cce2e8c08"
65
+ "gitHead": "2189a91ba403d92b2bb4f3e84af9b52852e5cbcf"
66
66
  }
@@ -81,6 +81,12 @@ export const SelectField = (props: Props) => {
81
81
  const [highlightedIndex, setHighlightedIndex] = useState<number>(-1);
82
82
  const selectRef = useRef<HTMLDivElement>(null);
83
83
  const optionsRef = useRef<HTMLUListElement>(null);
84
+ const hasManualSingleValue = useRef(false);
85
+ const hasManualMultiValue = useRef(false);
86
+ const lastSyncedDefaultValue = useRef(defaultValue?.value);
87
+ const lastSyncedDefaultValues = useRef(
88
+ defaultValueMulti.map((option) => option.value),
89
+ );
84
90
  const [isTabPressed, setIsTabPressed] = useState<boolean>(false);
85
91
  const [isSearching, setIsSearching] = useState<boolean>(false);
86
92
 
@@ -119,20 +125,71 @@ export const SelectField = (props: Props) => {
119
125
  const bgColor = isReadOnly ? "bg-gray-200" : `bg-${color}-50`;
120
126
  const textColor = isReadOnly ? "text-gray-500" : `text-${color}-900`;
121
127
  const borderColor = isReadOnly ? "border-gray-300" : `border-${color}-200`;
128
+ const defaultValueMultiKey = defaultValueMulti
129
+ .map((option) => String(option.value))
130
+ .join("\u0000");
122
131
 
123
- // Sincronizar estado interno cuando cambien las props
132
+ // Sincronizar defaults asíncronos con el estado visual y React Hook Form.
133
+ //
134
+ // Es importante depender del valor escalar y no del objeto completo: muchas
135
+ // pantallas construyen el option con `find()` en cada render. También solo
136
+ // reemplazamos el valor actual si todavía está vacío o conserva el último
137
+ // default sincronizado; así una selección manual no se pierde cuando el
138
+ // catálogo termina de cargar o se recrea el objeto de opciones.
124
139
  useEffect(() => {
125
- if (defaultValue && (!value || value.value !== defaultValue.value)) {
126
- setValue(defaultValue);
127
- setInputValue(defaultValue.label);
140
+ if (!defaultValue || isMulti || hasManualSingleValue.current) {
141
+ return;
128
142
  }
129
- }, [defaultValue]);
143
+
144
+ const currentValue = value?.value;
145
+ const canSyncDefault =
146
+ currentValue === undefined ||
147
+ currentValue === null ||
148
+ currentValue === lastSyncedDefaultValue.current;
149
+
150
+ if (canSyncDefault) {
151
+ if (currentValue !== defaultValue.value) {
152
+ setValue(defaultValue);
153
+ setInputValue(defaultValue.label);
154
+ }
155
+ setFormValue?.(name || "", defaultValue.value);
156
+ }
157
+
158
+ lastSyncedDefaultValue.current = defaultValue.value;
159
+ }, [defaultValue?.value, isMulti, name, setFormValue]);
130
160
 
131
161
  useEffect(() => {
132
- if (!areOptionArraysEqual(defaultValueMulti, valueMulti)) {
133
- setValueMulti(defaultValueMulti);
162
+ if (
163
+ !isMulti ||
164
+ defaultValueMulti.length === 0 ||
165
+ hasManualMultiValue.current
166
+ ) {
167
+ return;
168
+ }
169
+
170
+ const currentValues = valueMulti.map((option) => option.value);
171
+ const previousDefaultValues = lastSyncedDefaultValues.current;
172
+ const canSyncDefault =
173
+ currentValues.length === 0 ||
174
+ areOptionArraysEqual(
175
+ valueMulti,
176
+ previousDefaultValues.map((value) => ({ value, label: "" })),
177
+ );
178
+
179
+ if (canSyncDefault) {
180
+ if (!areOptionArraysEqual(defaultValueMulti, valueMulti)) {
181
+ setValueMulti(defaultValueMulti);
182
+ }
183
+ setFormValue?.(
184
+ name || "",
185
+ defaultValueMulti.map((option) => option.value),
186
+ );
134
187
  }
135
- }, [defaultValueMulti]);
188
+
189
+ lastSyncedDefaultValues.current = defaultValueMulti.map(
190
+ (option) => option.value,
191
+ );
192
+ }, [defaultValueMultiKey, isMulti, name, setFormValue]);
136
193
 
137
194
  useEffect(() => {
138
195
  setFilteredOptions(options);
@@ -156,6 +213,7 @@ export const SelectField = (props: Props) => {
156
213
 
157
214
  const handleSetSingleValue = useCallback(
158
215
  (value?: SelectFieldOption) => {
216
+ hasManualSingleValue.current = true;
159
217
  if (value) {
160
218
  setValue(value);
161
219
  setInputValue(value.label);
@@ -176,6 +234,7 @@ export const SelectField = (props: Props) => {
176
234
 
177
235
  const handleAddMultiValue = useCallback(
178
236
  (value?: SelectFieldOption) => {
237
+ hasManualMultiValue.current = true;
179
238
  if (value) {
180
239
  const existValue = options.some((v) => v.value === value.value);
181
240
  const noEstaYaSeleccionado = !valueMulti.some(