react-core-ts 2.1.44 → 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.
@@ -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
- // No suggestions to match against – notify parent with raw value
553
- onChange({ id: undefined, name: inputValue, from: 4 });
554
- hasUserChangedInputRef.current = false; // Reset flag
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
- onChange({ id: undefined, name: inputValue, from: 4 });
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
- onChange({ id: undefined, name: inputValue, from: 4 });
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
- // No suggestions available - trigger invalid value action
605
- onChange({ id: undefined, name: inputValue, from: 4 });
606
- setInputValue('');
607
- hasUserChangedInputRef.current = false; // Reset flag
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);