react-native-auto-positioned-popup 1.2.16 → 1.2.17

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.
@@ -1,3 +1,15 @@
1
+ // Module load marker - unique ID for tracking code version
2
+ // DEBUG FLAG: Set to false to disable all console logs for better performance
3
+ const POPUP_DEBUG = false;
4
+ const debugLog = (...args: any[]) => {
5
+ if (POPUP_DEBUG) {
6
+ console.log(...args);
7
+ }
8
+ };
9
+
10
+ // Only log module load in debug mode
11
+ debugLog('POPUP_MODULE_V17_LOADED at ' + new Date().toISOString());
12
+
1
13
  import React, {
2
14
  ForwardedRef,
3
15
  forwardRef,
@@ -35,7 +47,7 @@ import {useKeyboardStatus} from './KeyboardManager';
35
47
  type QueryListener = (query: string) => void;
36
48
  const queryChangeListeners: QueryListener[] = [];
37
49
  const emitQueryChange = (query: string) => {
38
- console.log('AutoPositionedPopup.tsx emitQueryChange query=', query, ' listeners=', queryChangeListeners.length);
50
+ debugLog('AutoPositionedPopup.tsx emitQueryChange query=', query, ' listeners=', queryChangeListeners.length);
39
51
  queryChangeListeners.forEach((l) => l(query));
40
52
  };
41
53
  const subscribeQueryChange = (listener: QueryListener) => {
@@ -92,7 +104,7 @@ const ListItem: React.FC<{
92
104
  rootViewsRef.current = rootViews;
93
105
  }, [rootViews]);
94
106
  return useMemo(() => {
95
- // console.log('AutoPositionedPopup.tsx ListItem=', {index, item, selectedItem});
107
+ // debugLog('AutoPositionedPopup.tsx ListItem=', {index, item, selectedItem});
96
108
  const isSelected = item.id === selectedItem?.id || item.title == selectedItem?.title;
97
109
  return (
98
110
  <TouchableOpacity
@@ -102,8 +114,8 @@ const ListItem: React.FC<{
102
114
  {backgroundColor: isSelected ? (themeMode === 'light' ? 'rgba(116, 116, 128, 0.08)' : 'rgba(120, 120, 128, 0.36)') : 'transparent'},
103
115
  ]}
104
116
  onPress={() => {
105
- // console.log('AutoPositionedPopup.tsx ListItem onPress item=', item); // Commented to prevent spam
106
- // console.log('AutoPositionedPopup.tsx ListItem onPress rootViews=', rootViewsRef.current); // Commented to prevent spam
117
+ // debugLog('AutoPositionedPopup.tsx ListItem onPress item=', item); // Commented to prevent spam
118
+ // debugLog('AutoPositionedPopup.tsx ListItem onPress rootViews=', rootViewsRef.current); // Commented to prevent spam
107
119
  updateState('selectedItem', item);
108
120
  }}
109
121
  >
@@ -171,16 +183,16 @@ const AutoPositionedPopupList: React.FC<AutoPositionedPopupListProps> = memo(
171
183
  useEffect(() => {
172
184
  (async () => {
173
185
  })();
174
- console.log(`AutoPositionedPopupList componentDidMount`);
186
+ debugLog(`AutoPositionedPopupList componentDidMount`);
175
187
  //componentWillUnmount
176
188
  return () => {
177
- console.log(`AutoPositionedPopupList componentWillUnmount`);
189
+ debugLog(`AutoPositionedPopupList componentWillUnmount`);
178
190
  setSearchQuery('');
179
191
  };
180
192
  }, []);
181
193
  useEffect(() => {
182
194
  const unsubscribe = subscribeQueryChange((newQuery: string) => {
183
- console.log('AutoPositionedPopupList useEffect subscribeQueryChange newQuery=', newQuery);
195
+ debugLog('AutoPositionedPopupList useEffect subscribeQueryChange newQuery=', newQuery);
184
196
  ref_searchQuery.current = newQuery;
185
197
  if (ref_list.current) {
186
198
  ref_list.current.scrollToTop();
@@ -190,24 +202,24 @@ const AutoPositionedPopupList: React.FC<AutoPositionedPopupListProps> = memo(
190
202
  return unsubscribe;
191
203
  }, []);
192
204
  const _updateState = (key: string, value: SelectedItem) => {
193
- console.log('AutoPositionedPopupList _updateState key=', key, ' value=', value);
205
+ debugLog('AutoPositionedPopupList _updateState key=', key, ' value=', value);
194
206
  setState((prevState) => ({
195
207
  ...prevState,
196
208
  [key]: value,
197
209
  }));
198
- console.log('AutoPositionedPopupList _updateState rootViews=', rootViewsRef.current);
210
+ debugLog('AutoPositionedPopupList _updateState rootViews=', rootViewsRef.current);
199
211
  updateState(key, value);
200
212
  };
201
213
  const _fetchData = async ({
202
214
  pageIndex,
203
215
  pageSize: currentPageSize,
204
216
  }: FetchDataParams): Promise<ListData | null> => {
205
- console.log('AutoPositionedPopupList _fetchData=', {pageIndex, pageSize: currentPageSize, 'state.localData': state.localData, 'ref_searchQuery.current': ref_searchQuery.current, localSearch});
217
+ debugLog('AutoPositionedPopupList _fetchData=', {pageIndex, pageSize: currentPageSize, 'state.localData': state.localData, 'ref_searchQuery.current': ref_searchQuery.current, localSearch});
206
218
  if (localSearch && state.localData.length > 0) {
207
219
  const result: SelectedItem[] = state.localData.filter((item: SelectedItem) => {
208
220
  return `${item.title}`?.toLowerCase().includes(ref_searchQuery.current.toLowerCase());
209
221
  });
210
- console.log('AutoPositionedPopupList _fetchData localSearch result=', result);
222
+ debugLog('AutoPositionedPopupList _fetchData localSearch result=', result);
211
223
  return Promise.resolve({
212
224
  items: result,
213
225
  pageIndex: 0,
@@ -220,7 +232,7 @@ const AutoPositionedPopupList: React.FC<AutoPositionedPopupListProps> = memo(
220
232
  pageSize: pageSize || 10,
221
233
  searchQuery: ref_searchQuery.current,
222
234
  });
223
- console.log('AutoPositionedPopupList _fetchData res=', res);
235
+ debugLog('AutoPositionedPopupList _fetchData res=', res);
224
236
  if (res?.items && localSearch) {
225
237
  setState((prevState) => {
226
238
  return {
@@ -239,9 +251,9 @@ const AutoPositionedPopupList: React.FC<AutoPositionedPopupListProps> = memo(
239
251
  }
240
252
  return null;
241
253
  } catch (e) {
242
- console.warn('Error in fetchData:', e);
254
+ debugLog('Error in fetchData:', e);
243
255
  }
244
- console.log('AutoPositionedPopupList _fetchData res=', null);
256
+ debugLog('AutoPositionedPopupList _fetchData res=', null);
245
257
  return null;
246
258
  };
247
259
  const _renderItem = useCallback(
@@ -251,7 +263,7 @@ const AutoPositionedPopupList: React.FC<AutoPositionedPopupListProps> = memo(
251
263
  [state.selectedItem, themeMode]
252
264
  );
253
265
  return useMemo(() => {
254
- console.log('AutoPositionedPopupList (global as any)?.$fake=', (global as any)?.$fake);
266
+ debugLog('AutoPositionedPopupList (global as any)?.$fake=', (global as any)?.$fake);
255
267
  // Babel configuration handles the path redirection based on global.$fake
256
268
  // No need for conditional import here
257
269
  return (
@@ -298,7 +310,7 @@ const listLayout = {
298
310
  const AutoPositionedPopup = memo(
299
311
  forwardRef<unknown, AutoPositionedPopupProps>(
300
312
  (props: AutoPositionedPopupProps, parentRef: ForwardedRef<unknown>): React.JSX.Element => {
301
- console.log('AutoPositionedPopup props=', props);
313
+ debugLog('AutoPositionedPopup props=', props);
302
314
  const {
303
315
  tag,
304
316
  style,
@@ -325,11 +337,11 @@ const AutoPositionedPopup = memo(
325
337
  };
326
338
  try {
327
339
  // const res1: any[] = await $api.xxx(pageSize)
328
- // console.log('${NAME} xxx res=', res)
340
+ // debugLog('${NAME} xxx res=', res)
329
341
  // res.items = res1
330
342
  // res.needLoadMore = res1.length === pageSize
331
343
  } catch (e) {
332
- console.warn('Error in fetch operation:', e);
344
+ debugLog('Error in fetch operation:', e);
333
345
  }
334
346
  return res;
335
347
  },
@@ -356,7 +368,7 @@ const AutoPositionedPopup = memo(
356
368
  selectedItem: selectedItem,
357
369
  });
358
370
  // Use RootView context
359
- const {addRootView, setRootViewNativeStyle, removeRootView, rootViews, setSearchQuery} = useRootView();
371
+ const {addRootView, setRootViewNativeStyle, updateRootView, removeRootView, rootViews, setSearchQuery} = useRootView();
360
372
  const rootViewsRef = useRef(rootViews);
361
373
  // Track TextInput focus and RootView states like project implementation
362
374
  const hasTriggeredFocus = useRef(false);
@@ -418,7 +430,7 @@ const AutoPositionedPopup = memo(
418
430
  */
419
431
  const scrollParentToTrigger = useCallback(() => {
420
432
  if (!parentScrollViewRef?.current || !triggerBtnRef.current) {
421
- console.log('AutoPositionedPopup scrollParentToTrigger: No parentScrollViewRef or triggerBtnRef available');
433
+ debugLog('AutoPositionedPopup scrollParentToTrigger: No parentScrollViewRef or triggerBtnRef available');
422
434
  return;
423
435
  }
424
436
 
@@ -428,18 +440,18 @@ const AutoPositionedPopup = memo(
428
440
  const nodeHandle = findNodeHandle(triggerBtnRef.current);
429
441
 
430
442
  if (nodeHandle && scrollView) {
431
- console.log('AutoPositionedPopup scrollParentToTrigger: Scrolling to trigger button with extraHeight=', scrollExtraHeight);
443
+ debugLog('AutoPositionedPopup scrollParentToTrigger: Scrolling to trigger button with extraHeight=', scrollExtraHeight);
432
444
 
433
445
  // KeyboardAwareScrollView has a scrollToFocusedInput method that handles this
434
446
  // However, it requires a ReactNode. We'll use scrollToPosition as an alternative.
435
447
  // First, measure the trigger button position relative to the ScrollView
436
448
  triggerBtnRef.current.measureInWindow((x, y, width, height) => {
437
449
  if (y === undefined || height === undefined) {
438
- console.log('AutoPositionedPopup scrollParentToTrigger: measureInWindow returned undefined');
450
+ debugLog('AutoPositionedPopup scrollParentToTrigger: measureInWindow returned undefined');
439
451
  return;
440
452
  }
441
453
 
442
- console.log('AutoPositionedPopup scrollParentToTrigger: trigger position=', { x, y, width, height });
454
+ debugLog('AutoPositionedPopup scrollParentToTrigger: trigger position=', { x, y, width, height });
443
455
 
444
456
  // Get keyboard height from Keyboard API
445
457
  // On keyboard show, scroll to position that keeps trigger above keyboard
@@ -451,7 +463,7 @@ const AutoPositionedPopup = memo(
451
463
  const triggerBottom = y + height;
452
464
  const visibleAreaBottom = screenHeight - keyboardHeight;
453
465
 
454
- console.log('AutoPositionedPopup scrollParentToTrigger: keyboard data=', {
466
+ debugLog('AutoPositionedPopup scrollParentToTrigger: keyboard data=', {
455
467
  keyboardHeight,
456
468
  screenHeight,
457
469
  triggerBottom,
@@ -462,7 +474,7 @@ const AutoPositionedPopup = memo(
462
474
  if (triggerBottom > visibleAreaBottom) {
463
475
  // Calculate how much to scroll
464
476
  const scrollAmount = triggerBottom - visibleAreaBottom + scrollExtraHeight;
465
- console.log('AutoPositionedPopup scrollParentToTrigger: scrolling by', scrollAmount);
477
+ debugLog('AutoPositionedPopup scrollParentToTrigger: scrolling by', scrollAmount);
466
478
 
467
479
  // Use scrollForExtraHeightOnAndroid or scrollToPosition
468
480
  if (typeof scrollView.scrollToPosition === 'function') {
@@ -470,7 +482,7 @@ const AutoPositionedPopup = memo(
470
482
  scrollView.scrollToPosition(0, scrollAmount, true);
471
483
  } else if (typeof scrollView.scrollToEnd === 'function') {
472
484
  // Fallback: scroll to end might help in some cases
473
- console.log('AutoPositionedPopup scrollParentToTrigger: using scrollToEnd fallback');
485
+ debugLog('AutoPositionedPopup scrollParentToTrigger: using scrollToEnd fallback');
474
486
  }
475
487
  }
476
488
  });
@@ -483,21 +495,21 @@ const AutoPositionedPopup = memo(
483
495
  * Uses stored trigger position (captured before TextInput replaces the trigger button)
484
496
  */
485
497
  const scrollToTriggerWithMeasure = useCallback(() => {
486
- console.log('AutoPositionedPopup scrollToTriggerWithMeasure called, tag=', tag, {
498
+ debugLog('AutoPositionedPopup scrollToTriggerWithMeasure called, tag=', tag, {
487
499
  hasParentScrollViewRef: !!parentScrollViewRef?.current,
488
500
  hasTriggerPosition: !!triggerPositionRef.current,
489
501
  triggerPosition: triggerPositionRef.current
490
502
  });
491
503
 
492
504
  if (!parentScrollViewRef?.current) {
493
- console.log('AutoPositionedPopup scrollToTriggerWithMeasure: parentScrollViewRef not available, tag=', tag);
505
+ debugLog('AutoPositionedPopup scrollToTriggerWithMeasure: parentScrollViewRef not available, tag=', tag);
494
506
  return;
495
507
  }
496
508
 
497
509
  // Use stored trigger position (captured when trigger was clicked)
498
510
  const storedPosition = triggerPositionRef.current;
499
511
  if (!storedPosition) {
500
- console.log('AutoPositionedPopup scrollToTriggerWithMeasure: no stored trigger position, tag=', tag);
512
+ debugLog('AutoPositionedPopup scrollToTriggerWithMeasure: no stored trigger position, tag=', tag);
501
513
  return;
502
514
  }
503
515
 
@@ -513,7 +525,7 @@ const AutoPositionedPopup = memo(
513
525
  const visibleAreaBottom = screenHeight - keyboardApproxHeight;
514
526
  const triggerBottom = triggerY + triggerHeight;
515
527
 
516
- console.log('AutoPositionedPopup scrollToTriggerWithMeasure: calculations=', {
528
+ debugLog('AutoPositionedPopup scrollToTriggerWithMeasure: calculations=', {
517
529
  tag,
518
530
  triggerY,
519
531
  triggerHeight,
@@ -527,7 +539,7 @@ const AutoPositionedPopup = memo(
527
539
  if (triggerBottom > visibleAreaBottom) {
528
540
  // Calculate scroll amount to bring trigger above keyboard
529
541
  const scrollAmount = triggerBottom - visibleAreaBottom + scrollExtraHeight;
530
- console.log('AutoPositionedPopup scrollToTriggerWithMeasure: scrolling, amount=', scrollAmount, 'tag=', tag);
542
+ debugLog('AutoPositionedPopup scrollToTriggerWithMeasure: scrolling, amount=', scrollAmount, 'tag=', tag);
531
543
 
532
544
  // Use scrollForExtraHeightOnAndroid for KeyboardAwareScrollView
533
545
  if (typeof scrollView.scrollForExtraHeightOnAndroid === 'function') {
@@ -538,10 +550,10 @@ const AutoPositionedPopup = memo(
538
550
  // Fallback to standard ScrollView method
539
551
  (scrollView as any).scrollTo({ y: scrollAmount, animated: true });
540
552
  } else {
541
- console.log('AutoPositionedPopup scrollToTriggerWithMeasure: no scroll method available on scrollView');
553
+ debugLog('AutoPositionedPopup scrollToTriggerWithMeasure: no scroll method available on scrollView');
542
554
  }
543
555
  } else {
544
- console.log('AutoPositionedPopup scrollToTriggerWithMeasure: trigger already visible, no scroll needed, tag=', tag);
556
+ debugLog('AutoPositionedPopup scrollToTriggerWithMeasure: trigger already visible, no scroll needed, tag=', tag);
545
557
  }
546
558
  }, [parentScrollViewRef, scrollExtraHeight, tag]);
547
559
 
@@ -551,10 +563,10 @@ const AutoPositionedPopup = memo(
551
563
  useEffect(() => {
552
564
  (async () => {
553
565
  })();
554
- console.log(`AutoPositionedPopup componentDidMount=`, {tag, CustomPopView});
566
+ debugLog(`AutoPositionedPopup componentDidMount=`, {tag, CustomPopView});
555
567
  //componentWillUnmount
556
568
  return () => {
557
- console.log(`AutoPositionedPopup componentWillUnmount tag=`, tag);
569
+ debugLog(`AutoPositionedPopup componentWillUnmount tag=`, tag);
558
570
  removeRootView(tag, forceRemoveAllRootViewOnItemSelected, rootViewsRef.current);
559
571
  setSearchQuery('');
560
572
  if (textInputRef.current) {
@@ -567,7 +579,7 @@ const AutoPositionedPopup = memo(
567
579
  };
568
580
  }, []);
569
581
  useEffect(() => {
570
- console.log('AutoPositionedPopup rootViews=', {tag, rootViews});
582
+ debugLog('AutoPositionedPopup rootViews=', {tag, rootViews});
571
583
  rootViewsRef.current = rootViews;
572
584
  if (rootViews.length === 0) {
573
585
  hasAddedRootView.current = false;
@@ -584,10 +596,10 @@ const AutoPositionedPopup = memo(
584
596
  }
585
597
  }, [rootViews]);
586
598
  useEffect(() => {
587
- console.log('AutoPositionedPopup useEffect [selectedItem, state.selectedItem, tag]=', {tag, selectedItem, 'state.selectedItem': state.selectedItem});
588
- console.log('AutoPositionedPopup useEffect state.selectedItem=', state.selectedItem);
599
+ debugLog('AutoPositionedPopup useEffect [selectedItem, state.selectedItem, tag]=', {tag, selectedItem, 'state.selectedItem': state.selectedItem});
600
+ debugLog('AutoPositionedPopup useEffect state.selectedItem=', state.selectedItem);
589
601
  if (state.selectedItem?.id !== selectedItem?.id || state.selectedItem?.title != selectedItem?.title) {
590
- console.log('AutoPositionedPopup useEffect selectedItem!=state.selectedItem');
602
+ debugLog('AutoPositionedPopup useEffect selectedItem!=state.selectedItem');
591
603
  setState((prevState) => {
592
604
  return {
593
605
  ...prevState,
@@ -603,7 +615,7 @@ const AutoPositionedPopup = memo(
603
615
  prevPropsRef.current.CustomPopView !== CustomPopView ||
604
616
  prevPropsRef.current.CustomPopViewStyle !== CustomPopViewStyle ||
605
617
  (prevPropsRef.current.TextInputProps !== TextInputProps && useTextInput);
606
- console.log('AutoPositionedPopup useEffect [isKeyboardFullyShown,\n' +
618
+ debugLog('AutoPositionedPopup useEffect [isKeyboardFullyShown,\n' +
607
619
  ' state.isFocus,\n' +
608
620
  ' useTextInput,\n' +
609
621
  ' CustomPopView,\n' +
@@ -634,13 +646,19 @@ const AutoPositionedPopup = memo(
634
646
  TextInputProps
635
647
  };
636
648
  // Only execute logic when keyboard state actually changes or user actively operates
637
- if (!keyboardStateChanged && hasAddedRootView.current) {
638
- console.log('AutoPositionedPopup: Skip execution - parent component re-rendered but keyboard state unchanged textInputRef.current=', textInputRef.current);
639
- // if (!ref_isFocus.current) {
640
- // textInputRef.current?.focus()
641
- // }
649
+ // CRITICAL FIX: Also allow execution when popup needs initial positioning
650
+ // hasAddedRootView.current = true means popup container exists
651
+ // hasShownRootView.current = false means positioning not done yet
652
+ // We MUST allow execution when popup needs positioning, even if keyboard state unchanged
653
+ if (!keyboardStateChanged && hasAddedRootView.current && hasShownRootView.current) {
654
+ debugLog('AutoPositionedPopup: Skip execution - already positioned and keyboard state unchanged');
642
655
  return;
643
656
  }
657
+
658
+ // Log when we're allowing execution for initial positioning
659
+ if (!keyboardStateChanged && hasAddedRootView.current && !hasShownRootView.current) {
660
+ debugLog('AutoPositionedPopup: ALLOWING execution for initial positioning (popup added but not positioned yet)');
661
+ }
644
662
  const getStatusBarHeight = (): number => {
645
663
  if (Platform.OS === 'android') {
646
664
  // Android: Use StatusBar.currentHeight API
@@ -660,7 +678,7 @@ const AutoPositionedPopup = memo(
660
678
  // When keyboard appears, the trigger button may be covered. If parentScrollViewRef
661
679
  // is provided, scroll the parent to keep the trigger visible above the keyboard.
662
680
  if (parentScrollViewRef?.current) {
663
- console.log('AutoPositionedPopup: Keyboard appeared, scrolling parent to keep trigger visible');
681
+ debugLog('AutoPositionedPopup: Keyboard appeared, scrolling parent to keep trigger visible');
664
682
  // Use a slight delay to ensure keyboard animation has started
665
683
  setTimeout(() => {
666
684
  scrollToTriggerWithMeasure();
@@ -681,105 +699,164 @@ const AutoPositionedPopup = memo(
681
699
  // then requestAnimationFrame ensures measurement happens after next render frame
682
700
  setTimeout(() => {
683
701
  requestAnimationFrame(() => {
684
- // CRITICAL FIX: Use triggerBtnRef (the actual TouchableOpacity) for measurement
685
- // instead of refAutoPositionedPopup (the outer View with flex:1/height:100%)
686
- const measureTarget = triggerBtnRef.current || refAutoPositionedPopup.current;
687
- measureTarget?.measureInWindow((x: number | undefined, y: number | undefined, width: number | undefined, height: number | undefined) => {
688
- console.log('AutoPositionedPopup useTextInput measureInWindow (after 300ms + RAF, layout stable)=', {x, y, width, height, usingTriggerRef: !!triggerBtnRef.current});
689
- // CRITICAL FIX: Handle undefined values from measureInWindow
690
- // This can happen during navigation transitions or when view is not yet mounted
702
+ // CRITICAL FIX: Measure CURRENT position AFTER keyboard animation completes
703
+ // DO NOT use stored triggerPositionRef because keyboard may have shifted the view up
704
+ // Instead, measure the outer wrapper (refAutoPositionedPopup)
705
+ // which reflects the ACTUAL current position after keyboard shift
706
+
707
+ // DEBUG: Log both refs to compare their positions
708
+ debugLog('AutoPositionedPopup DEBUG: refs status=', {
709
+ hasTextInputRef: !!textInputRef.current,
710
+ hasRefAutoPositionedPopup: !!refAutoPositionedPopup.current,
711
+ });
712
+
713
+ // Measure BOTH refs for comparison
714
+ if (textInputRef.current && refAutoPositionedPopup.current) {
715
+ textInputRef.current.measureInWindow((tx: number | undefined, ty: number | undefined, tw: number | undefined, th: number | undefined) => {
716
+ debugLog('AutoPositionedPopup DEBUG: textInputRef position=', {x: tx, y: ty, width: tw, height: th});
717
+ });
718
+ refAutoPositionedPopup.current.measureInWindow((rx: number | undefined, ry: number | undefined, rw: number | undefined, rh: number | undefined) => {
719
+ debugLog('AutoPositionedPopup DEBUG: refAutoPositionedPopup position=', {x: rx, y: ry, width: rw, height: rh});
720
+ });
721
+ }
722
+
723
+ // CRITICAL FIX: Use textInputRef as primary measurement target
724
+ // refAutoPositionedPopup.measureInWindow() returns undefined values
725
+ // because the outer wrapper View uses flex:1/height:100% which makes it unmeasurable
726
+ // textInputRef reliably returns the actual position of the input field
727
+ const measureTarget = textInputRef.current || refAutoPositionedPopup.current;
728
+
729
+ if (!measureTarget) {
730
+ debugLog('AutoPositionedPopup useTextInput: no measureTarget available, using fallback');
731
+ const screenHeightFallback = Dimensions.get('window').height;
732
+ const screenWidthFallback = Dimensions.get('window').width;
733
+ const fallbackY = (screenHeightFallback - listLayout.height) / 2;
734
+ ref_listPos.current = {x: screenWidthFallback * 0.05, y: fallbackY, width: screenWidthFallback * 0.9};
735
+ updateRootView(tag, {
736
+ style: {
737
+ top: ref_listPos.current?.y,
738
+ left: popUpViewStyle?.left,
739
+ width: popUpViewStyle?.width,
740
+ height: listLayout.height,
741
+ opacity: 1,
742
+ }
743
+ });
744
+ hasShownRootView.current = true;
745
+ return;
746
+ }
747
+
748
+ // Determine which ref is actually being used (for logging)
749
+ const usingTextInputRef = measureTarget === textInputRef.current;
750
+ debugLog('AutoPositionedPopup useTextInput: using measureTarget=', usingTextInputRef ? 'textInputRef' : 'refAutoPositionedPopup');
751
+
752
+ // Measure CURRENT position (after keyboard shifted the view)
753
+ measureTarget.measureInWindow((x: number | undefined, y: number | undefined, width: number | undefined, height: number | undefined) => {
754
+ debugLog('AutoPositionedPopup useTextInput: measured position for positioning=', {
755
+ x, y, width, height,
756
+ measureTarget: usingTextInputRef ? 'textInputRef' : 'refAutoPositionedPopup'
757
+ });
758
+
759
+ // Handle undefined values (can happen during navigation transitions)
691
760
  if (x === undefined || y === undefined || width === undefined || height === undefined) {
692
- console.warn('AutoPositionedPopup useTextInput: measureInWindow returned undefined values, using fallback position');
761
+ debugLog('AutoPositionedPopup useTextInput: measureInWindow returned undefined, using fallback');
693
762
  const screenHeightFallback = Dimensions.get('window').height;
694
763
  const screenWidthFallback = Dimensions.get('window').width;
695
764
  const fallbackY = (screenHeightFallback - listLayout.height) / 2;
696
- const fallbackX = screenWidthFallback * 0.05;
697
- const fallbackWidth = screenWidthFallback * 0.9;
698
- x = fallbackX;
765
+ x = screenWidthFallback * 0.05;
699
766
  y = fallbackY;
700
- width = fallbackWidth;
767
+ width = screenWidthFallback * 0.9;
701
768
  height = 50;
702
769
  }
703
- // CRITICAL FIX: Coordinate system mismatch issue
704
- // Problem: measureInWindow returns coordinates relative to window (fixed reference),
705
- // but popup uses absolute positioning relative to App container (which shifts when keyboard appears)
706
- //
707
- // When keyboard appears:
708
- // 1. measureInWindow returns y relative to window (e.g., y=400 after shifting)
709
- // 2. But popup's absolute positioning is relative to App container
710
- // 3. If App container shifted up by 200px, setting top=200 will display at window.y=0 (wrong!)
711
- //
712
- // Solution: Since popup is rendered at root level and uses absolute positioning,
713
- // we should directly use measureInWindow's y value without additional calculations
714
- // The popup container is at the same level as the page content
715
- const screenHeight = Dimensions.get('window').height; // Use window height, not screen
716
- console.log('AutoPositionedPopup useTextInput positioning data=', {
770
+
771
+ // Calculate screen height and popup position
772
+ const screenHeight = Dimensions.get('window').height;
773
+ debugLog('AutoPositionedPopup useTextInput positioning data=', {
717
774
  screenHeight,
718
775
  componentY: y,
719
776
  componentHeight: height,
720
777
  listHeight: listLayout.height
721
778
  });
722
- // FIXED POSITIONING LOGIC (with keyboard):
723
- // measureInWindow returns coordinates relative to the window (screen)
724
- // The popup uses position: 'absolute' relative to RootViewProvider
725
- // So we should NOT add statusBarHeight to the position calculation
726
- //
727
- // 1. Default: show popup ABOVE the input field
728
- // Position popup so that the trigger remains VISIBLE below the popup
729
- // Use (y + height * 0.7) as reference to compensate for measurement offset
730
- // while still leaving trigger visible (30% of trigger height exposed)
731
- let popupY = y + (height * 0.7) - listLayout.height;
732
-
733
- console.log('AutoPositionedPopup with keyboard: initial calculation for ABOVE position:', {
734
- componentY: y,
735
- componentHeight: height,
779
+
780
+ // POSITIONING LOGIC (with keyboard):
781
+ // Simple rule: popup must TOUCH the trigger with NO GAP
782
+ // 1. Default: show ABOVE trigger (popup bottom touches trigger top)
783
+ // 2. If ABOVE would overlap status bar: show BELOW (popup top touches trigger bottom)
784
+
785
+ debugLog('AutoPositionedPopup POSITIONING:', {
786
+ triggerY: y,
787
+ triggerHeight: height,
788
+ triggerBottom: y + height,
736
789
  popupHeight: listLayout.height,
790
+ screenHeight,
791
+ statusBarHeight
792
+ });
793
+
794
+ // 1. Default: show popup ABOVE the trigger
795
+ // Popup has internal padding (12px from autoPositionedPopupList style)
796
+ // To make popup CONTENT touch trigger (not container), add padding offset
797
+ // Container bottom at y + POPUP_PADDING, content bottom at y (no gap)
798
+ const POPUP_PADDING = 12;
799
+ let popupY = y - listLayout.height + POPUP_PADDING;
800
+ let position = 'ABOVE';
801
+
802
+ debugLog('AutoPositionedPopup: trying ABOVE position:', {
737
803
  popupY,
738
804
  popupBottom: popupY + listLayout.height,
805
+ contentBottom: popupY + listLayout.height - POPUP_PADDING,
739
806
  triggerTop: y,
740
- statusBarHeight
807
+ paddingOffset: POPUP_PADDING,
808
+ wouldOverlapStatusBar: popupY < statusBarHeight
741
809
  });
742
810
 
743
- // 2. Check if showing above would go behind status bar
811
+ // 2. If showing ABOVE would go behind status bar, show BELOW instead
744
812
  if (popupY < statusBarHeight) {
745
- console.log('AutoPositionedPopup with keyboard: would go behind status bar, showing BELOW instead');
746
- // Show BELOW the input field
747
- // Since y + height represents the trigger's "reference bottom" (accounting for measurement offset),
748
- // we need to add another height to position popup BELOW the actual trigger
749
- popupY = y + height + height;
750
- console.log('AutoPositionedPopup with keyboard: BELOW position calculated:', {
751
- formula: 'y + 2*height',
752
- y,
753
- height,
754
- popupY
813
+ // Show BELOW: popup top at trigger bottom
814
+ // Use trigger's measured height as buffer to account for row padding
815
+ // The TextInput is only part of the trigger row - row height scales with trigger height
816
+ const BELOW_BUFFER = height;
817
+ popupY = y + height + BELOW_BUFFER;
818
+ position = 'BELOW';
819
+
820
+ debugLog('AutoPositionedPopup: using BELOW position (ABOVE overlaps status bar):', {
821
+ popupY,
822
+ triggerBottom: y + height,
823
+ buffer: BELOW_BUFFER,
824
+ actualGap: BELOW_BUFFER
755
825
  });
756
826
 
757
- // 3. Also check if showing below would go off the bottom
827
+ // 3. Safety check: if BELOW would go off screen bottom, clamp it
758
828
  const maxY = screenHeight - listLayout.height;
759
829
  if (popupY > maxY) {
760
- // If both positions are problematic, clamp to visible area
761
- console.log('AutoPositionedPopup with keyboard: both positions problematic, clamping to visible area');
762
- popupY = Math.min(Math.max(statusBarHeight, y - listLayout.height), maxY);
830
+ popupY = maxY;
831
+ debugLog('AutoPositionedPopup: clamped to screen bottom:', { popupY, maxY });
763
832
  }
764
833
  } else {
765
- console.log('AutoPositionedPopup with keyboard: showing ABOVE input field (preferred position)');
834
+ debugLog('AutoPositionedPopup: using ABOVE position (preferred)');
766
835
  }
836
+
837
+ debugLog('AutoPositionedPopup FINAL POSITION:', { position, popupY, touchesTrigger: true });
838
+
767
839
  ref_listPos.current = {x: x, y: popupY, width: width};
768
- console.log('AutoPositionedPopup useTextInput final position=', ref_listPos.current);
769
- setRootViewNativeStyle(tag, {
840
+ debugLog('AutoPositionedPopup useTextInput final position=', ref_listPos.current);
841
+
842
+ // Use updateRootView instead of setRootViewNativeStyle for more reliable style updates
843
+ // setNativeProps may not work correctly when initial style is in an array
844
+ const newStyle = {
770
845
  top: ref_listPos.current?.y,
771
846
  left: popUpViewStyle?.left,
772
847
  width: popUpViewStyle?.width,
773
848
  height: listLayout.height,
774
849
  opacity: 1,
775
- });
850
+ };
851
+ debugLog('AutoPositionedPopup useTextInput: applying new style via updateRootView=', newStyle);
852
+ updateRootView(tag, {style: newStyle});
776
853
  hasShownRootView.current = true;
777
854
  });
778
855
  });
779
856
  }, 300) // 300ms is sufficient for keyboard animation, as proven by user testing (even 3000ms didn't fix wrong logic)
780
857
  } else if (!isKeyboardFullyShown && ref_isFocus.current && keyboardStateChanged) {
781
858
  // Only execute close logic when keyboard state actually changes from true to false
782
- console.log(
859
+ debugLog(
783
860
  'AutoPositionedPopup isKeyboardFullyShown useEffect removeRootView (keyboard state changed)=',
784
861
  {tag, forceRemoveAllRootViewOnItemSelected, keyboardStateChanged}
785
862
  );
@@ -795,217 +872,84 @@ const AutoPositionedPopup = memo(
795
872
  hasShownRootView.current = false;
796
873
  }
797
874
  } else {
875
+ // V17 SIMPLIFICATION: When useTextInput=false, ALWAYS show popup in CENTER of screen
876
+ // User request: "只要传入的 useTextInput 是 false, 弹框都显示在屏幕中间"
877
+ // This avoids all complex positioning calculations that kept failing
798
878
  if (state.isFocus) {
799
879
  if (isKeyboardFullyShown) {
800
880
  Keyboard.dismiss();
801
881
  return;
802
882
  }
803
- // CRITICAL FIX: Use triggerBtnRef (the actual TouchableOpacity) for measurement
804
- // instead of refAutoPositionedPopup (the outer View with flex:1/height:100%)
805
- // This ensures accurate position when component is inside complex layouts like KeyboardAwareScrollView
806
- const measureTarget = triggerBtnRef.current || refAutoPositionedPopup.current;
807
- measureTarget?.measureInWindow((x: number | undefined, y: number | undefined, width: number | undefined, height: number | undefined) => {
808
- console.log('AutoPositionedPopup !useTextInput measureInWindow=', {x, y, width, height, usingTriggerRef: !!triggerBtnRef.current});
809
- // CRITICAL FIX: Handle undefined values from measureInWindow
810
- // This can happen during navigation transitions or when view is not yet mounted
811
- if (x === undefined || y === undefined || width === undefined || height === undefined) {
812
- console.warn('AutoPositionedPopup: measureInWindow returned undefined values, using fallback position');
813
- // Use screen center as fallback position
814
- const screenHeight = Dimensions.get('window').height;
815
- const screenWidth = Dimensions.get('window').width;
816
- const fallbackY = (screenHeight - listLayout.height) / 2;
817
- const fallbackX = screenWidth * 0.05; // 5% from left
818
- const fallbackWidth = screenWidth * 0.9; // 90% width
819
- ref_listPos.current = { x: fallbackX, y: fallbackY, width: fallbackWidth };
820
- console.log('AutoPositionedPopup !useTextInput using fallback position=', ref_listPos.current);
821
- // Proceed with fallback values
822
- x = fallbackX;
823
- y = fallbackY;
824
- width = fallbackWidth;
825
- height = 50; // Default height for the trigger element
826
- }
827
- // CORRECT POSITIONING LOGIC (as per user requirement)
828
- // Default: show popup ABOVE the input field
829
- // Only if that goes off the top of screen (considering status bar), show BELOW instead
830
- const calculateOptimalPosition = (componentY: number, componentHeight: number, popupHeight: number) => {
831
- console.log('AutoPositionedPopup calculateOptimalPosition executing');
832
- // Use window height (visible area) instead of screen height
833
- const screenHeight = Dimensions.get('window').height;
834
- console.log('AutoPositionedPopup positioning data:', {
835
- screenHeight,
836
- componentY,
837
- componentHeight,
838
- popupHeight,
839
- statusBarHeight,
840
- platform: Platform.OS
841
- });
842
- // FIXED POSITIONING LOGIC:
843
- // The popup uses position: 'absolute' relative to the RootViewProvider container
844
- // measureInWindow returns coordinates relative to the window (screen)
845
- // So we should NOT add statusBarHeight to the position calculation
846
- //
847
- // 1. Default: show popup ABOVE the trigger element
848
- // FIX: Use (componentY + componentHeight) as the trigger's bottom edge reference point
849
- // This compensates for measurement inaccuracies when trigger is inside complex layouts (FlatList, ScrollView)
850
- // The popup's bottom should be at the trigger's top with minimal gap (≤5px)
851
- // Formula: popup_top = trigger_bottom - componentHeight - popupHeight
852
- // popup_bottom = trigger_bottom - componentHeight = trigger_top
853
- let popupY = componentY + componentHeight - popupHeight;
854
-
855
- console.log('AutoPositionedPopup: initial calculation for ABOVE position:', {
856
- componentY,
857
- componentHeight,
858
- popupHeight,
859
- popupY,
860
- triggerBottom: componentY + componentHeight,
861
- statusBarHeight
862
- });
863
883
 
864
- // 2. Check if showing above would go off the top of screen (behind status bar)
865
- if (popupY < statusBarHeight) {
866
- console.log('AutoPositionedPopup: would go behind status bar, showing BELOW instead');
867
- // Show BELOW the trigger element
868
- // Since componentY + componentHeight represents the trigger's "reference bottom" (accounting for measurement offset),
869
- // we need to add another componentHeight to position popup BELOW the actual trigger
870
- // Formula: popup top = componentY + (2 * componentHeight)
871
- // - (componentY + componentHeight) = trigger's actual top (compensated)
872
- // - + componentHeight = skip past trigger height to get to trigger's actual bottom
873
- popupY = componentY + componentHeight + componentHeight;
874
- console.log('AutoPositionedPopup: BELOW position calculated:', {
875
- formula: 'componentY + 2*componentHeight',
876
- componentY,
877
- componentHeight,
878
- popupY
879
- });
884
+ debugLog('🟢🟢🟢 POPUP_V17 useTextInput=false, showing popup in CENTER of screen');
880
885
 
881
- // 3. Also check if showing below would go off the bottom of screen
882
- const maxY = screenHeight - popupHeight;
883
- if (popupY > maxY) {
884
- // If both positions are problematic, clamp to visible area
885
- // Prioritize showing as close to trigger as possible
886
- console.log('AutoPositionedPopup: both positions problematic, clamping to visible area');
887
- popupY = Math.min(Math.max(statusBarHeight, componentY - popupHeight), maxY);
888
- }
889
- } else {
890
- console.log('AutoPositionedPopup: showing ABOVE input field (preferred position)');
891
- }
892
- console.log('AutoPositionedPopup final position:', {
893
- popupY,
894
- 'showing above': popupY < componentY,
895
- 'below status bar': popupY >= statusBarHeight
896
- });
897
- return {finalY: popupY, showAbove: popupY < componentY};
898
- };
899
- // Calculate position ONCE based on actual popup height
900
- const actualPopupHeight = CustomPopView && CustomPopViewStyle && typeof CustomPopViewStyle.height === 'number'
901
- ? CustomPopViewStyle.height
902
- : listLayout.height;
903
- console.log('AutoPositionedPopup 🔥 Using actualPopupHeight for calculation:', {actualPopupHeight, CustomPopView: !!CustomPopView});
904
- const positionResult = calculateOptimalPosition(y, height, actualPopupHeight);
905
- console.log('AutoPositionedPopup FINAL position result:', positionResult);
906
- ref_listPos.current = {x: x, y: positionResult.finalY, width: width};
907
- console.log('AutoPositionedPopup !useTextInput ref_listPos.current=', ref_listPos.current);
908
- if (CustomPopView && CustomPopViewStyle) {
909
- // Position already calculated correctly above, no need to recalculate
910
- const PopViewComponent = CustomPopView();
911
- console.log('AutoPositionedPopup !useTextInput addRootView=', {CustomPopViewStyle, PopViewComponent, 'state.selectedItem': state.selectedItem});
912
- addRootView({
913
- id: tag,
914
- style: !centerDisplay
915
- ? {
916
- top: ref_listPos.current.y,
917
- left: popUpViewStyle?.left,
918
- width: popUpViewStyle?.width,
919
- height: listLayout.height,
920
- opacity: 1,
921
- ...CustomPopViewStyle,
922
- }
923
- : {width: popUpViewStyle?.width, height: listLayout.height, ...CustomPopViewStyle},
924
- component: <PopViewComponent selectedItem={state.selectedItem}></PopViewComponent>,
925
- useModal: true,
926
- onModalClose: () => {
927
- console.log('AutoPositionedPopup onModalClose');
928
- removeRootView(tag, forceRemoveAllRootViewOnItemSelected, rootViewsRef.current);
929
- setState((prevState) => {
930
- return {
931
- ...prevState,
932
- isFocus: false,
933
- };
934
- });
935
- hasAddedRootView.current = false;
936
- hasShownRootView.current = false;
937
- hasTriggeredFocus.current = false;
938
- setSearchQuery('');
939
- },
940
- centerDisplay,
941
- });
942
- } else {
943
- console.log('AutoPositionedPopup !useTextInput addRootView tag=', tag);
944
- addRootView({
945
- id: tag,
946
- style: {
947
- top: ref_listPos.current.y,
948
- left: popUpViewStyle?.left,
949
- width: popUpViewStyle?.width,
950
- height: listLayout.height,
951
- opacity: 1,
952
- },
953
- component: (
954
- <AutoPositionedPopupList
955
- tag={tag}
956
- updateState={updateState}
957
- fetchData={fetchData}
958
- pageSize={pageSize}
959
- renderItem={renderItem}
960
- selectedItem={state.selectedItem}
961
- localSearch={localSearch}
962
- showListEmptyComponent={showListEmptyComponent}
963
- emptyText={emptyText}
964
- themeMode={themeMode}
965
- />
966
- ),
967
- useModal: true,
968
- onModalClose: () => {
969
- console.log('AutoPositionedPopup onModalClose tag=', tag);
970
- removeRootView(tag, forceRemoveAllRootViewOnItemSelected, rootViewsRef.current);
971
- setState((prevState) => {
972
- return {
973
- ...prevState,
974
- };
975
- });
976
- setSearchQuery('');
977
- },
978
- });
979
- }
980
- });
981
- }
982
- }
983
- if (isKeyboardFullyShown) {
984
- ref_isFocus.current = state.isFocus ?? false;
985
- if (isKeyboardFullyShown !== keyboardVisibleRef.current) {
986
- keyboardVisibleRef.current = isKeyboardFullyShown;
987
- if (isKeyboardFullyShown && textInputRef.current) {
988
- if (ref_searchQuery.current) {
989
- textInputRef.current.setNativeProps({text: ref_searchQuery.current});
990
- }
886
+ const actualPopupHeight = CustomPopView && CustomPopViewStyle && typeof CustomPopViewStyle.height === 'number'
887
+ ? CustomPopViewStyle.height
888
+ : listLayout.height;
889
+
890
+ if (CustomPopView && CustomPopViewStyle) {
891
+ const PopViewComponent = CustomPopView();
892
+ debugLog('🔵🔵🔵 POPUP_V17 CustomPopView centerDisplay=true');
893
+ addRootView({
894
+ id: tag,
895
+ style: { width: popUpViewStyle?.width, ...CustomPopViewStyle },
896
+ component: <PopViewComponent selectedItem={state.selectedItem}></PopViewComponent>,
897
+ useModal: true,
898
+ centerDisplay: true, // V17: Force center display for useTextInput=false
899
+ onModalClose: () => {
900
+ debugLog('AutoPositionedPopup V17 onModalClose tag=', tag);
901
+ removeRootView(tag, forceRemoveAllRootViewOnItemSelected, rootViewsRef.current);
902
+ setState((prevState) => ({ ...prevState }));
903
+ setSearchQuery('');
904
+ },
905
+ });
906
+ } else {
907
+ debugLog('🔵🔵🔵 POPUP_V17 List centerDisplay=true, height=', listLayout.height);
908
+ addRootView({
909
+ id: tag,
910
+ style: { width: popUpViewStyle?.width, height: listLayout.height, opacity: 1 },
911
+ component: (
912
+ <AutoPositionedPopupList
913
+ tag={tag} updateState={updateState} fetchData={fetchData} pageSize={pageSize}
914
+ renderItem={renderItem} selectedItem={state.selectedItem} localSearch={localSearch}
915
+ showListEmptyComponent={showListEmptyComponent} emptyText={emptyText} themeMode={themeMode}
916
+ />
917
+ ),
918
+ useModal: true,
919
+ centerDisplay: true, // V17: Force center display for useTextInput=false
920
+ onModalClose: () => {
921
+ debugLog('AutoPositionedPopup V17 onModalClose tag=', tag);
922
+ removeRootView(tag, forceRemoveAllRootViewOnItemSelected, rootViewsRef.current);
923
+ setState((prevState) => ({ ...prevState }));
924
+ setSearchQuery('');
925
+ },
926
+ });
991
927
  }
928
+ return; // V17: Early return after handling !useTextInput case
992
929
  }
993
930
  }
994
931
  }, [isKeyboardFullyShown,
995
- state.isFocus,
996
- useTextInput,
997
- CustomPopView,
998
- CustomPopViewStyle,
999
- forceRemoveAllRootViewOnItemSelected,
1000
- tag, TextInputProps,
1001
- state.selectedItem, showListEmptyComponent, themeMode
1002
- ]);
1003
- // Imperative handle for parent component access
1004
- useImperativeHandle(
932
+ state.isFocus,
933
+ useTextInput,
934
+ CustomPopView,
935
+ CustomPopViewStyle,
936
+ forceRemoveAllRootViewOnItemSelected,
937
+ tag, TextInputProps,
938
+ state.selectedItem, showListEmptyComponent, themeMode
939
+ ]);
940
+
941
+ // V16: All positioning logic is now in the useEffect above (calculateOptimalPosition + processPosition)
942
+ // V16 FIX: Capture position in onPress callback BEFORE setState is called
943
+ // This ensures triggerPositionRef.current is set when useEffect runs
944
+ // Formula: top = componentY - popupHeight (popup bottom touches trigger top exactly)
945
+ debugLog('🟢 POPUP_MODULE_V16_LOADED - capturing position in onPress callback before setState');
946
+
947
+ // Imperative handle for parent component access
948
+ useImperativeHandle(
1005
949
  parentRef,
1006
950
  () => ({
1007
951
  clearSelectedItem: () => {
1008
- console.log('AutoPositionedPopup clearSelectedItem tag=', tag);
952
+ debugLog('AutoPositionedPopup clearSelectedItem tag=', tag);
1009
953
  setState((prevState) => {
1010
954
  return {
1011
955
  ...prevState,
@@ -1027,14 +971,14 @@ const AutoPositionedPopup = memo(
1027
971
  []
1028
972
  );
1029
973
  const updateState = (key: string, value: SelectedItem) => {
1030
- console.log('AutoPositionedPopup updateState=', {key, value});
974
+ debugLog('AutoPositionedPopup updateState=', {key, value});
1031
975
  setState((prevState) => ({
1032
976
  ...prevState,
1033
977
  [key]: value,
1034
978
  }));
1035
979
  if (key === 'selectedItem' && onItemSelected) {
1036
980
  onItemSelected(value);
1037
- console.log('AutoPositionedPopup updateState onItemSelected rootViewsRef.current=', rootViewsRef.current);
981
+ debugLog('AutoPositionedPopup updateState onItemSelected rootViewsRef.current=', rootViewsRef.current);
1038
982
  removeRootView(tag, forceRemoveAllRootViewOnItemSelected, rootViewsRef.current);
1039
983
  hasAddedRootView.current = false;
1040
984
  hasShownRootView.current = false;
@@ -1069,7 +1013,7 @@ const AutoPositionedPopup = memo(
1069
1013
  // Only update when deep comparison detects real changes to avoid TextInput recreation due to reference changes during parent component redraws
1070
1014
  const stableInputStyle = useMemo(() => {
1071
1015
  if (!shallowEqual(stableInputStyleRef.current, inputStyle)) {
1072
- console.log(`AutoPositionedPopup stableInputStyle: `, {tag, inputStyle, themeMode});
1016
+ debugLog(`AutoPositionedPopup stableInputStyle: `, {tag, inputStyle, themeMode});
1073
1017
  stableInputStyleRef.current = inputStyle;
1074
1018
  }
1075
1019
  return stableInputStyleRef.current;
@@ -1077,10 +1021,10 @@ const AutoPositionedPopup = memo(
1077
1021
 
1078
1022
  const stableTextInputProps = useMemo(() => {
1079
1023
  if (!shallowEqual(stableTextInputPropsRef.current, TextInputProps)) {
1080
- console.log(`AutoPositionedPopup TextInputProps deep change detected, updating stable reference - tag: ${tag}`);
1024
+ debugLog(`AutoPositionedPopup TextInputProps deep change detected, updating stable reference - tag: ${tag}`);
1081
1025
  stableTextInputPropsRef.current = TextInputProps;
1082
1026
  }
1083
- console.log('AutoPositionedPopup stableTextInputProps=', {tag, TextInputProps, 'stableTextInputPropsRef.current': stableTextInputPropsRef.current})
1027
+ debugLog('AutoPositionedPopup stableTextInputProps=', {tag, TextInputProps, 'stableTextInputPropsRef.current': stableTextInputPropsRef.current})
1084
1028
  return stableTextInputPropsRef.current;
1085
1029
  }, [TextInputProps, tag]);
1086
1030
 
@@ -1093,7 +1037,7 @@ const AutoPositionedPopup = memo(
1093
1037
  const handleTextInputFocus = useCallback(() => {
1094
1038
  const currentTime = Date.now();
1095
1039
  const timeSinceLastFocus = currentTime - lastFocusTimeRef.current;
1096
- console.log(
1040
+ debugLog(
1097
1041
  'AutoPositionedPopup onFocus=',
1098
1042
  {
1099
1043
  tag,
@@ -1108,17 +1052,17 @@ const AutoPositionedPopup = memo(
1108
1052
  );
1109
1053
  // Prevent rapid repeated triggers (repeated events within 300ms are ignored)
1110
1054
  if (timeSinceLastFocus < 300) {
1111
- console.log('AutoPositionedPopup onFocus: Skip - event triggered too quickly (< 300ms)');
1055
+ debugLog('AutoPositionedPopup onFocus: Skip - event triggered too quickly (< 300ms)');
1112
1056
  return;
1113
1057
  }
1114
1058
  // Skip if keyboard is already open and focus has been handled
1115
1059
  if (isKeyboardFullyShown && hasTriggeredFocus.current) {
1116
- console.log('AutoPositionedPopup onFocus: Skip - keyboard already open and focus handled');
1060
+ debugLog('AutoPositionedPopup onFocus: Skip - keyboard already open and focus handled');
1117
1061
  return;
1118
1062
  }
1119
1063
  // Prevent concurrent processing
1120
1064
  if (isFocusEventProcessingRef.current) {
1121
- console.log('AutoPositionedPopup onFocus: Skip - processing another focus event');
1065
+ debugLog('AutoPositionedPopup onFocus: Skip - processing another focus event');
1122
1066
  return;
1123
1067
  }
1124
1068
  isFocusEventProcessingRef.current = true;
@@ -1142,7 +1086,7 @@ const AutoPositionedPopup = memo(
1142
1086
  }, [tag, isKeyboardFullyShown]); // Remove state.selectedItem, use stateRef instead
1143
1087
 
1144
1088
  const handleTextInputBlur = useCallback(() => {
1145
- console.log(
1089
+ debugLog(
1146
1090
  'AutoPositionedPopup onBlur=',
1147
1091
  {
1148
1092
  tag,
@@ -1153,7 +1097,7 @@ const AutoPositionedPopup = memo(
1153
1097
  );
1154
1098
  // If keyboard is still open, this is a false trigger caused by parent component re-render, should not reset
1155
1099
  if (isKeyboardFullyShown && hasTriggeredFocus.current) {
1156
- console.log('AutoPositionedPopup onBlur: Skip - keyboard still open, possibly caused by parent component re-render');
1100
+ debugLog('AutoPositionedPopup onBlur: Skip - keyboard still open, possibly caused by parent component re-render');
1157
1101
  return;
1158
1102
  }
1159
1103
 
@@ -1182,7 +1126,7 @@ const AutoPositionedPopup = memo(
1182
1126
  // Wrap TextInput independently in useMemo to recreate only when key props change
1183
1127
  // This avoids repeated ref callback triggers due to other props changes during parent component redraws
1184
1128
  const memoizedTextInput = useMemo(() => {
1185
- console.log('AutoPositionedPopup memoizedTextInput=', {tag, useTextInput, 'state.isFocus': state.isFocus, stableTextInputProps});
1129
+ debugLog('AutoPositionedPopup memoizedTextInput=', {tag, useTextInput, 'state.isFocus': state.isFocus, stableTextInputProps});
1186
1130
  if (!useTextInput || !state.isFocus) {
1187
1131
  return null;
1188
1132
  }
@@ -1191,11 +1135,11 @@ const AutoPositionedPopup = memo(
1191
1135
  ref={(ref) => {
1192
1136
  // Monitor TextInput mounting and unmounting
1193
1137
  if (ref && !textInputRef.current) {
1194
- console.log(`AutoPositionedPopup TextInput created/mounted - tag: ${tag}, ref:`, ref);
1138
+ debugLog(`AutoPositionedPopup TextInput created/mounted - tag: ${tag}, ref:`, ref);
1195
1139
  } else if (!ref && textInputRef.current) {
1196
- console.log(`AutoPositionedPopup TextInput unmounted - tag: ${tag}`);
1140
+ debugLog(`AutoPositionedPopup TextInput unmounted - tag: ${tag}`);
1197
1141
  } else if (ref && textInputRef.current && ref !== textInputRef.current) {
1198
- console.log(`AutoPositionedPopup TextInput replaced - tag: ${tag}, oldRef:`, textInputRef.current, 'newRef:', ref);
1142
+ debugLog(`AutoPositionedPopup TextInput replaced - tag: ${tag}, oldRef:`, textInputRef.current, 'newRef:', ref);
1199
1143
  }
1200
1144
  textInputRef.current = ref;
1201
1145
  }}
@@ -1203,14 +1147,14 @@ const AutoPositionedPopup = memo(
1203
1147
  style={[
1204
1148
  styles.inputStyle,
1205
1149
  stableInputStyle,
1206
- (themeMode==='dark' && {color:'#fff'})
1150
+ (themeMode==='dark' && {color:'#fff'}),
1207
1151
  ]}
1208
1152
  textAlign={stableTextInputProps && stableTextInputProps['textAlign'] || 'left'}
1209
1153
  multiline={stableTextInputProps && stableTextInputProps['multiline'] || false}
1210
1154
  numberOfLines={stableTextInputProps && stableTextInputProps['numberOfLines'] || 1}
1211
1155
  onChangeText={(searchQuery) => {
1212
1156
  ref_searchQuery.current = searchQuery;
1213
- console.log('AutoPositionedPopup onChangeText rootViews=', rootViews);
1157
+ debugLog('AutoPositionedPopup onChangeText rootViews=', rootViews);
1214
1158
  if (!localSearch) {
1215
1159
  if (debounceTimerRef.current) {
1216
1160
  clearTimeout(debounceTimerRef.current);
@@ -1249,7 +1193,7 @@ const AutoPositionedPopup = memo(
1249
1193
  onBlur={handleTextInputBlur}
1250
1194
  selectTextOnFocus={stableTextInputProps && stableTextInputProps['selectTextOnFocus'] || false}
1251
1195
  onSubmitEditing={(e: NativeSyntheticEvent<TextInputSubmitEditingEventData>) => {
1252
- console.log(
1196
+ debugLog(
1253
1197
  'AutoPositionedPopup.tsx onSubmitEditing e.nativeEvent.text=',
1254
1198
  e.nativeEvent.text
1255
1199
  );
@@ -1273,7 +1217,7 @@ const AutoPositionedPopup = memo(
1273
1217
 
1274
1218
  // Render the component following project implementation
1275
1219
  return useMemo(() => {
1276
- console.log('AutoPositionedPopup render tag=', tag); // Now safe - circular dependency fixed
1220
+ debugLog('AutoPositionedPopup render tag=', tag); // Now safe - circular dependency fixed
1277
1221
  return (
1278
1222
  <CustomRow>
1279
1223
  <View style={[styles.contain, style]} ref={refAutoPositionedPopup}>
@@ -1283,7 +1227,7 @@ const AutoPositionedPopup = memo(
1283
1227
  style={[styles.AutoPositionedPopupBtn, AutoPositionedPopupBtnStyle]}
1284
1228
  disabled={AutoPositionedPopupBtnDisabled}
1285
1229
  onPress={() => {
1286
- console.log('AutoPositionedPopup onPress=', {
1230
+ debugLog('AutoPositionedPopup onPress=', {
1287
1231
  tag,
1288
1232
  'state.isFocus': state.isFocus,
1289
1233
  useTextInput,
@@ -1296,10 +1240,13 @@ const AutoPositionedPopup = memo(
1296
1240
 
1297
1241
  // Capture trigger button position BEFORE switching to TextInput
1298
1242
  // This is critical because triggerBtnRef will become null after isFocus=true
1299
- if (triggerBtnRef.current && parentScrollViewRef?.current) {
1243
+ // IMPORTANT: Always capture position regardless of parentScrollViewRef
1244
+ if (triggerBtnRef.current) {
1300
1245
  triggerBtnRef.current.measureInWindow((x, y, width, height) => {
1301
- console.log('AutoPositionedPopup onPress: captured trigger position=', {tag, x, y, width, height});
1302
- triggerPositionRef.current = {x, y, width, height};
1246
+ debugLog('AutoPositionedPopup onPress: captured trigger position=', {tag, x, y, width, height});
1247
+ if (x !== undefined && y !== undefined && width !== undefined && height !== undefined) {
1248
+ triggerPositionRef.current = {x, y, width, height};
1249
+ }
1303
1250
  });
1304
1251
  }
1305
1252
 
@@ -1357,6 +1304,9 @@ const AutoPositionedPopup = memo(
1357
1304
  _addRootView()
1358
1305
  }
1359
1306
  } else {
1307
+ // V17 SIMPLIFICATION: For useTextInput=false, popup will be centered
1308
+ // No need for complex position measurement - just trigger focus
1309
+ debugLog('🔵🔵🔵 POPUP_V17 onPress useTextInput=false, will show centered popup');
1360
1310
  setState((prevState) => {
1361
1311
  return {
1362
1312
  ...prevState,
@@ -1364,7 +1314,7 @@ const AutoPositionedPopup = memo(
1364
1314
  };
1365
1315
  });
1366
1316
  }
1367
- console.log('AutoPositionedPopup onPress done')
1317
+ debugLog('AutoPositionedPopup onPress done')
1368
1318
  }}
1369
1319
  >
1370
1320
  {!btwChildren ? (
@@ -1391,29 +1341,29 @@ const AutoPositionedPopup = memo(
1391
1341
  );
1392
1342
  }, [
1393
1343
  tag,
1394
- // CRITICAL FIX: Remove all props that may change frequently or are inline functions
1344
+ // �?CRITICAL FIX: Remove all props that may change frequently or are inline functions
1395
1345
  // Changes to these props should not cause the entire component tree to recreate, especially TextInput
1396
- // fetchData, // Removed: inline function
1397
- // renderItem, // Removed: possibly inline function
1398
- // onItemSelected, // Removed: possibly inline function
1399
- // onSubmitEditing, // Removed: possibly inline function
1346
+ // fetchData, // �?Removed: inline function
1347
+ // renderItem, // �?Removed: possibly inline function
1348
+ // onItemSelected, // �?Removed: possibly inline function
1349
+ // onSubmitEditing, // �?Removed: possibly inline function
1400
1350
  localSearch,
1401
- // placeholder, // Removed: may change
1402
- // textAlign, // Removed: may change
1351
+ // placeholder, // �?Removed: may change
1352
+ // textAlign, // �?Removed: may change
1403
1353
  pageSize,
1404
1354
  selectedItem,
1405
- // CustomRow, // Removed: inline function, new reference each time
1355
+ // CustomRow, // �?Removed: inline function, new reference each time
1406
1356
  useTextInput,
1407
- // btwChildren, // Removed: inline function
1408
- // keyExtractor, // Removed: possibly inline function
1409
- // AutoPositionedPopupBtnStyle, // Removed: possibly inline object
1410
- // CustomPopView, // Removed: may change
1411
- // CustomPopViewStyle, // Removed: may change
1357
+ // btwChildren, // �?Removed: inline function
1358
+ // keyExtractor, // �?Removed: possibly inline function
1359
+ // AutoPositionedPopupBtnStyle, // �?Removed: possibly inline object
1360
+ // CustomPopView, // �?Removed: may change
1361
+ // CustomPopViewStyle, // �?Removed: may change
1412
1362
  forceRemoveAllRootViewOnItemSelected,
1413
1363
  state.isFocus,
1414
1364
  showListEmptyComponent,
1415
1365
  emptyText,
1416
- // Removed most dependencies that may cause re-rendering, keeping only core dependencies that truly affect component structure
1366
+ // �?Removed most dependencies that may cause re-rendering, keeping only core dependencies that truly affect component structure
1417
1367
  // This prevents TextInput recreation due to inline functions/objects during parent component redraws
1418
1368
  ]);
1419
1369
  }