react-core-ts 2.1.45 → 2.1.46
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/AutoCompleteWithSelectedList.js +4 -4
- package/dist/AutoCompleteWithSelectedList.js.map +1 -1
- package/dist/AutoCompleteWithTabFilter.js +4 -4
- package/dist/AutoCompleteWithTabFilter.js.map +1 -1
- package/dist/ReactAutoComplete.d.ts +2 -0
- package/dist/ReactAutoComplete.js +79 -30
- package/dist/ReactAutoComplete.js.map +1 -1
- package/dist/ReactAutoCompleteDropdown.js +25 -1
- package/dist/ReactAutoCompleteDropdown.js.map +1 -1
- package/docs/App.tsx +17 -0
- package/package.json +1 -1
- package/src/AutoCompleteWithSelectedList.tsx +9 -4
- package/src/AutoCompleteWithTabFilter.tsx +5 -4
- package/src/ReactAutoComplete.tsx +60 -13
- package/src/ReactAutoCompleteDropdown.tsx +22 -0
|
@@ -881,7 +881,7 @@ const AutoCompleteWithTabFilter = forwardRef<
|
|
|
881
881
|
title={String(item?.[desc])}
|
|
882
882
|
enabled={String(item?.[desc])?.length > textCount}
|
|
883
883
|
>
|
|
884
|
-
<div key={item.id} className="selected-item">
|
|
884
|
+
<div key={item.id} className="selected-item qbs-selected-chip">
|
|
885
885
|
{String(item?.[desc])?.length > textCount
|
|
886
886
|
? `${String(item?.[desc]).substring(0, textCount)}...`
|
|
887
887
|
: String(item?.[desc])}
|
|
@@ -902,7 +902,7 @@ const AutoCompleteWithTabFilter = forwardRef<
|
|
|
902
902
|
<div className="selected-items-container">
|
|
903
903
|
<Tooltip title={tooltipContent} enabled={true}>
|
|
904
904
|
<div
|
|
905
|
-
className={`selected-item-more qbs-rounded-full qbs-min-h-6 qbs-min-w-6 qbs-p-1 ${dropdown ? 'qbs-cursor-pointer' : ''
|
|
905
|
+
className={`selected-item-more qbs-selected-chip-more qbs-rounded-full qbs-min-h-6 qbs-min-w-6 qbs-p-1 ${dropdown ? 'qbs-cursor-pointer' : ''
|
|
906
906
|
}`}
|
|
907
907
|
onClick={() => {
|
|
908
908
|
handleShowAllSelected(dropdown);
|
|
@@ -1120,7 +1120,8 @@ const AutoCompleteWithTabFilter = forwardRef<
|
|
|
1120
1120
|
return (
|
|
1121
1121
|
<div
|
|
1122
1122
|
id={id ? `selected-list-${id}` : `selected-list-${name}`}
|
|
1123
|
-
className={fullWidth ? 'fullWidth' : 'autoWidth'}
|
|
1123
|
+
className={`${fullWidth ? 'fullWidth' : 'autoWidth'} ${selectedItems?.length > 0 ? 'qbs-has-selected' : ''}`}
|
|
1124
|
+
data-has-value={selectedItems?.length > 0 ? 'true' : 'false'}
|
|
1124
1125
|
ref={dropdownRef}
|
|
1125
1126
|
>
|
|
1126
1127
|
{label && (
|
|
@@ -1236,7 +1237,7 @@ const AutoCompleteWithTabFilter = forwardRef<
|
|
|
1236
1237
|
}
|
|
1237
1238
|
}}
|
|
1238
1239
|
onClick={() => handleOnClick()}
|
|
1239
|
-
className={generateClassName()}
|
|
1240
|
+
className={`${generateClassName()} ${selectedItems?.length > 0 ? 'qbs-input-has-selected' : ''}`}
|
|
1240
1241
|
placeholder={
|
|
1241
1242
|
selectedItems?.length > 0
|
|
1242
1243
|
? !tabInlineSearch && searchValue
|
|
@@ -52,6 +52,8 @@ interface AutoSuggestionInputProps {
|
|
|
52
52
|
labelTitle?: string;
|
|
53
53
|
isModern?: boolean;
|
|
54
54
|
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xxs';
|
|
55
|
+
/** When true, typed text that is not in the dropdown is kept and committed as the value. */
|
|
56
|
+
allowCustomValue?: boolean;
|
|
55
57
|
}
|
|
56
58
|
|
|
57
59
|
const ModernAutoComplete: React.FC<AutoSuggestionInputProps> = ({
|
|
@@ -87,6 +89,7 @@ const ModernAutoComplete: React.FC<AutoSuggestionInputProps> = ({
|
|
|
87
89
|
labelTitle,
|
|
88
90
|
isModern = true,
|
|
89
91
|
size = 'md',
|
|
92
|
+
allowCustomValue = false,
|
|
90
93
|
}) => {
|
|
91
94
|
const [inputValue, setInputValue] = useState<any>(value?.name ?? '');
|
|
92
95
|
const [isHovered, setIsHovered] = useState<boolean>(false);
|
|
@@ -110,6 +113,8 @@ const ModernAutoComplete: React.FC<AutoSuggestionInputProps> = ({
|
|
|
110
113
|
const scrollContainerRef = useRef<HTMLUListElement | null>(null);
|
|
111
114
|
const isSelectingRef = useRef(false);
|
|
112
115
|
const hasUserChangedInputRef = useRef(false); // Track if user has actually typed/changed input
|
|
116
|
+
const requestIdRef = useRef(0);
|
|
117
|
+
const lastQueryRef = useRef<string | null>(null);
|
|
113
118
|
const [dropPosition, setDropPosition] = useState<any>({
|
|
114
119
|
top: 0,
|
|
115
120
|
left: 0,
|
|
@@ -173,6 +178,7 @@ const ModernAutoComplete: React.FC<AutoSuggestionInputProps> = ({
|
|
|
173
178
|
hasUserChangedInputRef.current = true; // Mark that user has changed the input
|
|
174
179
|
|
|
175
180
|
setInputValue(value);
|
|
181
|
+
discardStaleSuggestions(value);
|
|
176
182
|
handleValChange(value);
|
|
177
183
|
if (!value) {
|
|
178
184
|
setInputValue('');
|
|
@@ -242,15 +248,32 @@ const ModernAutoComplete: React.FC<AutoSuggestionInputProps> = ({
|
|
|
242
248
|
setSuggestions(filteredData);
|
|
243
249
|
}
|
|
244
250
|
};
|
|
251
|
+
// Results from a previous search must not show while a new, unrelated search is pending.
|
|
252
|
+
const discardStaleSuggestions = (value: string) => {
|
|
253
|
+
if (type !== 'auto_complete' || isStaticList) return;
|
|
254
|
+
const next = value.trim().toLowerCase();
|
|
255
|
+
const last = lastQueryRef.current;
|
|
256
|
+
if (!next || last === null || !next.startsWith(last)) {
|
|
257
|
+
requestIdRef.current += 1;
|
|
258
|
+
lastQueryRef.current = null;
|
|
259
|
+
setSuggestions([]);
|
|
260
|
+
setIsLoading(false);
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
|
|
245
264
|
const handlePickSuggestions = async (value?: string) => {
|
|
246
265
|
if (getData) {
|
|
266
|
+
const requestId = ++requestIdRef.current;
|
|
247
267
|
setHasFetchedSuggestions(false);
|
|
248
268
|
setIsLoading(true);
|
|
249
269
|
try {
|
|
250
270
|
const fetchedSuggestions = await getData?.(value);
|
|
271
|
+
if (requestId !== requestIdRef.current) return;
|
|
251
272
|
setSuggestions(
|
|
252
273
|
Array.isArray(fetchedSuggestions) ? fetchedSuggestions : [],
|
|
253
274
|
);
|
|
275
|
+
lastQueryRef.current =
|
|
276
|
+
!value || value === '*' ? '' : value.trim().toLowerCase();
|
|
254
277
|
setHasFetchedSuggestions(true);
|
|
255
278
|
setIsLoading(false);
|
|
256
279
|
setTimeout(() => {
|
|
@@ -259,6 +282,7 @@ const ModernAutoComplete: React.FC<AutoSuggestionInputProps> = ({
|
|
|
259
282
|
}
|
|
260
283
|
}, 100);
|
|
261
284
|
} catch (error) {
|
|
285
|
+
if (requestId !== requestIdRef.current) return;
|
|
262
286
|
setHasFetchedSuggestions(false);
|
|
263
287
|
setIsLoading(false);
|
|
264
288
|
}
|
|
@@ -530,6 +554,21 @@ const ModernAutoComplete: React.FC<AutoSuggestionInputProps> = ({
|
|
|
530
554
|
// const getPosition = () => {
|
|
531
555
|
// return 'bottom'
|
|
532
556
|
// }
|
|
557
|
+
const commitCustomValue = (typedValue: string) => {
|
|
558
|
+
const trimmed = typedValue.toString().trim();
|
|
559
|
+
if (!trimmed) return;
|
|
560
|
+
onChange({ id: undefined, name: trimmed, from: 5 });
|
|
561
|
+
setInputValue(trimmed);
|
|
562
|
+
setDropOpen(false);
|
|
563
|
+
hasUserChangedInputRef.current = false;
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
const rejectUnlistedValue = (typedValue: string) => {
|
|
567
|
+
onChange({ id: undefined, name: typedValue, from: 4 });
|
|
568
|
+
setInputValue('');
|
|
569
|
+
hasUserChangedInputRef.current = false;
|
|
570
|
+
};
|
|
571
|
+
|
|
533
572
|
const selectExactMatchFromSuggestions = () => {
|
|
534
573
|
// If user is clicking on dropdown, don't execute exact match logic
|
|
535
574
|
if (isSelectingRef.current) {
|
|
@@ -549,9 +588,13 @@ const ModernAutoComplete: React.FC<AutoSuggestionInputProps> = ({
|
|
|
549
588
|
}
|
|
550
589
|
|
|
551
590
|
if (!filteredData || filteredData.length === 0) {
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
591
|
+
if (allowCustomValue) {
|
|
592
|
+
commitCustomValue(inputValue);
|
|
593
|
+
} else {
|
|
594
|
+
// No suggestions to match against – notify parent with raw value
|
|
595
|
+
onChange({ id: undefined, name: inputValue, from: 4 });
|
|
596
|
+
hasUserChangedInputRef.current = false; // Reset flag
|
|
597
|
+
}
|
|
555
598
|
return;
|
|
556
599
|
}
|
|
557
600
|
|
|
@@ -565,10 +608,11 @@ const ModernAutoComplete: React.FC<AutoSuggestionInputProps> = ({
|
|
|
565
608
|
if (matchIndex >= 0) {
|
|
566
609
|
const match = filteredData[matchIndex];
|
|
567
610
|
handleSuggestionClick(match, matchIndex);
|
|
611
|
+
} else if (allowCustomValue) {
|
|
612
|
+
commitCustomValue(inputValue);
|
|
568
613
|
} else {
|
|
569
|
-
// No exact match – notify parent with typed value
|
|
570
|
-
|
|
571
|
-
setInputValue('');
|
|
614
|
+
// No exact match – notify parent with typed value and clear input
|
|
615
|
+
rejectUnlistedValue(inputValue);
|
|
572
616
|
}
|
|
573
617
|
hasUserChangedInputRef.current = false; // Reset flag after handling
|
|
574
618
|
};
|
|
@@ -590,21 +634,24 @@ const ModernAutoComplete: React.FC<AutoSuggestionInputProps> = ({
|
|
|
590
634
|
// Exact match found - select it
|
|
591
635
|
isSelectingRef.current = true;
|
|
592
636
|
handleSuggestionClick(filteredData[exactMatchIndex], exactMatchIndex);
|
|
637
|
+
} else if (allowCustomValue) {
|
|
638
|
+
// Accept typed value that is not in the list
|
|
639
|
+
commitCustomValue(inputValue);
|
|
593
640
|
} else if (dropOpen && filteredData[selectedIndex]) {
|
|
594
641
|
// No exact match but dropdown is open and has highlighted item - select highlighted item
|
|
595
642
|
isSelectingRef.current = true;
|
|
596
643
|
handleSuggestionClick(filteredData[selectedIndex], selectedIndex);
|
|
597
644
|
} else {
|
|
598
645
|
// No exact match and no highlighted item - trigger invalid value action
|
|
599
|
-
|
|
600
|
-
setInputValue('');
|
|
601
|
-
hasUserChangedInputRef.current = false; // Reset flag
|
|
646
|
+
rejectUnlistedValue(inputValue);
|
|
602
647
|
}
|
|
603
648
|
} else if (inputValue && (!filteredData || filteredData.length === 0 ) && hasUserChangedInputRef.current) {
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
649
|
+
if (allowCustomValue) {
|
|
650
|
+
commitCustomValue(inputValue);
|
|
651
|
+
} else {
|
|
652
|
+
// No suggestions available - trigger invalid value action
|
|
653
|
+
rejectUnlistedValue(inputValue);
|
|
654
|
+
}
|
|
608
655
|
}
|
|
609
656
|
// Don't prevent default - allow Tab to move focus
|
|
610
657
|
};
|
|
@@ -113,6 +113,8 @@ const ModernAutoCompleteDropdown: React.FC<AutoSuggestionInputProps> = ({
|
|
|
113
113
|
|
|
114
114
|
const isSelectingRef = useRef(false);
|
|
115
115
|
const hasUserChangedInputRef = useRef(false); // Track if user has actually typed/changed input
|
|
116
|
+
const requestIdRef = useRef(0);
|
|
117
|
+
const lastQueryRef = useRef<string | null>(null);
|
|
116
118
|
const [dropPosition, setDropPosition] = useState<any>({
|
|
117
119
|
top: 0,
|
|
118
120
|
left: 0,
|
|
@@ -176,6 +178,7 @@ const ModernAutoCompleteDropdown: React.FC<AutoSuggestionInputProps> = ({
|
|
|
176
178
|
hasUserChangedInputRef.current = true; // Mark that user has changed the input
|
|
177
179
|
|
|
178
180
|
setInputValue(value);
|
|
181
|
+
discardStaleSuggestions(value);
|
|
179
182
|
handleValChange(value);
|
|
180
183
|
if (!value) {
|
|
181
184
|
setInputValue('');
|
|
@@ -247,17 +250,35 @@ const ModernAutoCompleteDropdown: React.FC<AutoSuggestionInputProps> = ({
|
|
|
247
250
|
setSuggestions(filteredData);
|
|
248
251
|
}
|
|
249
252
|
};
|
|
253
|
+
// Results from a previous search must not show while a new, unrelated search is pending.
|
|
254
|
+
const discardStaleSuggestions = (value: string) => {
|
|
255
|
+
if (type !== 'auto_complete' || isStaticList) return;
|
|
256
|
+
const next = value.trim().toLowerCase();
|
|
257
|
+
const last = lastQueryRef.current;
|
|
258
|
+
if (!next || last === null || !next.startsWith(last)) {
|
|
259
|
+
requestIdRef.current += 1;
|
|
260
|
+
lastQueryRef.current = null;
|
|
261
|
+
setSuggestions([]);
|
|
262
|
+
setIsLoading(false);
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
|
|
250
266
|
const handlePickSuggestions = async (value?: string) => {
|
|
251
267
|
if (getData) {
|
|
268
|
+
const requestId = ++requestIdRef.current;
|
|
252
269
|
setIsLoading(true);
|
|
253
270
|
try {
|
|
254
271
|
const fetchedSuggestions = await getData?.(value);
|
|
272
|
+
if (requestId !== requestIdRef.current) return;
|
|
255
273
|
setSuggestions(fetchedSuggestions);
|
|
274
|
+
lastQueryRef.current =
|
|
275
|
+
!value || value === '*' ? '' : value.trim().toLowerCase();
|
|
256
276
|
setIsLoading(false);
|
|
257
277
|
if (autoDropdown) {
|
|
258
278
|
setRefetchData(true);
|
|
259
279
|
}
|
|
260
280
|
} catch (error) {
|
|
281
|
+
if (requestId !== requestIdRef.current) return;
|
|
261
282
|
setIsLoading(false);
|
|
262
283
|
}
|
|
263
284
|
}
|
|
@@ -302,6 +323,7 @@ const ModernAutoCompleteDropdown: React.FC<AutoSuggestionInputProps> = ({
|
|
|
302
323
|
data?.push(element);
|
|
303
324
|
});
|
|
304
325
|
setSuggestions(fetchedSuggestions);
|
|
326
|
+
lastQueryRef.current = '';
|
|
305
327
|
setIsLoading(false);
|
|
306
328
|
if (autoDropdown) {
|
|
307
329
|
setRefetchData(false);
|