react-core-ts 1.0.42 → 1.0.43

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.
Files changed (44) hide show
  1. package/README.md +50 -0
  2. package/dist/AutoComplete.d.ts +2 -2
  3. package/dist/AutoComplete.js +44 -37
  4. package/dist/AutoComplete.js.map +1 -1
  5. package/dist/AutoCompleteWithSelectedList.d.ts +4 -0
  6. package/dist/AutoCompleteWithSelectedList.js +446 -0
  7. package/dist/AutoCompleteWithSelectedList.js.map +1 -0
  8. package/dist/ExpandableAutoComplete.js +12 -5
  9. package/dist/ExpandableAutoComplete.js.map +1 -1
  10. package/dist/commontypes.d.ts +11 -4
  11. package/dist/components/DropdownList.js +2 -2
  12. package/dist/components/DropdownList.js.map +1 -1
  13. package/dist/components/InputActions.d.ts +1 -0
  14. package/dist/components/InputActions.js +2 -2
  15. package/dist/components/InputActions.js.map +1 -1
  16. package/dist/index.d.ts +1 -0
  17. package/dist/index.js +3 -1
  18. package/dist/index.js.map +1 -1
  19. package/dist/styles/styles/components/autocomplete.css +602 -0
  20. package/dist/styles/styles/components/selectedautocomplete.css +121 -0
  21. package/dist/styles/styles/components/treedropdown.css +63 -0
  22. package/dist/styles/styles/global.css +7 -0
  23. package/dist/utilities/autosuggestions.d.ts +2 -2
  24. package/dist/utilities/autosuggestions.js +10 -7
  25. package/dist/utilities/autosuggestions.js.map +1 -1
  26. package/dist/utilities/filterSuggestions.d.ts +1 -1
  27. package/dist/utilities/filterSuggestions.js +10 -5
  28. package/dist/utilities/filterSuggestions.js.map +1 -1
  29. package/docs/App.tsx +96 -4
  30. package/docs/index.html +1 -1
  31. package/package.json +1 -1
  32. package/src/AutoComplete.tsx +66 -51
  33. package/src/AutoCompleteWithSelectedList.tsx +677 -0
  34. package/src/ExpandableAutoComplete.tsx +13 -5
  35. package/src/commontypes.ts +14 -6
  36. package/src/components/DropdownList.tsx +3 -3
  37. package/src/components/InputActions.tsx +3 -1
  38. package/src/index.html +1 -1
  39. package/src/index.tsx +1 -0
  40. package/src/styles/components/autocomplete.css +18 -1
  41. package/src/styles/components/selectedautocomplete.css +121 -0
  42. package/src/styles/global.css +2 -1
  43. package/src/utilities/autosuggestions.ts +18 -12
  44. package/src/utilities/filterSuggestions.ts +10 -7
@@ -0,0 +1,677 @@
1
+ import React, {
2
+ forwardRef,
3
+ useCallback,
4
+ useEffect,
5
+ useImperativeHandle,
6
+ useRef,
7
+ useState,
8
+ } from "react";
9
+ import ReactDOM from "react-dom";
10
+
11
+ import { AutoSuggestionInputProps, TabPops, ValueProps } from "./commontypes";
12
+ import DropdownList from "./components/DropdownList";
13
+ import InputActions from "./components/InputActions";
14
+ import { useSuggestions } from "./utilities/autosuggestions";
15
+ import { debounce } from "./utilities/debounce";
16
+ import { deepEqual } from "./utilities/deepEqual";
17
+ import { default as Tooltip } from "./utilities/expandableTootltip";
18
+ import { filterSuggestions } from "./utilities/filterSuggestions";
19
+ import { DropArrow, Search, Spinner } from "./utilities/icons";
20
+
21
+ const AutoCompleteWithSelectedList = forwardRef<
22
+ HTMLInputElement,
23
+ AutoSuggestionInputProps
24
+ >(
25
+ (
26
+ {
27
+ label,
28
+ onChange,
29
+ getData = async () => [],
30
+ data = [],
31
+ errors,
32
+ required = false,
33
+ name,
34
+ fullWidth = false,
35
+ placeholder,
36
+ id,
37
+ type = "custom_select",
38
+ selectedItems: propsSeelctedItems = [],
39
+ readOnly = false,
40
+ disabled = false,
41
+ value,
42
+ isMultiple = false,
43
+ desc = "name",
44
+ descId = "id",
45
+ singleSelect,
46
+ className,
47
+ async = false,
48
+ paginationEnabled,
49
+ initialLoad = false,
50
+ actionLabel,
51
+ handleAction,
52
+ nextBlock,
53
+ notDataMessage,
54
+ initialDataMessage,
55
+ onFocus,
56
+ expandable = false,
57
+ textCount = 10,
58
+ itemCount = 1,
59
+ scrollRef,
60
+ countOnly = true,
61
+ typeOnlyFetch = false,
62
+ tab = [],
63
+ },
64
+ ref
65
+ ) => {
66
+ const dropdownRef = useRef<HTMLDivElement>(null);
67
+ const dropdownSelectedRef = useRef<HTMLDivElement>(null);
68
+ const tabRef = useRef<HTMLUListElement>(null);
69
+ // State Hooks Section
70
+ const [isInitialRender, setIsInitialRender] = useState(true);
71
+
72
+ const [inputValue, setInputValue] = useState<string>(value);
73
+ const [searchValue, setSearchValue] = useState<string>("");
74
+ const [searchOldValue, setSearchOldValue] = useState<string>("");
75
+ const [nextPage, setNextPage] = useState<number | undefined>(1);
76
+ const [dropOpen, setDropOpen] = useState<boolean>(false);
77
+ const [selectedItems, setSelectedItems] = useState<ValueProps[]>([]);
78
+ const [showAllSelected, setShowAllSelected] = useState<boolean>(false);
79
+ const [activeTab, setActiveTab] = useState<number>(0);
80
+ // API call for suggestions through a custom hook
81
+ const inputRef = useRef(null);
82
+ const dropRef = useRef(null);
83
+ useImperativeHandle(ref, () => inputRef.current);
84
+ const [dropdownStyle, setDropdownStyle] = useState({
85
+ top: 0,
86
+ left: 0,
87
+ width: 200,
88
+ });
89
+
90
+ const adjustDropdownPosition = () => {
91
+ if (dropdownRef.current) {
92
+ const inputRect = dropdownRef.current.getBoundingClientRect();
93
+ const dropdownPosition = {
94
+ left: inputRect.left + window.scrollX,
95
+ width: inputRect.width,
96
+ top: 0,
97
+ };
98
+ // Check if there's enough space below the input for the dropdown
99
+ const spaceBelow = window.innerHeight - inputRect.bottom;
100
+ const spaceAbove = inputRect.top + window.scrollY;
101
+
102
+ let dropdownHeight = 275; // Assume a fixed height or calculate based on content
103
+ if (countOnly) {
104
+ if (dropdownSelectedRef?.current)
105
+ dropdownHeight += dropdownSelectedRef?.current?.clientHeight + 3;
106
+ }
107
+
108
+ if(tab.length > 0) {
109
+ if (tabRef?.current)
110
+ dropdownHeight += tabRef?.current?.clientHeight + 20;
111
+ }
112
+ if (spaceBelow >= dropdownHeight) {
113
+ dropdownPosition.top =
114
+ inputRect.top + window.scrollY + inputRect.height;
115
+ } else {
116
+ dropdownPosition.top =
117
+ inputRect.top + window.scrollY - dropdownHeight + 73;
118
+ }
119
+
120
+ setDropdownStyle({
121
+ ...dropdownPosition,
122
+ });
123
+ }
124
+ };
125
+
126
+ useEffect(() => {
127
+ window.addEventListener("resize", adjustDropdownPosition);
128
+ adjustDropdownPosition();
129
+
130
+ return () => {
131
+ window.removeEventListener("resize", adjustDropdownPosition);
132
+ };
133
+ }, [dropOpen, selectedItems]);
134
+
135
+ const { suggestions, isLoading, handlePickSuggestions } = useSuggestions(
136
+ getData,
137
+ data,
138
+ dropOpen,
139
+ async,
140
+ paginationEnabled,
141
+ initialLoad,
142
+ inputValue,
143
+ isMultiple,
144
+ setNextPage,
145
+ selectedItems,
146
+ typeOnlyFetch
147
+ // nextBlock
148
+ );
149
+
150
+ // Handling the selection of a suggestion
151
+ const handleSuggestionClick = useCallback((suggestion: ValueProps) => {
152
+ if (isMultiple) {
153
+ setSelectedItems((prev) => {
154
+ const isAdded = prev.some(
155
+ (item) => item[descId] === suggestion[descId]
156
+ );
157
+ if (isAdded) {
158
+ return prev.filter((item) => item[descId] !== suggestion[descId]);
159
+ } else {
160
+ return [...prev, suggestion];
161
+ }
162
+ });
163
+ } else {
164
+ setInputValue(suggestion[desc]);
165
+ setSearchValue("");
166
+ onChange(suggestion);
167
+ setDropOpen(false);
168
+ }
169
+ }, []);
170
+
171
+ // Adding debounce to avoid making API calls on every keystroke
172
+ const debouncedUpdate = useCallback(
173
+ debounce((value: string, tabVal?: number | string) => {
174
+ setSearchOldValue(value);
175
+ handlePickSuggestions(value, 1, false, tabVal);
176
+ }, 500),
177
+ []
178
+ );
179
+
180
+ const handleChangeWithDebounce = (value: string) => {
181
+ if ((type === "auto_complete" || type === "auto_suggestion") && async) {
182
+ const activeTabVal = tab.length > 0 ? tab?.[activeTab].id : undefined
183
+ debouncedUpdate(value, activeTabVal);
184
+ }
185
+ };
186
+
187
+ const handleMultiSelect = (e: any, suggestion: ValueProps) => {
188
+ const { checked } = e.target;
189
+ if (isMultiple) {
190
+ if (checked) {
191
+ setSelectedItems((prev) => [...prev, suggestion]);
192
+ } else {
193
+ setSelectedItems((prev) => {
194
+ return prev.filter(
195
+ (item, i) => item[descId] !== suggestion[descId]
196
+ );
197
+ });
198
+ }
199
+ } else {
200
+ if (checked) {
201
+ setInputValue(suggestion[desc]);
202
+ onChange(suggestion);
203
+ } else {
204
+ setInputValue("");
205
+ }
206
+ }
207
+ };
208
+ useEffect(() => {
209
+ if (!deepEqual(selectedItems, propsSeelctedItems))
210
+ setSelectedItems(propsSeelctedItems);
211
+ }, [propsSeelctedItems]);
212
+ // Effect to set the input value whenever `value` prop changes
213
+ useEffect(() => {
214
+ setInputValue(value ?? "");
215
+ }, [value]);
216
+
217
+ const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
218
+ const { value } = e.target;
219
+ setDropOpen(true);
220
+ setSearchValue(value);
221
+ handleChangeWithDebounce(value);
222
+ if (!value) {
223
+ setInputValue("");
224
+ // onChange({ [descId]: '', [desc]: '' });
225
+ }
226
+ };
227
+ const handleSuggestionChange = (e: React.ChangeEvent<HTMLInputElement>) => {
228
+ const { value } = e.target;
229
+ setDropOpen(true);
230
+ setSearchValue(value);
231
+ handleChangeWithDebounce(value);
232
+ };
233
+
234
+ const handleClear = () => {
235
+ if (searchValue) {
236
+ setSearchValue("");
237
+ setDropOpen(false);
238
+ } else {
239
+ setInputValue("");
240
+ onChange({ [descId]: "", [desc]: "" });
241
+ setDropOpen(false);
242
+ }
243
+ };
244
+
245
+ const handleClearSelected = () => {
246
+ setSelectedItems([]);
247
+ onChange({ [descId]: "", [desc]: "" });
248
+ };
249
+
250
+ const generateClassName = useCallback(() => {
251
+ return `qbs-textfield-default ${className} ${
252
+ errors && errors?.message ? "textfield-error" : "textfield"
253
+ } ${expandable ? "expandable" : ""}`;
254
+ }, [errors, name]);
255
+ const handleRemoveSelectedItem = (index: number) => {
256
+ setSelectedItems((prev) => {
257
+ return prev.filter((_, i) => i !== index);
258
+ });
259
+ };
260
+
261
+ useEffect(() => {
262
+ if (isInitialRender) {
263
+ setIsInitialRender(false);
264
+ } else {
265
+ onChange(selectedItems);
266
+ }
267
+ }, [selectedItems]);
268
+
269
+ useEffect(() => {
270
+ const handleClickOutside = (event: any) => {
271
+ if (
272
+ dropRef.current &&
273
+ event.target instanceof Node &&
274
+ !dropRef.current.contains(event.target) &&
275
+ event.target.nodeName !== "svg"
276
+ ) {
277
+ setDropOpen(false);
278
+ //setSearchValue("");
279
+ }
280
+ };
281
+ document.addEventListener("mousedown", handleClickOutside as any);
282
+ window.addEventListener("scroll", handleClickOutside as any);
283
+
284
+ const scrollableDivs = document.querySelectorAll(
285
+ 'div[style*="overflow"]'
286
+ );
287
+ scrollableDivs.forEach((div) =>
288
+ div.addEventListener("scroll", handleClickOutside as any)
289
+ );
290
+ if (scrollRef && scrollRef.current && scrollRef.current !== null) {
291
+ scrollRef.current.addEventListener("scroll", handleClickOutside as any);
292
+ }
293
+ return () => {
294
+ document.removeEventListener("mousedown", handleClickOutside as any);
295
+ window.removeEventListener("scroll", handleClickOutside as any);
296
+ scrollableDivs.forEach((div) =>
297
+ div.removeEventListener("scroll", handleClickOutside as any)
298
+ );
299
+ if (scrollRef && scrollRef.current && scrollRef.current !== null) {
300
+ scrollRef.current.removeEventListener(
301
+ "scroll",
302
+ handleClickOutside as any
303
+ );
304
+ }
305
+ };
306
+ }, []);
307
+
308
+ // Filtering suggestions based on type and search value
309
+ const selected: any = isMultiple ? selectedItems : inputValue;
310
+ const filteredData: ValueProps[] = filterSuggestions(
311
+ suggestions,
312
+ searchValue,
313
+ type,
314
+ desc,
315
+ selected,
316
+ async,
317
+ false,
318
+ true
319
+ );
320
+ const isSelected = (
321
+ item: ValueProps,
322
+ selectedItems: ValueProps[] | string
323
+ ): boolean => {
324
+ if (Array.isArray(selectedItems)) {
325
+ return selectedItems.some(
326
+ (selectedItem) => selectedItem[desc] === item[desc]
327
+ );
328
+ } else {
329
+ return item[desc] === selectedItems;
330
+ }
331
+ };
332
+
333
+ const handleLoadMore = () => {
334
+ if (paginationEnabled) {
335
+ handlePickSuggestions(searchValue, nextPage + 1, true);
336
+ setNextPage(nextPage + 1);
337
+ }
338
+ };
339
+
340
+ const handleOnClick = () => {
341
+ !disabled && !readOnly ? setDropOpen(true) : "";
342
+ };
343
+ const onInputFocus = () => {
344
+ if (inputRef.current) inputRef.current.focus();
345
+ handleOnClick();
346
+ };
347
+
348
+ const handleShowAllSelected = (enabled: boolean) => {
349
+ if (enabled) {
350
+ setShowAllSelected(true);
351
+ } else {
352
+ setShowAllSelected(false);
353
+ }
354
+ };
355
+
356
+ const tooltipContent =
357
+ selectedItems?.length > itemCount
358
+ ? selectedItems
359
+ ?.slice(itemCount)
360
+ .map((item) => item[desc])
361
+ .join(", ")
362
+ : "";
363
+
364
+ const handleDropOpen = (e: any) => {
365
+ if (!dropOpen) setDropOpen(true);
366
+ };
367
+
368
+ const handleDropClose = (e: any) => {
369
+ if (dropOpen) setDropOpen(false);
370
+ };
371
+
372
+ const getSelectedItems = (dropdown: boolean) => {
373
+ return (
374
+ <>
375
+ {selectedItems
376
+ .slice(
377
+ 0,
378
+ selectedItems?.length > itemCount
379
+ ? showAllSelected
380
+ ? selectedItems?.length
381
+ : itemCount
382
+ : selectedItems?.length
383
+ )
384
+ .map((item, index) => (
385
+ <div key={index} className="selected-items-container">
386
+ <Tooltip
387
+ title={item?.[desc]}
388
+ enabled={item?.[desc]?.length > textCount}
389
+ >
390
+ <div key={item.id} className="selected-item">
391
+ {item?.[desc]?.length > textCount
392
+ ? `${item?.[desc].substring(0, textCount)}...`
393
+ : item?.[desc]}
394
+
395
+ <button
396
+ onClick={() => handleRemoveSelectedItem(index)}
397
+ className="remove-item-btn"
398
+ aria-label={`Remove ${item?.[desc]}`}
399
+ >
400
+ X
401
+ </button>
402
+ </div>
403
+ </Tooltip>
404
+ </div>
405
+ ))}
406
+ {selectedItems?.length > itemCount && !showAllSelected && (
407
+ <div className="selected-items-container">
408
+ <Tooltip title={tooltipContent} enabled={true}>
409
+ <div
410
+ className={`selected-item-more qbs-rounded-full qbs-min-h-6 qbs-min-w-6 qbs-p-1 ${
411
+ dropdown ? "qbs-cursor-pointer" : ""
412
+ }`}
413
+ onClick={() => {
414
+ handleShowAllSelected(dropdown);
415
+ }}
416
+ >
417
+ +{selectedItems?.length - itemCount}{" "}
418
+ <span className="qbs-hidden">more</span>
419
+ </div>
420
+ </Tooltip>
421
+ </div>
422
+ )}
423
+ </>
424
+ );
425
+ };
426
+
427
+ const getTabItems = () => {
428
+ return (
429
+ <ul className="qbs-flex qbs-flex-wrap qbs-w-full qbs-tab qbs-mb-2 -qbs-mt-2" ref={tabRef}>
430
+ {tab.map((item: TabPops, idx: number) => (
431
+ <li className="qbs-flex-1 qbs-tab-items" key={`tab-${idx}`}>
432
+ <span
433
+ className={`qbs-inline-block qbs-tab-item qbs-text-sm qbs-cursor-pointer qbs-w-full qbs-text-center qbs-p-1 qbs-border-b-2 ${
434
+ activeTab === idx
435
+ ? "qbs-tab-active-item"
436
+ : ""
437
+ }`}
438
+ onClick={() => {
439
+ setActiveTab(idx);
440
+ }}
441
+ >
442
+ {item.label}
443
+ </span>
444
+ </li>
445
+ ))}
446
+ </ul>
447
+ );
448
+ };
449
+
450
+ return (
451
+ <div className={fullWidth ? "fullWidth" : "autoWidth"} ref={dropdownRef}>
452
+ {label && (
453
+ <div
454
+ style={{
455
+ marginBottom: 5,
456
+ display: "flex",
457
+ justifyContent: "space-between",
458
+ }}
459
+ >
460
+ <label className={`labels label-text`}>
461
+ {label}
462
+ {required && <span className="text-error"> *</span>}
463
+ </label>
464
+ <span onClick={() => handleAction?.()} className={`action_label`}>
465
+ {actionLabel}
466
+ </span>
467
+ </div>
468
+ )}
469
+ {/* Displaying selected items for multi-select */}
470
+
471
+ <div
472
+ className={`qbs-relative qbs-autocomplete-selected-comp ${
473
+ expandable ? "qbs-expandable-container" : "qbs-container"
474
+ }`}
475
+ >
476
+ {selectedItems?.length > 0 && (
477
+ <>
478
+ {!countOnly ? (
479
+ getSelectedItems(false)
480
+ ) : (
481
+ <div className="selected-items-container qbs-text-sm qbs-gap-1">
482
+ <span className="badge qbs-rounded-full qbs-text-xs qbs-inline-flex qbs-items-center qbs-justify-center qbs-px-2 qbs-py-1 qbs-leading-none qbs-min-w-6 qbs-min-h-6">
483
+ {selectedItems?.length}
484
+ </span>
485
+ Item{selectedItems?.length > 1 && "s"} selected
486
+ </div>
487
+ )}
488
+ </>
489
+ )}
490
+
491
+ <div
492
+ className={`qbs-textfield-expandable ${
493
+ !expandable ? "qbs-normal" : ""
494
+ }`}
495
+ data-value={
496
+ selectedItems?.length > 0 || searchValue
497
+ ? searchValue
498
+ : placeholder ?? ""
499
+ }
500
+ onClick={() => onInputFocus()}
501
+ >
502
+ <input
503
+ id={id}
504
+ ref={inputRef}
505
+ type="text"
506
+ value={
507
+ type === "auto_suggestion" && !expandable
508
+ ? inputValue
509
+ : searchValue || inputValue
510
+ }
511
+ onChange={handleChange}
512
+ // onBlur={handleBlur}
513
+ onFocus={onFocus}
514
+ onClick={() => handleOnClick()}
515
+ className={generateClassName()}
516
+ placeholder={selectedItems?.length > 0 ? "" : placeholder ?? ""}
517
+ readOnly={
518
+ readOnly ||
519
+ type === "custom_select" ||
520
+ (type == "auto_suggestion" && !expandable)
521
+ }
522
+ disabled={disabled}
523
+ data-testid="custom-autocomplete"
524
+ />
525
+ </div>
526
+
527
+ {/* Icons for Clearing Input or Toggling Dropdown */}
528
+
529
+ <InputActions
530
+ inputValue={inputValue}
531
+ searchValue={searchValue}
532
+ dropOpen={dropOpen}
533
+ handleDropOpen={handleDropOpen}
534
+ handleDropClose={handleDropClose}
535
+ disabled={disabled}
536
+ readOnly={readOnly}
537
+ expandable={expandable}
538
+ handleClear={handleClear}
539
+ countOnly={countOnly}
540
+ />
541
+ {/* Displaying Loading Spinner */}
542
+
543
+ {/* Suggestions Dropdown */}
544
+
545
+ {dropOpen &&
546
+ ReactDOM.createPortal(
547
+ <div
548
+ ref={dropRef}
549
+ style={{ ...dropdownStyle, minHeight: 192 }}
550
+ className={`qbs-autocomplete-suggestions qbs-autocomplete-selected-suggestions`}
551
+ >
552
+ <>{tab.length > 0 && getTabItems()}</>
553
+ {type == "auto_suggestion" && !expandable && (
554
+ <div
555
+ style={{ position: "relative" }}
556
+ className="react-core-ts-search-container"
557
+ >
558
+ <span className="dropdown-search-icon">
559
+ <Search />
560
+ </span>
561
+ <input
562
+ className="dropdown-search-input"
563
+ onChange={handleSuggestionChange}
564
+ value={searchValue}
565
+ placeholder="Search"
566
+ />
567
+ </div>
568
+ )}
569
+
570
+ <div className={`qbs-autocomplete-suggestions-sub `}>
571
+ {filteredData?.length > 0 ? (
572
+ filteredData.map((suggestion: ValueProps, idx: number) => (
573
+ <DropdownList
574
+ idx={idx}
575
+ suggestion={suggestion}
576
+ isSelected={isSelected}
577
+ handleSuggestionClick={handleSuggestionClick}
578
+ handleMultiSelect={handleMultiSelect}
579
+ selected={selected}
580
+ isMultiple={isMultiple}
581
+ singleSelect={singleSelect}
582
+ desc={desc}
583
+ key={suggestion[descId]}
584
+ />
585
+ ))
586
+ ) : (
587
+ <>
588
+ {isLoading ||
589
+ (searchValue !== searchOldValue &&
590
+ searchValue !== "" &&
591
+ async) ? (
592
+ <div className="qbs-flex qbs-align-middle qbs-justify-center qbs-min-emp-h qbs-py-80">
593
+ <div className="qbs-pt-16">
594
+ <Spinner />
595
+ </div>
596
+ </div>
597
+ ) : (
598
+ <div className="qbs-autocomplete-notfound qbs-text-center qbs-justify-center qbs-align-middle qbs-min-emp-h qbs-py-80">
599
+ {searchValue !== ""
600
+ ? notDataMessage ?? "No Results Found"
601
+ : initialDataMessage ?? "Type to search"}
602
+ </div>
603
+ )}
604
+ </>
605
+ )}
606
+ {paginationEnabled &&
607
+ nextBlock !== 0 &&
608
+ nextBlock !== undefined &&
609
+ filteredData?.length > 0 && (
610
+ <div
611
+ className="loadMoreSection"
612
+ onClick={() => handleLoadMore()}
613
+ >
614
+ <p style={{ margin: 2 }}>Load More</p>
615
+ </div>
616
+ )}
617
+ </div>
618
+ <>
619
+ {countOnly && selectedItems?.length > 0 && (
620
+ <>
621
+ <div
622
+ className={`qbs-expandable-container qbs-dropdown-selected-container`}
623
+ ref={dropdownSelectedRef}
624
+ >
625
+ <div className="qbs-flex qbs-w-full qbs-text-xs ">
626
+ <div className="qbs-selected-lbs qbs-flex-grow">
627
+ Selected Items
628
+ </div>
629
+ <div
630
+ className="qbs-clear-link qbs-text-right qbs-cursor-pointer"
631
+ onClick={handleClearSelected}
632
+ >
633
+ Clear
634
+ </div>
635
+ </div>
636
+
637
+ {getSelectedItems(true)}
638
+
639
+ <>
640
+ {selectedItems?.length > itemCount &&
641
+ showAllSelected && (
642
+ <div className="qbs-readmore-collapse">
643
+ <div
644
+ className="qbs-more-collapse"
645
+ onClick={() => {
646
+ setShowAllSelected(false);
647
+ }}
648
+ >
649
+ <DropArrow className={`icon-button-rotate`} />
650
+ </div>
651
+ </div>
652
+ )}
653
+ </>
654
+ </div>
655
+ </>
656
+ )}
657
+ </>
658
+ </div>,
659
+ document.body
660
+ )}
661
+ </div>
662
+
663
+ {/* Displaying Validation Error */}
664
+ {errors && (
665
+ <div
666
+ className="text-error text-error-label mt-[1px]"
667
+ data-testid="autocomplete-error"
668
+ >
669
+ {errors.message}
670
+ </div>
671
+ )}
672
+ </div>
673
+ );
674
+ }
675
+ );
676
+
677
+ export default React.memo(AutoCompleteWithSelectedList);
@@ -135,14 +135,22 @@ const ExpandableAutoComplete = forwardRef<
135
135
  // Handling the selection of a suggestion
136
136
  const handleSuggestionClick = useCallback((suggestion: ValueProps) => {
137
137
  if (isMultiple) {
138
- setSelectedItems((prev) => [...prev, suggestion]);
138
+ setSelectedItems((prev) => {
139
+ const isAdded = prev.some(
140
+ (item) => item[descId] === suggestion[descId]
141
+ );
142
+ if (isAdded) {
143
+ return prev.filter((item) => item[descId] !== suggestion[descId]);
144
+ } else {
145
+ return [...prev, suggestion];
146
+ }
147
+ });
139
148
  } else {
140
149
  setInputValue(suggestion[desc]);
150
+ setSearchValue("");
151
+ onChange(suggestion);
152
+ setDropOpen(false);
141
153
  }
142
- setSearchValue("");
143
- setInputValue(suggestion[desc]);
144
- onChange(suggestion);
145
- setDropOpen(false);
146
154
  }, []);
147
155
 
148
156
  // Adding debounce to avoid making API calls on every keystroke