react-native-auto-positioned-popup 1.0.9 → 1.0.11

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.
@@ -14,15 +14,14 @@ import React, {
14
14
  import {
15
15
  Dimensions,
16
16
  Keyboard,
17
+ Platform,
18
+ StatusBar,
17
19
  Text,
18
20
  TextInput as RNTextInput,
19
21
  TouchableOpacity,
20
22
  View,
21
23
  } from 'react-native';
22
- // @ts-ignore - Skip type checking for third-party library with type issues
23
- import {AdvancedFlatList as AdvancedFlatListLib} from 'react-native-advanced-flatlist';
24
- // @ts-ignore - Direct import from source when using fake data
25
- import AdvancedFlatListSource from 'react-native-advanced-flatlist';
24
+ import {AdvancedFlatList, ListData, FetchDataParams} from 'react-native-advanced-flatlist';
26
25
  import {TextInputSubmitEditingEventData} from 'react-native/Libraries/Components/TextInput/TextInput';
27
26
  import {LayoutRectangle, NativeSyntheticEvent} from 'react-native/Libraries/Types/CoreEventTypes';
28
27
  import {AutoPositionedPopupProps, Data, SelectedItem} from './AutoPositionedPopupProps';
@@ -209,10 +208,7 @@ const AutoPositionedPopupList: React.FC<AutoPositionedPopupListProps> = memo(
209
208
  const _fetchData = async ({
210
209
  pageIndex,
211
210
  pageSize: currentPageSize,
212
- }: {
213
- pageIndex: number;
214
- pageSize: number;
215
- }): Promise<Data | null> => {
211
+ }: FetchDataParams): Promise<ListData | null> => {
216
212
  console.log('AutoPositionedPopupList _fetchData pageIndex=', pageIndex, ' pageSize=', currentPageSize);
217
213
  console.log('AutoPositionedPopupList _fetchData state.localData=', state.localData);
218
214
  console.log('AutoPositionedPopupList _fetchData ref_searchQuery.current=', ref_searchQuery.current);
@@ -243,7 +239,15 @@ const AutoPositionedPopupList: React.FC<AutoPositionedPopupListProps> = memo(
243
239
  };
244
240
  });
245
241
  }
246
- return Promise.resolve(res);
242
+ // Convert Data to ListData if needed
243
+ if (res) {
244
+ return Promise.resolve({
245
+ items: res.items as any[], // Convert to ListItem array
246
+ pageIndex: res.pageIndex,
247
+ needLoadMore: res.needLoadMore,
248
+ });
249
+ }
250
+ return null;
247
251
  } catch (e) {
248
252
  console.warn('Error in fetchData:', e);
249
253
  }
@@ -258,19 +262,18 @@ const AutoPositionedPopupList: React.FC<AutoPositionedPopupListProps> = memo(
258
262
  );
259
263
  return useMemo(() => {
260
264
  console.log('AutoPositionedPopupList (global as any)?.$fake=', (global as any)?.$fake);
261
- // Choose AdvancedFlatList version based on global.$fake
262
- const AdvancedFlatListComponent = (global as any)?.$fake ? AdvancedFlatListSource : AdvancedFlatListLib;
263
-
265
+ // Babel configuration handles the path redirection based on global.$fake
266
+ // No need for conditional import here
264
267
  return (
265
268
  <View style={[styles.baseModalView, styles.autoPositionedPopupList]}>
266
- {/* @ts-ignore - Type assertion to bypass third-party library type issues */}
267
- <AdvancedFlatListComponent
269
+ <AdvancedFlatList
268
270
  style={[{borderRadius: 0}]}
269
- {...(ref_list && { ref: ref_list })}
270
- keyExtractor={keyExtractor}
271
+ {...(ref_list && {ref: ref_list})}
272
+ keyExtractor={(item, index) => keyExtractor ? keyExtractor(item as SelectedItem) : (item as SelectedItem).id}
271
273
  keyboardShouldPersistTaps={'always'}
272
- {...({ fetchData: _fetchData })}
273
- renderItem={renderItem || _renderItem}
274
+ fetchData={_fetchData}
275
+ renderItem={renderItem ? ({item, index}) => renderItem({item: item as SelectedItem, index}) : ({item, index}) => _renderItem({item: item as SelectedItem, index})}
276
+ showListEmptyComponent={false}
274
277
  />
275
278
  </View>
276
279
  );
@@ -301,9 +304,7 @@ const listLayout = {
301
304
  };
302
305
 
303
306
  // Main AutoPositionedPopup component
304
- const AutoPositionedPopup: MemoExoticComponent<
305
- ForwardRefExoticComponent<AutoPositionedPopupProps>
306
- > = memo(
307
+ const AutoPositionedPopup = memo(
307
308
  forwardRef<unknown, AutoPositionedPopupProps>(
308
309
  (props: AutoPositionedPopupProps, parentRef: ForwardedRef<unknown>): React.JSX.Element => {
309
310
  console.log('AutoPositionedPopup props=', props);
@@ -370,9 +371,9 @@ const AutoPositionedPopup: MemoExoticComponent<
370
371
  const hasAddedRootView = useRef(false);
371
372
  const hasShownRootView = useRef(false);
372
373
  // Additional refs for keyboard and position tracking
373
- const ref_isFocus = useRef<boolean>();
374
- const ref_isKeyboardFullyShown = useRef<boolean>();
375
- const ref_listPos: MutableRefObject<any> = useRef<LayoutRectangle>()
374
+ const ref_isFocus = useRef<boolean>(false);
375
+ const ref_isKeyboardFullyShown = useRef<boolean>(false);
376
+ const ref_listPos: MutableRefObject<any> = useRef<LayoutRectangle | undefined>(undefined)
376
377
  const keyboardVisibleRef = useRef(false);
377
378
  const refAutoPositionedPopup = useRef<View>(null);
378
379
  const ref_searchQuery = useRef<string>('');
@@ -510,96 +511,218 @@ const AutoPositionedPopup: MemoExoticComponent<
510
511
  if (state.isFocus) {
511
512
  refAutoPositionedPopup.current?.measureInWindow((x: number, y: number, width: number, height: number) => {
512
513
  console.log('AutoPositionedPopup measureInWindow x=', x, ' y=', y, ' width=', width, ' height=', height);
513
-
514
- // INTELLIGENT POSITION CALCULATION
514
+
515
+ // INTELLIGENT POSITION CALCULATION - MODIFIED VERSION WITH STATUS BAR SAFETY
515
516
  const calculateOptimalPosition = (componentY: number, componentHeight: number, popupHeight: number) => {
517
+ console.log('🔥🔥🔥 NEW CALCULATE OPTIMAL POSITION FUNCTION EXECUTING 🔥🔥🔥');
518
+
516
519
  // Use window height (visible area) instead of screen height (includes status bar)
517
520
  const windowHeight = Dimensions.get('window').height;
518
521
  const visibleAreaCenter = windowHeight / 2;
519
-
522
+
523
+ // Cross-platform status bar height handling
524
+ const getStatusBarHeight = () => {
525
+ if (Platform.OS === 'android') {
526
+ return StatusBar.currentHeight || 24; // Android default
527
+ } else {
528
+ // iOS status bar heights vary by device
529
+ const {height: screenHeight} = Dimensions.get('screen');
530
+ const {height: windowHeightLocal} = Dimensions.get('window');
531
+ return screenHeight - windowHeightLocal; // Safe area top
532
+ }
533
+ };
534
+ const statusBarHeight = getStatusBarHeight();
535
+ console.log('🔥 Cross-platform StatusBar height:', statusBarHeight, 'Platform:', Platform.OS);
536
+
520
537
  // Calculate component center point as requested
521
538
  const componentCenterY = componentY + componentHeight / 2;
522
-
539
+
523
540
  console.log('AutoPositionedPopup positioning data:', {
524
541
  windowHeight,
525
542
  visibleAreaCenter,
526
543
  componentCenterY,
527
544
  componentY,
528
545
  componentHeight,
529
- popupHeight
546
+ popupHeight,
547
+ statusBarHeight
530
548
  });
531
-
549
+
532
550
  let showAbove = false;
533
- let finalY = componentY + componentHeight; // Default: show below
534
-
535
- // Primary logic: use component center point vs visible area center
536
- if (componentCenterY > visibleAreaCenter) {
537
- // Component center is in lower half, prefer showing above
538
- const spaceAbove = componentY;
539
- if (spaceAbove >= popupHeight) {
540
- showAbove = true;
541
- finalY = componentY - popupHeight;
542
- } else {
543
- // Not enough space above, check if we can fit below
544
- const spaceBelow = windowHeight - (componentY + componentHeight);
545
- if (spaceBelow >= popupHeight) {
546
- // Show below if there's space
547
- finalY = componentY + componentHeight;
548
- } else {
549
- // Force above with clipping
550
- showAbove = true;
551
- finalY = Math.max(0, componentY - popupHeight);
552
- }
553
- }
551
+ let finalY = componentY + componentHeight; // Default fallback: show below
552
+
553
+ // CORRECTED LOGIC: Calculate actual usable space considering status bar
554
+ const rawSpaceAbove = componentY;
555
+ const spaceBelow = windowHeight - (componentY + componentHeight);
556
+ // Actual usable space above must account for status bar
557
+ const usableSpaceAbove = componentY - statusBarHeight;
558
+
559
+ console.log('🔥 AutoPositionedPopup CORRECTED SPACE CALCULATION (pre-spacing):', {
560
+ rawSpaceAbove,
561
+ usableSpaceAbove,
562
+ spaceBelow,
563
+ popupHeight,
564
+ componentY,
565
+ componentHeight,
566
+ windowHeight,
567
+ statusBarHeight
568
+ });
569
+
570
+ // ULTRA-TIGHT SPACING: Minimal spacing for tight visual connection
571
+ const getOptimalSpacing = (compY: number, compHeight: number, winHeight: number) => {
572
+ const componentCenter = compY + compHeight / 2;
573
+ const screenCenter = winHeight / 2;
574
+ const distanceFromCenter = Math.abs(componentCenter - screenCenter) / screenCenter;
575
+
576
+ // Check if component is in bottom half for ultra-tight spacing
577
+ const isInBottomHalf = componentCenter > screenCenter;
578
+
579
+ // Base spacing: extremely small for bottom components
580
+ const baseSpacing = isInBottomHalf ? 0.5 : 3;
581
+
582
+ // Aggressive spacing reduction for edge positions - ultra tight for bottom half
583
+ const edgeProximityFactor = isInBottomHalf ?
584
+ Math.max(0.15, 1 - distanceFromCenter * 1.2) :
585
+ Math.max(0.4, 1 - distanceFromCenter * 0.7);
586
+
587
+ // Minimal component-relative spacing for bottom components
588
+ const relativeSpacingPercent = isInBottomHalf ? 0.02 : 0.12;
589
+ const relativeSpacing = Math.min(compHeight * relativeSpacingPercent, isInBottomHalf ? 3 : 10);
590
+
591
+ // Strong platform adjustment - much smaller for Android bottom components
592
+ const platformMultiplier = Platform.OS === 'ios' ? 1.0 : (isInBottomHalf ? 0.5 : 0.9);
593
+
594
+ const finalSpacing = Math.max(baseSpacing, relativeSpacing) * edgeProximityFactor * platformMultiplier;
595
+
596
+ console.log('🔥 Advanced spacing calculation:', {
597
+ componentCenter,
598
+ screenCenter,
599
+ distanceFromCenter,
600
+ isInBottomHalf,
601
+ edgeProximityFactor,
602
+ baseSpacing,
603
+ relativeSpacing,
604
+ relativeSpacingPercent,
605
+ platformMultiplier,
606
+ finalSpacing
607
+ });
608
+
609
+ return finalSpacing;
610
+ };
611
+ // const POPUP_SPACING = getOptimalSpacing(componentY, componentHeight, windowHeight);
612
+ // console.log('🔥 Optimal popup spacing calculated:', POPUP_SPACING, 'for componentHeight:', componentHeight, 'at Y:', componentY);
613
+
614
+ // console.log('🔥 AutoPositionedPopup FINAL SPACE CHECK WITH SPACING:', {
615
+ // POPUP_SPACING,
616
+ // 'usableSpaceAbove >= popupHeight + POPUP_SPACING': usableSpaceAbove >= popupHeight + POPUP_SPACING,
617
+ // 'spaceBelow >= popupHeight + POPUP_SPACING': spaceBelow >= popupHeight + POPUP_SPACING,
618
+ // 'usableSpaceAbove needed': popupHeight + POPUP_SPACING,
619
+ // 'spaceBelow needed': popupHeight + POPUP_SPACING
620
+ // });
621
+
622
+ // FORCE ABOVE PRIORITY: If component is in bottom half, always try above first
623
+ const isInBottomHalf = componentCenterY > visibleAreaCenter;
624
+
625
+ // console.log('🔥 Position decision factors:', {
626
+ // isInBottomHalf,
627
+ // componentCenterY,
628
+ // visibleAreaCenter,
629
+ // 'spaceBelow >= needed': spaceBelow >= popupHeight + POPUP_SPACING,
630
+ // 'usableSpaceAbove >= needed': usableSpaceAbove >= popupHeight + POPUP_SPACING
631
+ // });
632
+
633
+ if (isInBottomHalf && usableSpaceAbove >= popupHeight ) {
634
+ // Component in bottom half + enough space above = FORCE ABOVE
635
+ showAbove = true;
636
+ finalY = componentY - popupHeight +componentHeight/2;
637
+ console.log('🔥 AutoPositionedPopup: FORCE ABOVE - bottom half component with enough space, finalY=', finalY);
638
+ } else if (!isInBottomHalf && spaceBelow >= popupHeight ) {
639
+ // Component in top half + enough space below = show below
640
+ showAbove = false;
641
+ finalY = componentY + componentHeight*2;
642
+ console.log('🔥 AutoPositionedPopup: Showing below - top half component with enough space, finalY=', finalY);
643
+ } else if (usableSpaceAbove >= popupHeight ) {
644
+ // Fallback: enough space above
645
+ showAbove = true;
646
+ finalY = componentY - popupHeight ;
647
+ console.log('🔥 AutoPositionedPopup: Showing above - enough space available (fallback), finalY=', finalY);
648
+ } else if (spaceBelow >= popupHeight ) {
649
+ // Fallback: enough space below
650
+ showAbove = false;
651
+ finalY = componentY + componentHeight ;
652
+ console.log('🔥 AutoPositionedPopup: Showing below - enough space available (fallback), finalY=', finalY);
554
653
  } else {
555
- // Component center is in upper half, prefer showing below
556
- const spaceBelow = windowHeight - (componentY + componentHeight);
557
- if (spaceBelow >= popupHeight) {
558
- // Show below
559
- finalY = componentY + componentHeight;
654
+ // Emergency fallback: choose larger space
655
+ if (usableSpaceAbove >= spaceBelow) {
656
+ showAbove = true;
657
+ finalY = Math.max(statusBarHeight, componentY - popupHeight );
658
+ console.log('🔥 AutoPositionedPopup: Emergency above - larger space, finalY=', finalY);
560
659
  } else {
561
- // Not enough space below, try above
562
- const spaceAbove = componentY;
563
- if (spaceAbove >= popupHeight) {
564
- showAbove = true;
565
- finalY = componentY - popupHeight;
566
- } else {
567
- // Force below with clipping
568
- finalY = Math.min(windowHeight - popupHeight, componentY + componentHeight);
569
- }
660
+ showAbove = false;
661
+ finalY = componentY + componentHeight ;
662
+ console.log('🔥 AutoPositionedPopup: Emergency below - larger space, finalY=', finalY);
570
663
  }
571
664
  }
572
-
573
- // Final boundary check to ensure popup stays within visible area
574
- if (finalY < 0) {
575
- finalY = 0;
665
+
666
+ // Enhanced boundary check with detailed logging
667
+ console.log('🔥 Pre-boundary check:', {
668
+ originalFinalY: finalY,
669
+ showAbove,
670
+ statusBarHeight,
671
+ windowHeight,
672
+ popupHeight,
673
+ 'finalY < statusBarHeight': finalY < statusBarHeight,
674
+ 'finalY + popupHeight > windowHeight': finalY + popupHeight > windowHeight
675
+ });
676
+
677
+ if (showAbove && finalY < statusBarHeight) {
678
+ const oldFinalY = finalY;
679
+ finalY = statusBarHeight;
680
+ console.log('🔥 BOUNDARY FIX: Above display adjusted for status bar:', oldFinalY, '->', finalY);
576
681
  }
577
- if (finalY + popupHeight > windowHeight) {
682
+
683
+ if (!showAbove && finalY + popupHeight > windowHeight) {
684
+ const oldFinalY = finalY;
578
685
  finalY = windowHeight - popupHeight;
686
+ console.log('🔥 BOUNDARY FIX: Below display adjusted to fit window:', oldFinalY, '->', finalY);
687
+ }
688
+
689
+ // CRITICAL CHECK: Detect if boundary check is changing display direction
690
+ if (showAbove && finalY + popupHeight > componentY ) {
691
+ console.log('🚨 WARNING: Above positioning may overlap with component!');
579
692
  }
580
-
581
- return { finalY, showAbove };
693
+
694
+ if (!showAbove && finalY < componentY + componentHeight ) {
695
+ console.log('🚨 WARNING: Below positioning may overlap with component!');
696
+ }
697
+
698
+ console.log('🔥 Post-boundary check final result:', {
699
+ finalY,
700
+ showAbove,
701
+ 'popupTop': finalY,
702
+ 'popupBottom': finalY + popupHeight,
703
+ 'componentTop': componentY,
704
+ 'componentBottom': componentY + componentHeight
705
+ });
706
+
707
+ return {finalY, showAbove};
582
708
  };
583
-
584
- const positionResult = calculateOptimalPosition(y, height, listLayout.height);
585
- console.log('AutoPositionedPopup position result:', positionResult);
586
-
709
+
710
+ // Calculate position ONCE based on actual popup height
711
+ const actualPopupHeight = CustomPopView && CustomPopViewStyle && typeof CustomPopViewStyle.height === 'number'
712
+ ? CustomPopViewStyle.height
713
+ : listLayout.height;
714
+
715
+ console.log('🔥 Using actualPopupHeight for calculation:', actualPopupHeight, 'CustomPopView:', !!CustomPopView);
716
+
717
+ const positionResult = calculateOptimalPosition(y, height, actualPopupHeight);
718
+ console.log('AutoPositionedPopup FINAL position result:', positionResult);
719
+
587
720
  ref_listPos.current = {x: x, y: positionResult.finalY, width: width};
588
721
  console.log('AutoPositionedPopup ref_listPos.current=', ref_listPos.current);
722
+
589
723
  if (CustomPopView && CustomPopViewStyle) {
590
724
  console.log('AutoPositionedPopup CustomPopViewStyle=', CustomPopViewStyle);
591
- // Ensure CustomPopViewStyle.height is a number before using it in calculations
592
- const customHeight =
593
- typeof CustomPopViewStyle.height === 'number' ? CustomPopViewStyle.height : listLayout.height;
594
-
595
- // Apply same intelligent positioning strategy for CustomPopView
596
- console.log('AutoPositionedPopup CustomPopView using intelligent positioning, customHeight=', customHeight);
597
-
598
- // Use the same intelligent positioning function for CustomPopView
599
- const customPositionResult = calculateOptimalPosition(y, height, customHeight);
600
- console.log('AutoPositionedPopup CustomPopView position result:', customPositionResult, 'tag=', tag);
601
-
602
- ref_listPos.current = {x: x, y: customPositionResult.finalY, width: width};
725
+ // Position already calculated correctly above, no need to recalculate
603
726
  const PopViewComponent = CustomPopView();
604
727
  console.log('AutoPositionedPopup addRootView PopViewComponent=', PopViewComponent);
605
728
  console.log('AutoPositionedPopup addRootView state.selectedItem=', state.selectedItem);
@@ -672,7 +795,7 @@ const AutoPositionedPopup: MemoExoticComponent<
672
795
  }
673
796
  }
674
797
  if (isKeyboardFullyShown) {
675
- ref_isFocus.current = state.isFocus;
798
+ ref_isFocus.current = state.isFocus ?? false;
676
799
  if (isKeyboardFullyShown !== keyboardVisibleRef.current) {
677
800
  keyboardVisibleRef.current = isKeyboardFullyShown;
678
801
  if (isKeyboardFullyShown && textInputRef.current) {
@@ -1,19 +1,72 @@
1
1
  declare module 'react-native-advanced-flatlist' {
2
- import { ComponentType } from 'react';
3
- import { FlatListProps, ListRenderItem } from 'react-native';
2
+ import React, { ComponentType, ReactNode } from 'react';
3
+ import { StyleProp, ViewStyle } from 'react-native';
4
4
 
5
- export interface AdvancedFlatListProps<ItemT = any> extends FlatListProps<ItemT> {
6
- renderItem?: ListRenderItem<ItemT>;
5
+ export interface ListItem {
6
+ id: string | number;
7
+ selected?: boolean;
8
+ [key: string]: any;
7
9
  }
8
10
 
9
- export const AdvancedFlatList: ComponentType<AdvancedFlatListProps>;
10
- export default AdvancedFlatList;
11
- }
11
+ export interface ListData {
12
+ items: ListItem[];
13
+ pageIndex?: number;
14
+ needLoadMore?: boolean;
15
+ }
12
16
 
13
- declare module 'react-native-advanced-flatlist' {
14
- import { ComponentType } from 'react';
15
- import { FlatListProps } from 'react-native';
17
+ export interface FetchDataParams {
18
+ pageIndex: number;
19
+ pageSize: number;
20
+ }
21
+
22
+ export interface RenderItemParams {
23
+ item: ListItem;
24
+ index: number;
25
+ selected?: boolean;
26
+ onItemPress?: (item: ListItem, index: number) => void;
27
+ }
16
28
 
17
- const AdvancedFlatListSource: ComponentType<FlatListProps<any>>;
18
- export default AdvancedFlatListSource;
29
+ export interface AdvancedFlatListProps {
30
+ tag?: string;
31
+ initialData?: Partial<ListData>;
32
+ initPageIndex?: number;
33
+ pageSize?: number;
34
+ fetchData: (params: FetchDataParams) => Promise<ListData | null>;
35
+ renderItem?: (params: RenderItemParams) => React.ReactNode;
36
+ keyExtractor?: (item: ListItem, index: number) => string;
37
+ style?: StyleProp<ViewStyle>;
38
+ ListHeaderComponent?: ReactNode;
39
+ ListEmptyComponent?: React.ComponentType<any> | React.ReactElement<unknown> | null | undefined;
40
+ autoRefresh?: boolean;
41
+ disabledRefresh?: boolean;
42
+ keyboardShouldPersistTaps?: 'always' | 'never' | 'handled' | undefined;
43
+ emptyText?: string;
44
+ showListEmptyComponent?: boolean;
45
+ onScrollBeginDrag?: (event: any) => void;
46
+ onScrollEndDrag?: (event: any) => void;
47
+ singleSelect?: boolean;
48
+ onSingleSelectChange?: (selectedItem: ListItem | null) => void;
49
+ onItemPress?: (item: ListItem, index: number) => void;
50
+ }
51
+
52
+ export interface AdvancedFlatListRef {
53
+ scrollToTop: () => void;
54
+ refresh: () => void;
55
+ stopRefresh: () => void;
56
+ getItems: () => ListItem[];
57
+ changeItemSelect: (index: number) => void;
58
+ clearSelection: () => void;
59
+ }
60
+
61
+ export interface InternalState {
62
+ items: ListItem[];
63
+ pageIndex: number;
64
+ loading: boolean;
65
+ refreshing: boolean;
66
+ needLoadMore: boolean;
67
+ selectedId: string | number | null;
68
+ }
69
+
70
+ export const AdvancedFlatList: ComponentType<AdvancedFlatListProps>;
71
+ export default AdvancedFlatList;
19
72
  }