react-core-ts 2.0.23 → 2.1.0

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.
@@ -144,6 +144,7 @@ const DropdownFilterTabs = forwardRef<
144
144
  const [isKeyboardOpen, setIsKeyboardOpen] = useState(false);
145
145
  const [hasMoreOptionValues, setHasMoreOptionValues] = useState(false);
146
146
  const [tabsScrollable, setTabsScrollable] = useState(false);
147
+ const scrollableDivsRef = useRef<NodeListOf<Element> | null>(null);
147
148
 
148
149
  // Use external reset key if provided, otherwise default to 0
149
150
  const currentResetKey = externalResetKey !== undefined ? externalResetKey : 0;
@@ -170,9 +171,16 @@ const DropdownFilterTabs = forwardRef<
170
171
  setActiveTab(currentTab);
171
172
  }, [currentTab]);
172
173
 
174
+ // Reset focused index when active tab changes
175
+ useEffect(() => {
176
+ setFocusedIndex(0);
177
+ }, [activeTab]);
178
+
173
179
  const adjustDropdownPosition = () => {
174
- if (dropdownRef?.current) {
175
- const inputRect = dropdownRef?.current?.getBoundingClientRect();
180
+ // Use requestAnimationFrame to ensure DOM is ready
181
+ requestAnimationFrame(() => {
182
+ if (dropdownRef?.current) {
183
+ const inputRect = dropdownRef?.current?.getBoundingClientRect();
176
184
  const dropdownPosition = {
177
185
  left: inputRect.left + window.scrollX,
178
186
  width: inputRect.width,
@@ -248,14 +256,14 @@ const DropdownFilterTabs = forwardRef<
248
256
  // setDropdownStyle({
249
257
  // ...dropdownPosition,
250
258
  // });
251
- setDropdownStyle((prevStyle) => ({
252
- ...prevStyle,
253
- transform: `translateY(${dropdownPosition.top}px)`,
254
- left: desiredLeft,
255
- width: desiredWidth,
256
- }));
257
-
258
- }
259
+ setDropdownStyle((prevStyle) => ({
260
+ ...prevStyle,
261
+ transform: `translateY(${dropdownPosition.top}px)`,
262
+ left: desiredLeft,
263
+ width: desiredWidth,
264
+ }));
265
+ }
266
+ });
259
267
  };
260
268
 
261
269
  useEffect(() => {
@@ -291,18 +299,40 @@ const DropdownFilterTabs = forwardRef<
291
299
  setSelectedItems((prev) => {
292
300
  const isAdded =
293
301
  prev && prev.length > 0
294
- ? prev.some(
295
- (item) =>
302
+ ? prev.some((item) => {
303
+ const sameId =
296
304
  getKeyValue(item, descId, 'id') ===
297
- getKeyValue(suggestion, descId, 'id')
298
- )
305
+ getKeyValue(suggestion, descId, 'id');
306
+
307
+ if (!sameId) return false;
308
+
309
+ // If there is no tab context, treat any same-id item as already added
310
+ if (
311
+ currentTabId === undefined ||
312
+ item?.tabId === undefined
313
+ ) {
314
+ return true;
315
+ }
316
+
317
+ // With tab context, only consider items from the current tab
318
+ return item.tabId === currentTabId;
319
+ })
299
320
  : false;
300
321
  if (isAdded) {
301
- return prev.filter(
302
- (item) =>
303
- getKeyValue(item, descId, 'id') !==
304
- getKeyValue(suggestion, descId, 'id')
305
- );
322
+ // Remove only the item for the current tab (if tabId is used)
323
+ return prev.filter((item) => {
324
+ const sameId =
325
+ getKeyValue(item, descId, 'id') ===
326
+ getKeyValue(suggestion, descId, 'id');
327
+
328
+ if (!sameId) return true;
329
+
330
+ if (currentTabId === undefined || item?.tabId === undefined) {
331
+ return false;
332
+ }
333
+
334
+ return item.tabId !== currentTabId;
335
+ });
306
336
  } else {
307
337
  // Include tab ID when adding suggestion
308
338
  const suggestionWithTabId = {
@@ -350,12 +380,23 @@ const DropdownFilterTabs = forwardRef<
350
380
  };
351
381
  setSelectedItems((prev) => [...prev, suggestionWithTabId]);
352
382
  } else {
383
+ // When unchecking, only remove the item for the current tab (if tabId is used)
353
384
  setSelectedItems((prev) => {
354
- return prev.filter(
355
- (item, i) =>
356
- getKeyValue(item, descId, 'id') !==
357
- getKeyValue(suggestion, descId, 'id')
358
- );
385
+ return prev.filter((item) => {
386
+ const sameId =
387
+ getKeyValue(item, descId, 'id') ===
388
+ getKeyValue(suggestion, descId, 'id');
389
+
390
+ if (!sameId) return true;
391
+
392
+ // If there is no tab context, behave as before (remove by id)
393
+ if (currentTabId === undefined || item?.tabId === undefined) {
394
+ return false;
395
+ }
396
+
397
+ // With tab context, only remove the item that belongs to the current tab
398
+ return item.tabId !== currentTabId;
399
+ });
359
400
  });
360
401
  }
361
402
  } else {
@@ -519,22 +560,44 @@ const DropdownFilterTabs = forwardRef<
519
560
  // Capture-phase scroll catches scrolls from any scroll container (scroll doesn't bubble).
520
561
  document.addEventListener('scroll', handleScroll as any, true);
521
562
 
522
- const scrollableDivs = document.querySelectorAll(
523
- 'div[style*="overflow"], .overflow-auto, .overflow-y-auto, .overflow-x-auto'
524
- );
525
- scrollableDivs.forEach((div) =>
563
+ // Query scrollable divs and store in ref for cleanup
564
+ // Re-query when dropdown opens to catch dynamically added containers
565
+ const queryScrollableDivs = () => {
566
+ return document.querySelectorAll(
567
+ 'div[style*="overflow"], .overflow-auto, .overflow-y-auto, .overflow-x-auto'
568
+ );
569
+ };
570
+
571
+ // Clean up previous listeners if any
572
+ if (scrollableDivsRef.current) {
573
+ scrollableDivsRef.current.forEach((div) =>
574
+ div.removeEventListener('scroll', handleScroll as any)
575
+ );
576
+ }
577
+
578
+ // Query and attach listeners to scrollable divs
579
+ scrollableDivsRef.current = queryScrollableDivs();
580
+ scrollableDivsRef.current.forEach((div) =>
526
581
  div.addEventListener('scroll', handleScroll as any)
527
582
  );
583
+
528
584
  if (scrollRef && scrollRef.current && scrollRef.current !== null) {
529
585
  scrollRef.current.addEventListener('scroll', handleScroll as any);
530
586
  }
587
+
531
588
  return () => {
532
589
  document.removeEventListener('mousedown', handleClickOutside as any);
533
590
  window.removeEventListener('scroll', handleScroll as any);
534
591
  document.removeEventListener('scroll', handleScroll as any, true);
535
- scrollableDivs.forEach((div) =>
536
- div.removeEventListener('scroll', handleScroll as any)
537
- );
592
+
593
+ // Clean up scrollable divs listeners
594
+ if (scrollableDivsRef.current) {
595
+ scrollableDivsRef.current.forEach((div) =>
596
+ div.removeEventListener('scroll', handleScroll as any)
597
+ );
598
+ scrollableDivsRef.current = null;
599
+ }
600
+
538
601
  if (closeOnScrollTimeoutRef.current) {
539
602
  window.clearTimeout(closeOnScrollTimeoutRef.current);
540
603
  closeOnScrollTimeoutRef.current = null;
@@ -549,7 +612,10 @@ const DropdownFilterTabs = forwardRef<
549
612
  }, [dropOpen, typeOnlyFetch, uniqueDropArrowId, onToolClose]);
550
613
 
551
614
  // Filtering suggestions based on type and search value
552
- const selected: any = isMultiple ? selectedItems : inputValue;
615
+ // Use filteredSelectedItems when tabs are present, otherwise use selectedItems
616
+ const selected: any = isMultiple
617
+ ? (tab.length > 0 ? filteredSelectedItems : selectedItems)
618
+ : inputValue;
553
619
  const filteredData: ValueProps[] = filterSuggestions(
554
620
  suggestions,
555
621
  searchValue,
@@ -574,18 +640,22 @@ const DropdownFilterTabs = forwardRef<
574
640
  prevSelectedItemsRef.current = selectedItems;
575
641
  }
576
642
  }
577
- adjustDropdownPosition();
578
- setTimeout(() => {
579
- adjustDropdownPosition();
580
- }, 200);
643
+ // Use double requestAnimationFrame to ensure DOM updates are complete
644
+ requestAnimationFrame(() => {
645
+ requestAnimationFrame(() => {
646
+ adjustDropdownPosition();
647
+ });
648
+ });
581
649
  // eslint-disable-next-line react-hooks/exhaustive-deps
582
650
  }, [selectedItems]);
583
651
 
584
652
  useEffect(() => {
585
- adjustDropdownPosition();
586
- setTimeout(() => {
587
- adjustDropdownPosition();
588
- }, 200);
653
+ // Use double requestAnimationFrame to ensure DOM updates are complete
654
+ requestAnimationFrame(() => {
655
+ requestAnimationFrame(() => {
656
+ adjustDropdownPosition();
657
+ });
658
+ });
589
659
  // eslint-disable-next-line react-hooks/exhaustive-deps
590
660
  }, [filteredData]);
591
661
 
@@ -648,23 +718,35 @@ const DropdownFilterTabs = forwardRef<
648
718
  };
649
719
  }, [filteredData, dropOpen, isLoading, focusedIndex]);
650
720
 
651
- const isSelected = (
721
+ const isSelected = useCallback((
652
722
  item: ValueProps,
653
723
  selectedItems: ValueProps[] | string
654
724
  ): boolean => {
655
725
  if (Array.isArray(selectedItems)) {
656
- return selectedItems.some(
657
- (selectedItem) =>
658
- getKeyValue(selectedItem, desc, 'name') === getKeyValue(item, desc, 'name') ||
659
- getKeyValue(selectedItem, descId, 'id') === getKeyValue(item, descId, 'id')
660
- );
726
+ return selectedItems.some((selectedItem) => {
727
+ // When tabs are present, ensure the selected item belongs to the current tab
728
+ if (tab.length > 0 && currentTabId !== undefined) {
729
+ // Only match if the selectedItem's tabId matches the current tab
730
+ // If selectedItem doesn't have tabId, don't match it (it belongs to no tab or old data)
731
+ if (selectedItem?.tabId === undefined || selectedItem.tabId !== currentTabId) {
732
+ return false;
733
+ }
734
+ }
735
+
736
+ return (
737
+ getKeyValue(selectedItem, desc, 'name') ===
738
+ getKeyValue(item, desc, 'name') ||
739
+ getKeyValue(selectedItem, descId, 'id') ===
740
+ getKeyValue(item, descId, 'id')
741
+ );
742
+ });
661
743
  } else {
662
744
  return (
663
745
  getKeyValue(item, desc, 'name') === safeToLowerString(selectedItems) ||
664
746
  getKeyValue(item, descId, 'id') === safeToLowerString(selectedItems)
665
747
  );
666
748
  }
667
- };
749
+ }, [tab, currentTabId, desc, descId]);
668
750
 
669
751
  const handleLoadMore = () => {
670
752
  if (paginationEnabled) {
@@ -716,6 +798,7 @@ const DropdownFilterTabs = forwardRef<
716
798
  handlePickSuggestions(searchValue, 1, false, activeTabVal);
717
799
  }
718
800
  setActiveTab(index);
801
+ setFocusedIndex(0); // Reset focused index when switching tabs
719
802
  }
720
803
  }, [activeTab, clearTabSwitch, handleClearSelected, tabInlineSearch, tab, searchValue, handlePickSuggestions, resetSuggections]);
721
804
 
package/src/index.tsx CHANGED
@@ -10,6 +10,7 @@ export { default as AutoCompleteWithTreeStructure } from './AutoCompleteWithTree
10
10
  export type { AutoSuggestionInputProps } from './commontypes';
11
11
  export { default as ExpandableAutoComplete } from './ExpandableAutoComplete';
12
12
  export { default as ModernAutoComplete } from './ReactAutoComplete';
13
+ export { default as ModernAutoCompleteSuggections } from './ReactAutoCompleteSuggections';
13
14
  export { default as ModernCheckBox } from './ReactCheckbox';
14
15
  export { default as ModernOtpInput } from './ReactOTPInput';
15
16
  export { default as ModernRadio } from './ReactRadioButton';