@k3-tech/react-kit 0.0.57 → 0.0.58
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/dist/index.js
CHANGED
|
@@ -19603,6 +19603,7 @@ function Autocomplete({
|
|
|
19603
19603
|
initialSelectedOptions,
|
|
19604
19604
|
loadSelected
|
|
19605
19605
|
}, ref) {
|
|
19606
|
+
var _a2;
|
|
19606
19607
|
const isMultiple = !!multiple;
|
|
19607
19608
|
const isControlled = controlledValue !== void 0;
|
|
19608
19609
|
const [internalValue, setInternalValue] = useState(() => {
|
|
@@ -19719,67 +19720,68 @@ function Autocomplete({
|
|
|
19719
19720
|
}
|
|
19720
19721
|
});
|
|
19721
19722
|
}, [currentValue, options, storeOption]);
|
|
19722
|
-
const
|
|
19723
|
-
const
|
|
19724
|
-
useEffect(() => {
|
|
19725
|
-
if (!loadSelected || hasLoadedInitial.current) return;
|
|
19726
|
-
if (!currentValue) return;
|
|
19727
|
-
const values = Array.isArray(currentValue) ? currentValue : [currentValue];
|
|
19728
|
-
if (values.length === 0) return;
|
|
19729
|
-
const missing = values.filter((v) => !labelMapRef.current.has(v));
|
|
19730
|
-
if (missing.length === 0) {
|
|
19731
|
-
hasLoadedInitial.current = true;
|
|
19732
|
-
return;
|
|
19733
|
-
}
|
|
19734
|
-
hasLoadedInitial.current = true;
|
|
19735
|
-
let cancelled = false;
|
|
19736
|
-
loadSelected(missing).then((opts) => {
|
|
19737
|
-
if (!cancelled && opts.length > 0) {
|
|
19738
|
-
opts.forEach(storeOption);
|
|
19739
|
-
setLabelsLoadedCounter((c2) => c2 + 1);
|
|
19740
|
-
}
|
|
19741
|
-
}).catch(() => {
|
|
19742
|
-
});
|
|
19743
|
-
return () => {
|
|
19744
|
-
cancelled = true;
|
|
19745
|
-
};
|
|
19746
|
-
}, [loadSelected, storeOption, currentValue]);
|
|
19723
|
+
const [loadingSelectedValues, setLoadingSelectedValues] = useState(() => /* @__PURE__ */ new Set());
|
|
19724
|
+
const inFlightSelectedRef = useRef(/* @__PURE__ */ new Set());
|
|
19747
19725
|
useEffect(() => {
|
|
19748
|
-
if (!loadSelected
|
|
19726
|
+
if (!loadSelected) return;
|
|
19749
19727
|
const values = Array.isArray(currentValue) ? currentValue : currentValue ? [currentValue] : [];
|
|
19750
|
-
|
|
19728
|
+
if (values.length === 0) return;
|
|
19729
|
+
const missing = values.filter(
|
|
19730
|
+
(v) => !labelMapRef.current.has(v) && !inFlightSelectedRef.current.has(v)
|
|
19731
|
+
);
|
|
19751
19732
|
if (missing.length === 0) return;
|
|
19752
|
-
|
|
19733
|
+
missing.forEach((v) => {
|
|
19734
|
+
inFlightSelectedRef.current.add(v);
|
|
19735
|
+
});
|
|
19736
|
+
setLoadingSelectedValues((prev) => {
|
|
19737
|
+
const next = new Set(prev);
|
|
19738
|
+
missing.forEach((v) => {
|
|
19739
|
+
next.add(v);
|
|
19740
|
+
});
|
|
19741
|
+
return next;
|
|
19742
|
+
});
|
|
19753
19743
|
loadSelected(missing).then((opts) => {
|
|
19754
|
-
|
|
19744
|
+
opts.forEach((opt) => {
|
|
19745
|
+
storeOption(opt);
|
|
19746
|
+
const requested = missing.find(
|
|
19747
|
+
(m2) => String(m2) === String(opt.value)
|
|
19748
|
+
);
|
|
19749
|
+
if (requested !== void 0 && requested !== opt.value) {
|
|
19750
|
+
storeOption({ ...opt, value: requested });
|
|
19751
|
+
}
|
|
19752
|
+
});
|
|
19755
19753
|
}).catch(() => {
|
|
19754
|
+
}).finally(() => {
|
|
19755
|
+
missing.forEach((v) => {
|
|
19756
|
+
inFlightSelectedRef.current.delete(v);
|
|
19757
|
+
});
|
|
19758
|
+
setLoadingSelectedValues((prev) => {
|
|
19759
|
+
const next = new Set(prev);
|
|
19760
|
+
missing.forEach((v) => {
|
|
19761
|
+
next.delete(v);
|
|
19762
|
+
});
|
|
19763
|
+
return next;
|
|
19764
|
+
});
|
|
19756
19765
|
});
|
|
19757
|
-
|
|
19758
|
-
cancelled = true;
|
|
19759
|
-
};
|
|
19760
|
-
}, [currentValue, loadSelected, isOpen, storeOption]);
|
|
19766
|
+
}, [currentValue, loadSelected, storeOption]);
|
|
19761
19767
|
const getLabel2 = useCallback((v) => {
|
|
19762
19768
|
return labelMapRef.current.get(v) ?? String(v);
|
|
19763
19769
|
}, []);
|
|
19764
19770
|
const selectedItems = useMemo(() => {
|
|
19771
|
+
const toItem = (v) => ({
|
|
19772
|
+
value: v,
|
|
19773
|
+
label: getLabel2(v),
|
|
19774
|
+
raw: rawMapRef.current.get(v),
|
|
19775
|
+
loading: loadingSelectedValues.has(v)
|
|
19776
|
+
});
|
|
19765
19777
|
if (!isMultiple) {
|
|
19766
19778
|
if (currentValue === null || currentValue === void 0 || Array.isArray(currentValue))
|
|
19767
19779
|
return [];
|
|
19768
|
-
return [
|
|
19769
|
-
{
|
|
19770
|
-
value: currentValue,
|
|
19771
|
-
label: getLabel2(currentValue),
|
|
19772
|
-
raw: rawMapRef.current.get(currentValue)
|
|
19773
|
-
}
|
|
19774
|
-
];
|
|
19780
|
+
return [toItem(currentValue)];
|
|
19775
19781
|
}
|
|
19776
19782
|
const values = Array.isArray(currentValue) ? currentValue : [];
|
|
19777
|
-
return values.map(
|
|
19778
|
-
|
|
19779
|
-
label: getLabel2(v),
|
|
19780
|
-
raw: rawMapRef.current.get(v)
|
|
19781
|
-
}));
|
|
19782
|
-
}, [currentValue, isMultiple, getLabel2]);
|
|
19783
|
+
return values.map(toItem);
|
|
19784
|
+
}, [currentValue, isMultiple, getLabel2, loadingSelectedValues]);
|
|
19783
19785
|
const handleSelect = useCallback(
|
|
19784
19786
|
(item) => {
|
|
19785
19787
|
if (!item) return;
|
|
@@ -19868,10 +19870,11 @@ function Autocomplete({
|
|
|
19868
19870
|
return searchInput;
|
|
19869
19871
|
}
|
|
19870
19872
|
if (selectedItems.length > 0) {
|
|
19871
|
-
return selectedItems[0].label;
|
|
19873
|
+
return selectedItems[0].loading ? "" : selectedItems[0].label;
|
|
19872
19874
|
}
|
|
19873
19875
|
return "";
|
|
19874
19876
|
}, [isMultiple, isOpen, searchInput, selectedItems]);
|
|
19877
|
+
const isSingleSelectionLoading = !isMultiple && !!((_a2 = selectedItems[0]) == null ? void 0 : _a2.loading);
|
|
19875
19878
|
const { getInputProps, getItemProps, getMenuProps, highlightedIndex } = useCombobox({
|
|
19876
19879
|
items,
|
|
19877
19880
|
itemToString: (item) => (item == null ? void 0 : item.label) ?? "",
|
|
@@ -19937,7 +19940,7 @@ function Autocomplete({
|
|
|
19937
19940
|
variant: chipVariant,
|
|
19938
19941
|
className: cn$1("gap-1", chipClassName),
|
|
19939
19942
|
children: [
|
|
19940
|
-
/* @__PURE__ */ jsx("span", { className: "max-w-[100px] truncate", children: item.label }),
|
|
19943
|
+
/* @__PURE__ */ jsx("span", { className: "max-w-[100px] truncate", children: item.loading ? /* @__PURE__ */ jsx(LoaderCircle, { className: "h-3 w-3 animate-spin" }) : item.label }),
|
|
19941
19944
|
/* @__PURE__ */ jsx(
|
|
19942
19945
|
"button",
|
|
19943
19946
|
{
|
|
@@ -19978,7 +19981,8 @@ function Autocomplete({
|
|
|
19978
19981
|
}
|
|
19979
19982
|
),
|
|
19980
19983
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 shrink-0", children: [
|
|
19981
|
-
|
|
19984
|
+
isSingleSelectionLoading && /* @__PURE__ */ jsx(LoaderCircle, { className: "h-4 w-4 animate-spin opacity-50" }),
|
|
19985
|
+
showClearButton && !isSingleSelectionLoading && /* @__PURE__ */ jsx(
|
|
19982
19986
|
"button",
|
|
19983
19987
|
{
|
|
19984
19988
|
type: "button",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Autocomplete.d.ts","sourceRoot":"","sources":["../../../../src/kit/components/autocomplete/Autocomplete.tsx"],"names":[],"mappings":"AAEA,OAAO,EAEL,YAAY,EACZ,aAAa,EAOd,MAAM,OAAO,CAAC;AASf,OAAO,KAAK,EACV,mBAAmB,EAEnB,gBAAgB,EAChB,kBAAkB,EACnB,MAAM,SAAS,CAAC;AAEjB,MAAM,MAAM,iBAAiB,CAAC,CAAC,GAAG,OAAO,IAAI;IAC3C,IAAI,EAAE,gBAAgB,CAAC;IACvB,OAAO,CAAC,EAAE,kBAAkB,CAAC,CAAC,CAAC,EAAE,CAAC;IAClC,OAAO,CAAC,EAAE,mBAAmB,CAAC,CAAC,CAAC,CAAC;IACjC,aAAa,CAAC,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC,CAAC;IACvE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC;IACxD,QAAQ,CAAC,EAAE,CACT,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,EACtD,QAAQ,EAAE,kBAAkB,CAAC,CAAC,CAAC,GAAG,kBAAkB,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,EAChE,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,GAAG,IAAI,KACjB,IAAI,CAAC;IACV,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,SAAS,GAAG,WAAW,GAAG,aAAa,GAAG,SAAS,CAAC;IAClE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,CACb,MAAM,EAAE,kBAAkB,CAAC,CAAC,CAAC,EAC7B,QAAQ,EAAE,OAAO,KACd,KAAK,CAAC,SAAS,CAAC;IACrB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC;IAC/D,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,sBAAsB,CAAC,EAAE,kBAAkB,CAAC,CAAC,CAAC,GAAG,kBAAkB,CAAC,CAAC,CAAC,EAAE,CAAC;IACzE,YAAY,CAAC,EAAE,CACb,MAAM,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,KAC3B,OAAO,CAAC,kBAAkB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;CACvC,CAAC;AAIF,wBAAgB,YAAY,CAAC,CAAC,GAAG,OAAO,EAAE,EACxC,IAAe,EACf,OAAY,EACZ,OAAO,EACP,aAAa,EACb,QAA4B,EAC5B,KAAK,EAAE,eAAe,EACtB,QAAQ,EACR,WAAmC,EACnC,QAAgB,EAChB,QAAgB,EAChB,SAAS,EACT,KAAK,EACL,WAAyB,EACzB,aAAa,EACb,SAA8B,EAC9B,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,gBAAwB,EACxB,SAAgB,EAChB,sBAAsB,EACtB,YAAY,GACb,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,GAAG,
|
|
1
|
+
{"version":3,"file":"Autocomplete.d.ts","sourceRoot":"","sources":["../../../../src/kit/components/autocomplete/Autocomplete.tsx"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -249,65 +249,91 @@ export function Autocomplete<T = unknown>({
|
|
|
249
249
|
});
|
|
250
250
|
}, [currentValue, options, storeOption]);
|
|
251
251
|
|
|
252
|
-
//
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
252
|
+
// Tracks selected values currently being resolved via loadSelected. Doubles
|
|
253
|
+
// as the loading indicator and the recompute trigger: removing a value on
|
|
254
|
+
// settle re-renders, and by then its label is already stored in labelMapRef.
|
|
255
|
+
const [loadingSelectedValues, setLoadingSelectedValues] = useState<
|
|
256
|
+
Set<string | number>
|
|
257
|
+
>(() => new Set());
|
|
258
|
+
|
|
259
|
+
// Values with a loadSelected fetch currently in flight. Prevents duplicate
|
|
260
|
+
// concurrent requests when the parent re-renders with a fresh currentValue
|
|
261
|
+
// reference (common in multiple mode) before the first fetch resolves.
|
|
262
|
+
const inFlightSelectedRef = useRef<Set<string | number>>(new Set());
|
|
263
|
+
|
|
264
|
+
// Load selected labels via loadSelected whenever currentValue changes.
|
|
265
|
+
// Covers mount, programmatic value changes (even while the dropdown is
|
|
266
|
+
// closed), and dropdown opens — anything not already cached or resolvable
|
|
267
|
+
// from the local options array.
|
|
256
268
|
useEffect(() => {
|
|
257
|
-
|
|
258
|
-
if (!loadSelected || hasLoadedInitial.current) return;
|
|
259
|
-
if (!currentValue) return;
|
|
260
|
-
|
|
261
|
-
const values = Array.isArray(currentValue) ? currentValue : [currentValue];
|
|
262
|
-
if (values.length === 0) return;
|
|
263
|
-
|
|
264
|
-
// Check if any values are missing labels
|
|
265
|
-
const missing = values.filter((v) => !labelMapRef.current.has(v));
|
|
266
|
-
if (missing.length === 0) {
|
|
267
|
-
hasLoadedInitial.current = true;
|
|
268
|
-
return;
|
|
269
|
-
}
|
|
269
|
+
if (!loadSelected) return;
|
|
270
270
|
|
|
271
|
-
hasLoadedInitial.current = true;
|
|
272
|
-
let cancelled = false;
|
|
273
|
-
loadSelected(missing)
|
|
274
|
-
.then((opts) => {
|
|
275
|
-
if (!cancelled && opts.length > 0) {
|
|
276
|
-
opts.forEach(storeOption);
|
|
277
|
-
// Only trigger re-render if we actually stored something
|
|
278
|
-
setLabelsLoadedCounter((c) => c + 1);
|
|
279
|
-
}
|
|
280
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
281
|
-
})
|
|
282
|
-
.catch(() => { });
|
|
283
|
-
return () => {
|
|
284
|
-
cancelled = true;
|
|
285
|
-
};
|
|
286
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
287
|
-
}, [loadSelected, storeOption, currentValue]); // Only run once on mount - uses closure values
|
|
288
|
-
|
|
289
|
-
// Load selected labels if missing when dropdown opens
|
|
290
|
-
useEffect(() => {
|
|
291
|
-
if (!loadSelected || !isOpen) return;
|
|
292
271
|
const values = Array.isArray(currentValue)
|
|
293
272
|
? currentValue
|
|
294
273
|
: currentValue
|
|
295
274
|
? [currentValue]
|
|
296
275
|
: [];
|
|
297
|
-
|
|
276
|
+
if (values.length === 0) return;
|
|
277
|
+
|
|
278
|
+
// Only fetch values whose labels we don't already have and aren't already
|
|
279
|
+
// being fetched.
|
|
280
|
+
const missing = values.filter(
|
|
281
|
+
(v) => !labelMapRef.current.has(v) && !inFlightSelectedRef.current.has(v),
|
|
282
|
+
);
|
|
298
283
|
if (missing.length === 0) return;
|
|
299
284
|
|
|
300
|
-
|
|
285
|
+
missing.forEach((v) => {
|
|
286
|
+
inFlightSelectedRef.current.add(v);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
// Mark missing values as loading so the UI shows a spinner instead of
|
|
290
|
+
// flashing the raw value while the fetch is in flight.
|
|
291
|
+
setLoadingSelectedValues((prev) => {
|
|
292
|
+
const next = new Set(prev);
|
|
293
|
+
missing.forEach((v) => {
|
|
294
|
+
next.add(v);
|
|
295
|
+
});
|
|
296
|
+
return next;
|
|
297
|
+
});
|
|
298
|
+
|
|
301
299
|
loadSelected(missing)
|
|
302
300
|
.then((opts) => {
|
|
303
|
-
if
|
|
301
|
+
// Store unconditionally — these are ref writes, safe even if this
|
|
302
|
+
// effect run was superseded. (Guarding on a "cancelled" flag here
|
|
303
|
+
// dropped labels in multiple mode, where currentValue is a fresh
|
|
304
|
+
// array reference each render and re-runs/cancels the effect.)
|
|
305
|
+
opts.forEach((opt) => {
|
|
306
|
+
storeOption(opt);
|
|
307
|
+
// Align the returned option back to the originally-requested value
|
|
308
|
+
// so getLabel finds it even when loadSelected returns a different
|
|
309
|
+
// value type (e.g. a GraphQL ID serialized as a string while
|
|
310
|
+
// currentValue holds a number). Match loosely by string, then store
|
|
311
|
+
// under the exact requested value.
|
|
312
|
+
const requested = missing.find(
|
|
313
|
+
(m) => String(m) === String(opt.value),
|
|
314
|
+
);
|
|
315
|
+
if (requested !== undefined && requested !== opt.value) {
|
|
316
|
+
storeOption({ ...opt, value: requested });
|
|
317
|
+
}
|
|
318
|
+
});
|
|
304
319
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
305
320
|
})
|
|
306
|
-
.catch(() => { })
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
321
|
+
.catch(() => { })
|
|
322
|
+
.finally(() => {
|
|
323
|
+
missing.forEach((v) => {
|
|
324
|
+
inFlightSelectedRef.current.delete(v);
|
|
325
|
+
});
|
|
326
|
+
// Clear the loading flags regardless of outcome; the resulting state
|
|
327
|
+
// change re-renders and recomputes selectedItems from labelMapRef.
|
|
328
|
+
setLoadingSelectedValues((prev) => {
|
|
329
|
+
const next = new Set(prev);
|
|
330
|
+
missing.forEach((v) => {
|
|
331
|
+
next.delete(v);
|
|
332
|
+
});
|
|
333
|
+
return next;
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
}, [currentValue, loadSelected, storeOption]);
|
|
311
337
|
|
|
312
338
|
// Get label helper
|
|
313
339
|
const getLabel = useCallback((v: string | number) => {
|
|
@@ -316,6 +342,12 @@ export function Autocomplete<T = unknown>({
|
|
|
316
342
|
|
|
317
343
|
// Selected items for display
|
|
318
344
|
const selectedItems = useMemo(() => {
|
|
345
|
+
const toItem = (v: string | number) => ({
|
|
346
|
+
value: v,
|
|
347
|
+
label: getLabel(v),
|
|
348
|
+
raw: rawMapRef.current.get(v),
|
|
349
|
+
loading: loadingSelectedValues.has(v),
|
|
350
|
+
});
|
|
319
351
|
if (!isMultiple) {
|
|
320
352
|
if (
|
|
321
353
|
currentValue === null ||
|
|
@@ -323,22 +355,14 @@ export function Autocomplete<T = unknown>({
|
|
|
323
355
|
Array.isArray(currentValue)
|
|
324
356
|
)
|
|
325
357
|
return [];
|
|
326
|
-
return [
|
|
327
|
-
{
|
|
328
|
-
value: currentValue,
|
|
329
|
-
label: getLabel(currentValue),
|
|
330
|
-
raw: rawMapRef.current.get(currentValue),
|
|
331
|
-
},
|
|
332
|
-
];
|
|
358
|
+
return [toItem(currentValue)];
|
|
333
359
|
}
|
|
334
360
|
const values = Array.isArray(currentValue) ? currentValue : [];
|
|
335
|
-
return values.map(
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
341
|
-
}, [currentValue, isMultiple, getLabel]); // labelsLoadedCounter triggers re-compute when loadSelected completes
|
|
361
|
+
return values.map(toItem);
|
|
362
|
+
// loadingSelectedValues also drives the recompute when loadSelected
|
|
363
|
+
// settles: clearing a value's flag re-renders, and its label is in
|
|
364
|
+
// labelMapRef (a ref React can't track) by then.
|
|
365
|
+
}, [currentValue, isMultiple, getLabel, loadingSelectedValues]);
|
|
342
366
|
|
|
343
367
|
// Handle selection
|
|
344
368
|
const handleSelect = useCallback(
|
|
@@ -450,13 +474,18 @@ export function Autocomplete<T = unknown>({
|
|
|
450
474
|
if (isMultiple || isOpen) {
|
|
451
475
|
return searchInput;
|
|
452
476
|
}
|
|
453
|
-
// In single mode when closed, show selected item label
|
|
477
|
+
// In single mode when closed, show selected item label. While its label
|
|
478
|
+
// is still resolving, show nothing (a spinner renders in the trailing
|
|
479
|
+
// icons) so we don't flash the raw value.
|
|
454
480
|
if (selectedItems.length > 0) {
|
|
455
|
-
return selectedItems[0].label;
|
|
481
|
+
return selectedItems[0].loading ? '' : selectedItems[0].label;
|
|
456
482
|
}
|
|
457
483
|
return '';
|
|
458
484
|
}, [isMultiple, isOpen, searchInput, selectedItems]);
|
|
459
485
|
|
|
486
|
+
// Whether the single-mode selection is still resolving its label
|
|
487
|
+
const isSingleSelectionLoading = !isMultiple && !!selectedItems[0]?.loading;
|
|
488
|
+
|
|
460
489
|
// Downshift
|
|
461
490
|
const { getInputProps, getItemProps, getMenuProps, highlightedIndex } =
|
|
462
491
|
useCombobox({
|
|
@@ -542,7 +571,13 @@ export function Autocomplete<T = unknown>({
|
|
|
542
571
|
variant={chipVariant}
|
|
543
572
|
className={cn('gap-1', chipClassName)}
|
|
544
573
|
>
|
|
545
|
-
<span className="max-w-[100px] truncate">
|
|
574
|
+
<span className="max-w-[100px] truncate">
|
|
575
|
+
{item.loading ? (
|
|
576
|
+
<Loader2 className="h-3 w-3 animate-spin" />
|
|
577
|
+
) : (
|
|
578
|
+
item.label
|
|
579
|
+
)}
|
|
580
|
+
</span>
|
|
546
581
|
<button
|
|
547
582
|
type="button"
|
|
548
583
|
onClick={(e) => {
|
|
@@ -583,7 +618,10 @@ export function Autocomplete<T = unknown>({
|
|
|
583
618
|
)}
|
|
584
619
|
/>
|
|
585
620
|
<div className="flex items-center gap-2 shrink-0">
|
|
586
|
-
{
|
|
621
|
+
{isSingleSelectionLoading && (
|
|
622
|
+
<Loader2 className="h-4 w-4 animate-spin opacity-50" />
|
|
623
|
+
)}
|
|
624
|
+
{showClearButton && !isSingleSelectionLoading && (
|
|
587
625
|
<button
|
|
588
626
|
type="button"
|
|
589
627
|
onClick={(e) => {
|