react-core-ts 2.1.4 → 2.1.6

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.
@@ -107,6 +107,7 @@ const ModernAutoComplete: React.FC<AutoSuggestionInputProps> = ({
107
107
  const itemRefs = useRef<(HTMLLIElement | null)[]>([]);
108
108
  const scrollContainerRef = useRef<HTMLUListElement | null>(null);
109
109
  const isSelectingRef = useRef(false);
110
+ const hasUserChangedInputRef = useRef(false); // Track if user has actually typed/changed input
110
111
  const [dropPosition, setDropPosition] = useState<any>({
111
112
  top: 0,
112
113
  left: 0,
@@ -119,11 +120,11 @@ const ModernAutoComplete: React.FC<AutoSuggestionInputProps> = ({
119
120
  param1?: string | number,
120
121
  param2?: string | number,
121
122
  param3?: string | number,
122
- param4?: string | number
123
+ param4?: string | number,
123
124
  ): boolean => {
124
125
  const checkIncludesIfExists = (
125
126
  mainString?: string | number,
126
- substring?: string
127
+ substring?: string,
127
128
  ) => {
128
129
  return substring &&
129
130
  mainString &&
@@ -137,7 +138,7 @@ const ModernAutoComplete: React.FC<AutoSuggestionInputProps> = ({
137
138
  };
138
139
  const checkArrayContains = (
139
140
  strArray: string[] | undefined,
140
- param: string
141
+ param: string,
141
142
  ): boolean => strArray?.includes(param) ?? false;
142
143
 
143
144
  return (
@@ -166,6 +167,7 @@ const ModernAutoComplete: React.FC<AutoSuggestionInputProps> = ({
166
167
  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
167
168
  const { value } = event.target;
168
169
  timerRef.current = 0;
170
+ hasUserChangedInputRef.current = true; // Mark that user has changed the input
169
171
 
170
172
  setInputValue(value);
171
173
  handleValChange(value);
@@ -194,9 +196,9 @@ const ModernAutoComplete: React.FC<AutoSuggestionInputProps> = ({
194
196
  timerRef.current = 1;
195
197
  }, 200);
196
198
  },
197
- isStaticList ? 0 : 1000
199
+ isStaticList ? 0 : 1000,
198
200
  ),
199
- []
201
+ [],
200
202
  );
201
203
  const handleDropData = (value?: string) => {
202
204
  if (type === 'auto_complete') {
@@ -210,8 +212,8 @@ const ModernAutoComplete: React.FC<AutoSuggestionInputProps> = ({
210
212
  value,
211
213
  item.param1,
212
214
  item.param2,
213
- item.param3
214
- )
215
+ item.param3,
216
+ ),
215
217
  )
216
218
  : data;
217
219
  setSuggestions(filteredData);
@@ -230,8 +232,8 @@ const ModernAutoComplete: React.FC<AutoSuggestionInputProps> = ({
230
232
  value,
231
233
  item.param1,
232
234
  item.param2,
233
- item.param3
234
- )
235
+ item.param3,
236
+ ),
235
237
  )
236
238
  : data;
237
239
  setSuggestions(filteredData);
@@ -331,6 +333,7 @@ const ModernAutoComplete: React.FC<AutoSuggestionInputProps> = ({
331
333
  }, [disabled]);
332
334
  const handleSuggestionClick = (suggestion: valueProps, index: number) => {
333
335
  isSelectingRef.current = true;
336
+ hasUserChangedInputRef.current = false; // Reset flag when selecting from dropdown
334
337
  onChange({
335
338
  ...suggestion,
336
339
  id: suggestion?.id,
@@ -369,7 +372,7 @@ const ModernAutoComplete: React.FC<AutoSuggestionInputProps> = ({
369
372
  mainElement?.addEventListener('scroll', handleScroll);
370
373
 
371
374
  const gridElements = document.querySelectorAll(
372
- '.k-grid-content, .overflow-auto, .overflow-y-auto, .overflow-x-auto'
375
+ '.k-grid-content, .overflow-auto, .overflow-y-auto, .overflow-x-auto',
373
376
  );
374
377
  gridElements.forEach((gridElement: any) => {
375
378
  gridElement.addEventListener('scroll', handleScroll);
@@ -394,11 +397,13 @@ const ModernAutoComplete: React.FC<AutoSuggestionInputProps> = ({
394
397
  setDropOpen(false);
395
398
  setInputValue('');
396
399
  onChange({ id: undefined, name: '', from: 3 });
400
+ hasUserChangedInputRef.current = false; // Reset flag when clearing
397
401
  onLabelClick();
398
402
  };
399
403
 
400
404
  useEffect(() => {
401
405
  setInputValue(value?.name ?? '');
406
+ hasUserChangedInputRef.current = false; // Reset flag when value prop changes (e.g., coming back to page)
402
407
  }, [value?.name]);
403
408
 
404
409
  useEffect(() => {
@@ -417,7 +422,7 @@ const ModernAutoComplete: React.FC<AutoSuggestionInputProps> = ({
417
422
  return errMsg;
418
423
  };
419
424
  const generateClassName = (
420
- type: 'input' | 'label' | 'message' | 'adorement'
425
+ type: 'input' | 'label' | 'message' | 'adorement',
421
426
  ): string => {
422
427
  let className = propsClassName;
423
428
  switch (type) {
@@ -446,16 +451,16 @@ const ModernAutoComplete: React.FC<AutoSuggestionInputProps> = ({
446
451
  ? 'cursor-pointer'
447
452
  : 'cursor-text peer-focus:cursor-pointer'
448
453
  } ${
449
- isDisabled && !checkIsEmptyField()
450
- ? 'disabled-input-label-bg'
451
- : !isDisabled || !checkIsEmptyField()
452
- ? 'active-input-label-bg'
453
- : ''
454
- } absolute duration-300 transform -translate-y-4 top-2 z-1 origin-[0] px-0 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:top-1/2 peer-focus:top-2 peer-focus:-translate-y-4 start-[14px] rtl:peer-focus:translate-x-1/4 rtl:peer-focus:left-auto ${
455
- isDisabled
456
- ? 'cursor-pointer'
457
- : 'cursor-text peer-focus:cursor-pointer'
458
- }
454
+ isDisabled && !checkIsEmptyField()
455
+ ? 'disabled-input-label-bg'
456
+ : !isDisabled || !checkIsEmptyField()
457
+ ? 'active-input-label-bg'
458
+ : ''
459
+ } absolute duration-300 transform -translate-y-4 top-2 z-1 origin-[0] px-0 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:top-1/2 peer-focus:top-2 peer-focus:-translate-y-4 start-[14px] rtl:peer-focus:translate-x-1/4 rtl:peer-focus:left-auto ${
460
+ isDisabled
461
+ ? 'cursor-pointer'
462
+ : 'cursor-text peer-focus:cursor-pointer'
463
+ }
459
464
  ${
460
465
  checkIsEmptyField()
461
466
  ? 'modern-input-label-size'
@@ -499,16 +504,24 @@ const ModernAutoComplete: React.FC<AutoSuggestionInputProps> = ({
499
504
  // If user is clicking on dropdown, don't execute exact match logic
500
505
  if (isSelectingRef.current) {
501
506
  isSelectingRef.current = false;
507
+ hasUserChangedInputRef.current = false; // Reset flag
508
+ return;
509
+ }
510
+
511
+ // Only proceed if user has actually changed the input value
512
+ if (!hasUserChangedInputRef.current) {
502
513
  return;
503
514
  }
504
515
 
505
516
  if (!inputValue) {
517
+ hasUserChangedInputRef.current = false; // Reset flag
506
518
  return;
507
519
  }
508
520
 
509
521
  if (!filteredData || filteredData.length === 0) {
510
522
  // No suggestions to match against – notify parent with raw value
511
523
  onChange({ id: undefined, name: inputValue, from: 4 });
524
+ hasUserChangedInputRef.current = false; // Reset flag
512
525
  return;
513
526
  }
514
527
 
@@ -527,6 +540,7 @@ const ModernAutoComplete: React.FC<AutoSuggestionInputProps> = ({
527
540
  onChange({ id: undefined, name: inputValue, from: 4 });
528
541
  setInputValue('');
529
542
  }
543
+ hasUserChangedInputRef.current = false; // Reset flag after handling
530
544
  };
531
545
 
532
546
  // Handle Tab key on input - works even when dropdown is closed or loading
@@ -554,11 +568,13 @@ const ModernAutoComplete: React.FC<AutoSuggestionInputProps> = ({
554
568
  // No exact match and no highlighted item - trigger invalid value action
555
569
  onChange({ id: undefined, name: inputValue, from: 4 });
556
570
  setInputValue('');
571
+ hasUserChangedInputRef.current = false; // Reset flag
557
572
  }
558
- } else if (inputValue && (!filteredData || filteredData.length === 0)) {
573
+ } else if (inputValue && (!filteredData || filteredData.length === 0 ) && hasUserChangedInputRef.current) {
559
574
  // No suggestions available - trigger invalid value action
560
575
  onChange({ id: undefined, name: inputValue, from: 4 });
561
576
  setInputValue('');
577
+ hasUserChangedInputRef.current = false; // Reset flag
562
578
  }
563
579
  // Don't prevent default - allow Tab to move focus
564
580
  };
@@ -574,8 +590,8 @@ const ModernAutoComplete: React.FC<AutoSuggestionInputProps> = ({
574
590
  item.param1 ?? '',
575
591
  item.param2 ?? '',
576
592
  item.param3 ?? '',
577
- item.param4 ?? ''
578
- )
593
+ item.param4 ?? '',
594
+ ),
579
595
  )
580
596
  : suggestions;
581
597
 
@@ -642,8 +658,8 @@ const ModernAutoComplete: React.FC<AutoSuggestionInputProps> = ({
642
658
  e.key === 'ArrowDown'
643
659
  ? (selectedIndex + 1) % filteredData.length
644
660
  : e.key === 'ArrowUp'
645
- ? (selectedIndex - 1 + filteredData.length) % filteredData.length
646
- : selectedIndex;
661
+ ? (selectedIndex - 1 + filteredData.length) % filteredData.length
662
+ : selectedIndex;
647
663
 
648
664
  switch (e.key) {
649
665
  case 'ArrowDown':
@@ -883,7 +899,7 @@ const ModernAutoComplete: React.FC<AutoSuggestionInputProps> = ({
883
899
  onBlur={(e) => {
884
900
  // Check immediately if user is selecting (Tab or click)
885
901
  const wasSelecting = isSelectingRef.current;
886
-
902
+
887
903
  // Delay to allow click events on dropdown to process first
888
904
  setTimeout(() => {
889
905
  // Check if focus moved to dropdown or if user is selecting
@@ -928,7 +944,7 @@ const ModernAutoComplete: React.FC<AutoSuggestionInputProps> = ({
928
944
  ? placeholder && isCustomPlaceholder
929
945
  ? placeholder
930
946
  : 'Type to search'
931
- : placeholder ?? '--Select--'
947
+ : (placeholder ?? '--Select--')
932
948
  }
933
949
  onFocus={onInputFocus}
934
950
  onClick={(e) => {
@@ -955,7 +971,7 @@ const ModernAutoComplete: React.FC<AutoSuggestionInputProps> = ({
955
971
  <div
956
972
  ref={adorementRef}
957
973
  className={`${generateClassName(
958
- 'adorement'
974
+ 'adorement',
959
975
  )} qbs-autocomplete-adorement custom-dorpdown-adorement mr-[1px] ${
960
976
  isLoading ? 'bg-white' : ''
961
977
  }`}
@@ -996,7 +1012,7 @@ const ModernAutoComplete: React.FC<AutoSuggestionInputProps> = ({
996
1012
  {errors && errors[name] && (
997
1013
  <div
998
1014
  className={` text-error-label relative cursor-pointer ${generateClassName(
999
- 'message'
1015
+ 'message',
1000
1016
  )}`}
1001
1017
  onMouseEnter={() => setIsHovered(true)}
1002
1018
  onMouseLeave={() => setIsHovered(false)}
@@ -111,6 +111,7 @@ const ModernAutoCompleteDropdown: React.FC<AutoSuggestionInputProps> = ({
111
111
  const [refetchData, setRefetchData] = useState(false);
112
112
 
113
113
  const isSelectingRef = useRef(false);
114
+ const hasUserChangedInputRef = useRef(false); // Track if user has actually typed/changed input
114
115
  const [dropPosition, setDropPosition] = useState<any>({
115
116
  top: 0,
116
117
  left: 0,
@@ -170,6 +171,7 @@ const ModernAutoCompleteDropdown: React.FC<AutoSuggestionInputProps> = ({
170
171
  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
171
172
  const { value } = event.target;
172
173
  timerRef.current = 0;
174
+ hasUserChangedInputRef.current = true; // Mark that user has changed the input
173
175
 
174
176
  setInputValue(value);
175
177
  handleValChange(value);
@@ -340,6 +342,7 @@ const ModernAutoCompleteDropdown: React.FC<AutoSuggestionInputProps> = ({
340
342
  }, [disabled]);
341
343
  const handleSuggestionClick = (suggestion: valueProps, index: number) => {
342
344
  isSelectingRef.current = true;
345
+ hasUserChangedInputRef.current = false; // Reset flag when selecting from dropdown
343
346
  onChange({
344
347
  ...suggestion,
345
348
  id: suggestion?.id,
@@ -414,6 +417,7 @@ const ModernAutoCompleteDropdown: React.FC<AutoSuggestionInputProps> = ({
414
417
  setDropOpen(false);
415
418
  setInputValue('');
416
419
  onChange({ id: undefined, name: '', from: 3 });
420
+ hasUserChangedInputRef.current = false; // Reset flag when clearing
417
421
  onLabelClick();
418
422
  if (autoDropdown) {
419
423
  setRefetchData(true);
@@ -422,6 +426,7 @@ const ModernAutoCompleteDropdown: React.FC<AutoSuggestionInputProps> = ({
422
426
 
423
427
  useEffect(() => {
424
428
  setInputValue(value?.name ?? '');
429
+ hasUserChangedInputRef.current = false; // Reset flag when value prop changes (e.g., coming back to page)
425
430
  }, [value?.name]);
426
431
 
427
432
  useEffect(() => {
@@ -522,16 +527,24 @@ const ModernAutoCompleteDropdown: React.FC<AutoSuggestionInputProps> = ({
522
527
  // If user is clicking on dropdown, don't execute exact match logic
523
528
  if (isSelectingRef.current) {
524
529
  isSelectingRef.current = false;
530
+ hasUserChangedInputRef.current = false; // Reset flag
531
+ return;
532
+ }
533
+
534
+ // Only proceed if user has actually changed the input value
535
+ if (!hasUserChangedInputRef.current) {
525
536
  return;
526
537
  }
527
538
 
528
539
  if (!inputValue) {
540
+ hasUserChangedInputRef.current = false; // Reset flag
529
541
  return;
530
542
  }
531
543
 
532
544
  if (!filteredData || filteredData.length === 0) {
533
545
  // No suggestions to match against – notify parent with raw value
534
546
  onChange({ id: undefined, name: inputValue, from: 4 });
547
+ hasUserChangedInputRef.current = false; // Reset flag
535
548
  return;
536
549
  }
537
550
 
@@ -550,6 +563,7 @@ const ModernAutoCompleteDropdown: React.FC<AutoSuggestionInputProps> = ({
550
563
  onChange({ id: undefined, name: inputValue, from: 4 });
551
564
  setInputValue('');
552
565
  }
566
+ hasUserChangedInputRef.current = false; // Reset flag after handling
553
567
  };
554
568
 
555
569
  // Handle Tab key on input - works even when dropdown is closed or loading
@@ -577,11 +591,13 @@ const ModernAutoCompleteDropdown: React.FC<AutoSuggestionInputProps> = ({
577
591
  // No exact match and no highlighted item - trigger invalid value action
578
592
  onChange({ id: undefined, name: inputValue, from: 4 });
579
593
  setInputValue('');
594
+ hasUserChangedInputRef.current = false; // Reset flag
580
595
  }
581
596
  } else if (inputValue && (!filteredData || filteredData.length === 0)) {
582
597
  // No suggestions available - trigger invalid value action
583
598
  onChange({ id: undefined, name: inputValue, from: 4 });
584
599
  setInputValue('');
600
+ hasUserChangedInputRef.current = false; // Reset flag
585
601
  }
586
602
  // Don't prevent default - allow Tab to move focus
587
603
  };
@@ -59,6 +59,7 @@ type ModernAutoCompleteSuggectionsProps = {
59
59
  desc?: string;
60
60
  /** Field name that contains the unique identifier (default: 'id'). */
61
61
  descId?: string;
62
+ showWarningIcon?: boolean;
62
63
  };
63
64
 
64
65
  /**
@@ -74,7 +75,7 @@ const SuggestionItem: React.FC<{ name: string; count: number; showCount: boolean
74
75
  }) => {
75
76
  return (
76
77
  <div className="py-0.5 flex items-center justify-between gap-2 group relative">
77
- <span className="flex-1 whitespace-normal break-words">
78
+ <span className="flex-1 whitespace-normal break-words suggection-label">
78
79
  {name}
79
80
  </span>
80
81
  {showCount && (
@@ -115,8 +116,10 @@ const ModernAutoCompleteSuggections: React.FC<
115
116
  labelTitle,
116
117
  desc = 'name',
117
118
  descId = 'id',
119
+ showWarningIcon = true,
118
120
  }) => {
119
121
  const [inputValue, setInputValue] = useState(value ?? '');
122
+ const [debouncedInputValue, setDebouncedInputValue] = useState('');
120
123
  const [items, setItems] = useState<SuggestionItem[]>(data ?? []);
121
124
  const [isLoading, setIsLoading] = useState(false);
122
125
  const [showDropdown, setShowDropdown] = useState(false);
@@ -152,13 +155,26 @@ const ModernAutoCompleteSuggections: React.FC<
152
155
  }
153
156
  }, [data]);
154
157
 
155
- // Load from getData only when user types some text (no initial preload)
158
+ // Debounce inputValue so we don't call API on every keystroke
159
+ useEffect(() => {
160
+ if (!hasTyped) return;
161
+ const handle = window.setTimeout(() => {
162
+ setDebouncedInputValue(inputValue);
163
+ }, 500); // 500ms debounce
164
+
165
+ return () => {
166
+ window.clearTimeout(handle);
167
+ };
168
+ }, [inputValue, hasTyped]);
169
+
170
+ // Load from getData only when user types some text (no initial preload),
171
+ // and only after debounce delay.
156
172
  useEffect(() => {
157
173
  // Do not call API until the user has actually typed at least once
158
174
  if (!hasTyped) return;
159
175
 
160
176
  let cancelled = false;
161
- const query = inputValue.trim();
177
+ const query = debouncedInputValue.trim();
162
178
  if (!getDataRef.current || query === '') {
163
179
  // Clear items when input is cleared so dropdown also clears
164
180
  if (!cancelled) {
@@ -189,7 +205,14 @@ const ModernAutoCompleteSuggections: React.FC<
189
205
  return () => {
190
206
  cancelled = true;
191
207
  };
192
- }, [inputValue, hasTyped, data]);
208
+ }, [debouncedInputValue, data]);
209
+
210
+ // Notify parent via onChange when debounced value changes
211
+ useEffect(() => {
212
+ if (!hasTyped) return;
213
+ if (!onChange) return;
214
+ onChange(debouncedInputValue);
215
+ }, [debouncedInputValue, onChange]);
193
216
 
194
217
  // Group by desc field and compute counts
195
218
  const grouped = useMemo(() => {
@@ -232,7 +255,14 @@ const ModernAutoCompleteSuggections: React.FC<
232
255
  return grouped.filter((g) => g.name.toLowerCase().includes(term));
233
256
  }, [grouped, inputValue, hasTyped]);
234
257
 
235
- // Update dropdown visibility based on visible items
258
+ const effectiveVisible = useMemo(() => {
259
+ if (!hasTyped && items.length > 0) {
260
+ return grouped;
261
+ }
262
+ return visible;
263
+ }, [hasTyped, items.length, grouped, visible]);
264
+
265
+ // Update dropdown visibility based on visible items from typing
236
266
  useEffect(() => {
237
267
  setShowDropdown(visible.length > 0);
238
268
  }, [visible.length]);
@@ -317,7 +347,7 @@ const ModernAutoCompleteSuggections: React.FC<
317
347
  };
318
348
 
319
349
  const generateClassName = (
320
- type: 'input' | 'label' | 'message' | 'adorement'
350
+ type: 'input' | 'label' | 'message' | 'adorement' | 'warning'
321
351
  ): string => {
322
352
  let className = propsClassName || '';
323
353
  switch (type) {
@@ -370,6 +400,9 @@ const ModernAutoCompleteSuggections: React.FC<
370
400
  case 'message':
371
401
  className = ' text-error-icon ';
372
402
  break;
403
+ case 'warning':
404
+ className = ' text-warning-icon ';
405
+ break;
373
406
  case 'adorement':
374
407
  className += ' absolute right-0 adorement gap-1 flex items-center ';
375
408
  break;
@@ -464,11 +497,16 @@ const ModernAutoCompleteSuggections: React.FC<
464
497
  value={inputValue ? inputValue : ''}
465
498
  ref={inputRef}
466
499
  disabled={disabled}
500
+ onFocus={() => {
501
+ // show suggestions when the field gains focus.
502
+ if (!disabled && effectiveVisible.length > 0) {
503
+ setShowDropdown(true);
504
+ }
505
+ }}
467
506
  onChange={(e) => {
468
507
  const val = e.target.value;
469
508
  setInputValue(val);
470
509
  setHasTyped(true);
471
- onChange?.(val);
472
510
  }}
473
511
  onBlur={() => {}}
474
512
  placeholder={placeholder}
@@ -510,16 +548,26 @@ const ModernAutoCompleteSuggections: React.FC<
510
548
  <CustomIcons name="alert" type="medium" />
511
549
  </div>
512
550
  )}
551
+
552
+ {showWarningIcon && effectiveVisible.length > 0 && (
553
+ <div
554
+ className={`text-warning-label relative mr-1 ${generateClassName(
555
+ 'warning'
556
+ )}`}
557
+ >
558
+ <CustomIcons name="alert" type="medium" />
559
+ </div>
560
+ )}
513
561
  </div>
514
562
  </div>
515
563
  </div>
516
564
  </div>
517
- {showDropdown && visible.length > 0 && (
565
+ {showDropdown && effectiveVisible.length > 0 && (
518
566
  <>
519
567
  {insideOpen ? (
520
568
  <div className="suggection-list">
521
569
  <div className="bg-white border border-grey-light shadow-gray-300 shadow-md rounded-sm max-h-40 overflow-auto text-[11px] text-gray-700 px-3.5 py-1.5">
522
- {visible.map((g) => (
570
+ {effectiveVisible.map((g) => (
523
571
  <SuggestionItem
524
572
  key={g.name}
525
573
  name={g.name}
@@ -532,16 +580,16 @@ const ModernAutoCompleteSuggections: React.FC<
532
580
  ) : (
533
581
  <Portal>
534
582
  <div
535
- className="autocomplete-suggections suggection-list bg-white shadow-gray-300 shadow-md border border-grey-light z-50"
583
+ className="autocomplete-suggections autosuggection-list suggection-list bg-white shadow-gray-300 shadow-md border border-grey-light z-50"
536
584
  style={dropPosition}
537
585
  >
538
586
  <div className="h-auto max-h-40 overflow-auto w-full py-1.5">
539
- {visible.map((g) => (
540
- <div key={g.name} className="px-3.5 text-[11px] text-gray-700">
587
+ {effectiveVisible.map((g) => (
588
+ <div key={g.name} className="px-3.5 text-gray-700 suggection-item">
541
589
  <SuggestionItem
542
590
  name={g.name}
543
591
  count={g.count}
544
- showCount={g.count > -1}
592
+ showCount={g.count > 0}
545
593
  />
546
594
  </div>
547
595
  ))}