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 +1 @@
1
- {"version":3,"file":"AutoPositionedPopup.d.ts","sourceRoot":"","sources":["../src/AutoPositionedPopup.tsx"],"names":[],"mappings":"AAAA,OAAO,KAYN,MAAM,OAAO,CAAC;AAgBf,OAAO,EAAC,wBAAwB,EAAqB,MAAM,4BAA4B,CAAC;AA6QxF,QAAA,MAAM,mBAAmB,qFAmmCxB,CAAC;AAEF,eAAe,mBAAmB,CAAC"}
1
+ {"version":3,"file":"AutoPositionedPopup.d.ts","sourceRoot":"","sources":["../src/AutoPositionedPopup.tsx"],"names":[],"mappings":"AAYA,OAAO,KAYN,MAAM,OAAO,CAAC;AAgBf,OAAO,EAAC,wBAAwB,EAAqB,MAAM,4BAA4B,CAAC;AA6QxF,QAAA,MAAM,mBAAmB,qFAqiCxB,CAAC;AAEF,eAAe,mBAAmB,CAAC"}
@@ -1,4 +1,14 @@
1
1
  import { debugLog } from './constants';
2
+ // Module load marker - unique ID for tracking code version
3
+ // DEBUG FLAG: Set to false to disable all console logs for better performance
4
+ const POPUP_DEBUG = false;
5
+ const debugLog = (...args) => {
6
+ if (POPUP_DEBUG) {
7
+ debugLog(...args);
8
+ }
9
+ };
10
+ // Only log module load in debug mode
11
+ debugLog('POPUP_MODULE_V17_LOADED at ' + new Date().toISOString());
2
12
  import React, { forwardRef, memo, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState, } from 'react';
3
13
  import { Dimensions, findNodeHandle, Keyboard, Platform, StatusBar, Text, TextInput as RNTextInput, TouchableOpacity, View, } from 'react-native';
4
14
  import { AdvancedFlatList } from 'react-native-advanced-flatlist';
@@ -131,7 +141,7 @@ const AutoPositionedPopupList = memo(({ tag, updateState, fetchData, keyExtracto
131
141
  return null;
132
142
  }
133
143
  catch (e) {
134
- console.warn('Error in fetchData:', e);
144
+ debugLog('Error in fetchData:', e);
135
145
  }
136
146
  debugLog('AutoPositionedPopupList _fetchData res=', null);
137
147
  return null;
@@ -180,7 +190,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
180
190
  // res.needLoadMore = res1.length === pageSize
181
191
  }
182
192
  catch (e) {
183
- console.warn('Error in fetch operation:', e);
193
+ debugLog('Error in fetch operation:', e);
184
194
  }
185
195
  return res;
186
196
  }, renderItem, onItemSelected, localSearch = false, pageSize = 20, selectedItem, useTextInput = false, btwChildren, CustomRow = ({ children }) => <View>{children}</View>, keyExtractor = (item) => String((item === null || item === void 0 ? void 0 : item.id) || ''), AutoPositionedPopupBtnDisabled = false, forceRemoveAllRootViewOnItemSelected = false, centerDisplay = false, selectedItemBackgroundColor = 'rgba(116, 116, 128, 0.08)',
@@ -192,7 +202,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
192
202
  selectedItem: selectedItem,
193
203
  });
194
204
  // Use RootView context
195
- const { addRootView, setRootViewNativeStyle, removeRootView, rootViews, setSearchQuery } = useRootView();
205
+ const { addRootView, setRootViewNativeStyle, updateRootView, removeRootView, rootViews, setSearchQuery } = useRootView();
196
206
  const rootViewsRef = useRef(rootViews);
197
207
  // Track TextInput focus and RootView states like project implementation
198
208
  const hasTriggeredFocus = useRef(false);
@@ -405,7 +415,6 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
405
415
  }
406
416
  }, [selectedItem, state.selectedItem, tag]);
407
417
  useEffect(() => {
408
- var _a;
409
418
  // Detect if keyboard state has actually changed to avoid false triggers during parent component re-renders
410
419
  const keyboardStateChanged = prevIsKeyboardFullyShownRef.current !== isKeyboardFullyShown;
411
420
  const propsChanged = prevPropsRef.current.CustomPopView !== CustomPopView ||
@@ -442,13 +451,18 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
442
451
  TextInputProps
443
452
  };
444
453
  // Only execute logic when keyboard state actually changes or user actively operates
445
- if (!keyboardStateChanged && hasAddedRootView.current) {
446
- debugLog('AutoPositionedPopup: Skip execution - parent component re-rendered but keyboard state unchanged textInputRef.current=', textInputRef.current);
447
- // if (!ref_isFocus.current) {
448
- // textInputRef.current?.focus()
449
- // }
454
+ // CRITICAL FIX: Also allow execution when popup needs initial positioning
455
+ // hasAddedRootView.current = true means popup container exists
456
+ // hasShownRootView.current = false means positioning not done yet
457
+ // We MUST allow execution when popup needs positioning, even if keyboard state unchanged
458
+ if (!keyboardStateChanged && hasAddedRootView.current && hasShownRootView.current) {
459
+ debugLog('AutoPositionedPopup: Skip execution - already positioned and keyboard state unchanged');
450
460
  return;
451
461
  }
462
+ // Log when we're allowing execution for initial positioning
463
+ if (!keyboardStateChanged && hasAddedRootView.current && !hasShownRootView.current) {
464
+ debugLog('AutoPositionedPopup: ALLOWING execution for initial positioning (popup added but not positioned yet)');
465
+ }
452
466
  const getStatusBarHeight = () => {
453
467
  if (Platform.OS === 'android') {
454
468
  // Android: Use StatusBar.currentHeight API
@@ -489,97 +503,142 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
489
503
  // then requestAnimationFrame ensures measurement happens after next render frame
490
504
  setTimeout(() => {
491
505
  requestAnimationFrame(() => {
492
- // CRITICAL FIX: Use triggerBtnRef (the actual TouchableOpacity) for measurement
493
- // instead of refAutoPositionedPopup (the outer View with flex:1/height:100%)
494
- const measureTarget = triggerBtnRef.current || refAutoPositionedPopup.current;
495
- measureTarget === null || measureTarget === void 0 ? void 0 : measureTarget.measureInWindow((x, y, width, height) => {
506
+ // CRITICAL FIX: Measure CURRENT position AFTER keyboard animation completes
507
+ // DO NOT use stored triggerPositionRef because keyboard may have shifted the view up
508
+ // Instead, measure the outer wrapper (refAutoPositionedPopup)
509
+ // which reflects the ACTUAL current position after keyboard shift
510
+ var _a;
511
+ // DEBUG: Log both refs to compare their positions
512
+ debugLog('AutoPositionedPopup DEBUG: refs status=', {
513
+ hasTextInputRef: !!textInputRef.current,
514
+ hasRefAutoPositionedPopup: !!refAutoPositionedPopup.current,
515
+ });
516
+ // Measure BOTH refs for comparison
517
+ if (textInputRef.current && refAutoPositionedPopup.current) {
518
+ textInputRef.current.measureInWindow((tx, ty, tw, th) => {
519
+ debugLog('AutoPositionedPopup DEBUG: textInputRef position=', { x: tx, y: ty, width: tw, height: th });
520
+ });
521
+ refAutoPositionedPopup.current.measureInWindow((rx, ry, rw, rh) => {
522
+ debugLog('AutoPositionedPopup DEBUG: refAutoPositionedPopup position=', { x: rx, y: ry, width: rw, height: rh });
523
+ });
524
+ }
525
+ // CRITICAL FIX: Use textInputRef as primary measurement target
526
+ // refAutoPositionedPopup.measureInWindow() returns undefined values
527
+ // because the outer wrapper View uses flex:1/height:100% which makes it unmeasurable
528
+ // textInputRef reliably returns the actual position of the input field
529
+ const measureTarget = textInputRef.current || refAutoPositionedPopup.current;
530
+ if (!measureTarget) {
531
+ debugLog('AutoPositionedPopup useTextInput: no measureTarget available, using fallback');
532
+ const screenHeightFallback = Dimensions.get('window').height;
533
+ const screenWidthFallback = Dimensions.get('window').width;
534
+ const fallbackY = (screenHeightFallback - listLayout.height) / 2;
535
+ ref_listPos.current = { x: screenWidthFallback * 0.05, y: fallbackY, width: screenWidthFallback * 0.9 };
536
+ updateRootView(tag, {
537
+ style: {
538
+ top: (_a = ref_listPos.current) === null || _a === void 0 ? void 0 : _a.y,
539
+ left: popUpViewStyle === null || popUpViewStyle === void 0 ? void 0 : popUpViewStyle.left,
540
+ width: popUpViewStyle === null || popUpViewStyle === void 0 ? void 0 : popUpViewStyle.width,
541
+ height: listLayout.height,
542
+ opacity: 1,
543
+ }
544
+ });
545
+ hasShownRootView.current = true;
546
+ return;
547
+ }
548
+ // Determine which ref is actually being used (for logging)
549
+ const usingTextInputRef = measureTarget === textInputRef.current;
550
+ debugLog('AutoPositionedPopup useTextInput: using measureTarget=', usingTextInputRef ? 'textInputRef' : 'refAutoPositionedPopup');
551
+ // Measure CURRENT position (after keyboard shifted the view)
552
+ measureTarget.measureInWindow((x, y, width, height) => {
496
553
  var _a;
497
- debugLog('AutoPositionedPopup useTextInput measureInWindow (after 300ms + RAF, layout stable)=', { x, y, width, height, usingTriggerRef: !!triggerBtnRef.current });
498
- // CRITICAL FIX: Handle undefined values from measureInWindow
499
- // This can happen during navigation transitions or when view is not yet mounted
554
+ debugLog('AutoPositionedPopup useTextInput: measured position for positioning=', {
555
+ x, y, width, height,
556
+ measureTarget: usingTextInputRef ? 'textInputRef' : 'refAutoPositionedPopup'
557
+ });
558
+ // Handle undefined values (can happen during navigation transitions)
500
559
  if (x === undefined || y === undefined || width === undefined || height === undefined) {
501
- console.warn('AutoPositionedPopup useTextInput: measureInWindow returned undefined values, using fallback position');
560
+ debugLog('AutoPositionedPopup useTextInput: measureInWindow returned undefined, using fallback');
502
561
  const screenHeightFallback = Dimensions.get('window').height;
503
562
  const screenWidthFallback = Dimensions.get('window').width;
504
563
  const fallbackY = (screenHeightFallback - listLayout.height) / 2;
505
- const fallbackX = screenWidthFallback * 0.05;
506
- const fallbackWidth = screenWidthFallback * 0.9;
507
- x = fallbackX;
564
+ x = screenWidthFallback * 0.05;
508
565
  y = fallbackY;
509
- width = fallbackWidth;
566
+ width = screenWidthFallback * 0.9;
510
567
  height = 50;
511
568
  }
512
- // CRITICAL FIX: Coordinate system mismatch issue
513
- // Problem: measureInWindow returns coordinates relative to window (fixed reference),
514
- // but popup uses absolute positioning relative to App container (which shifts when keyboard appears)
515
- //
516
- // When keyboard appears:
517
- // 1. measureInWindow returns y relative to window (e.g., y=400 after shifting)
518
- // 2. But popup's absolute positioning is relative to App container
519
- // 3. If App container shifted up by 200px, setting top=200 will display at window.y=0 (wrong!)
520
- //
521
- // Solution: Since popup is rendered at root level and uses absolute positioning,
522
- // we should directly use measureInWindow's y value without additional calculations
523
- // The popup container is at the same level as the page content
524
- const screenHeight = Dimensions.get('window').height; // Use window height, not screen
569
+ // Calculate screen height and popup position
570
+ const screenHeight = Dimensions.get('window').height;
525
571
  debugLog('AutoPositionedPopup useTextInput positioning data=', {
526
572
  screenHeight,
527
573
  componentY: y,
528
574
  componentHeight: height,
529
575
  listHeight: listLayout.height
530
576
  });
531
- // FIXED POSITIONING LOGIC (with keyboard):
532
- // measureInWindow returns coordinates relative to the window (screen)
533
- // The popup uses position: 'absolute' relative to RootViewProvider
534
- // So we should NOT add statusBarHeight to the position calculation
535
- //
536
- // 1. Default: show popup ABOVE the input field
537
- // Position popup so that the trigger remains VISIBLE below the popup
538
- // Use (y + height * 0.7) as reference to compensate for measurement offset
539
- // while still leaving trigger visible (30% of trigger height exposed)
540
- let popupY = y + (height * 0.7) - listLayout.height;
541
- debugLog('AutoPositionedPopup with keyboard: initial calculation for ABOVE position:', {
542
- componentY: y,
543
- componentHeight: height,
577
+ // POSITIONING LOGIC (with keyboard):
578
+ // Simple rule: popup must TOUCH the trigger with NO GAP
579
+ // 1. Default: show ABOVE trigger (popup bottom touches trigger top)
580
+ // 2. If ABOVE would overlap status bar: show BELOW (popup top touches trigger bottom)
581
+ debugLog('AutoPositionedPopup POSITIONING:', {
582
+ triggerY: y,
583
+ triggerHeight: height,
584
+ triggerBottom: y + height,
544
585
  popupHeight: listLayout.height,
586
+ screenHeight,
587
+ statusBarHeight
588
+ });
589
+ // 1. Default: show popup ABOVE the trigger
590
+ // Popup has internal padding (12px from autoPositionedPopupList style)
591
+ // To make popup CONTENT touch trigger (not container), add padding offset
592
+ // Container bottom at y + POPUP_PADDING, content bottom at y (no gap)
593
+ const POPUP_PADDING = 12;
594
+ let popupY = y - listLayout.height + POPUP_PADDING;
595
+ let position = 'ABOVE';
596
+ debugLog('AutoPositionedPopup: trying ABOVE position:', {
545
597
  popupY,
546
598
  popupBottom: popupY + listLayout.height,
599
+ contentBottom: popupY + listLayout.height - POPUP_PADDING,
547
600
  triggerTop: y,
548
- statusBarHeight
601
+ paddingOffset: POPUP_PADDING,
602
+ wouldOverlapStatusBar: popupY < statusBarHeight
549
603
  });
550
- // 2. Check if showing above would go behind status bar
604
+ // 2. If showing ABOVE would go behind status bar, show BELOW instead
551
605
  if (popupY < statusBarHeight) {
552
- debugLog('AutoPositionedPopup with keyboard: would go behind status bar, showing BELOW instead');
553
- // Show BELOW the input field
554
- // Since y + height represents the trigger's "reference bottom" (accounting for measurement offset),
555
- // we need to add another height to position popup BELOW the actual trigger
556
- popupY = y + height + height;
557
- debugLog('AutoPositionedPopup with keyboard: BELOW position calculated:', {
558
- formula: 'y + 2*height',
559
- y,
560
- height,
561
- popupY
606
+ // Show BELOW: popup top at trigger bottom
607
+ // Use trigger's measured height as buffer to account for row padding
608
+ // The TextInput is only part of the trigger row - row height scales with trigger height
609
+ const BELOW_BUFFER = height;
610
+ popupY = y + height + BELOW_BUFFER;
611
+ position = 'BELOW';
612
+ debugLog('AutoPositionedPopup: using BELOW position (ABOVE overlaps status bar):', {
613
+ popupY,
614
+ triggerBottom: y + height,
615
+ buffer: BELOW_BUFFER,
616
+ actualGap: BELOW_BUFFER
562
617
  });
563
- // 3. Also check if showing below would go off the bottom
618
+ // 3. Safety check: if BELOW would go off screen bottom, clamp it
564
619
  const maxY = screenHeight - listLayout.height;
565
620
  if (popupY > maxY) {
566
- // If both positions are problematic, clamp to visible area
567
- debugLog('AutoPositionedPopup with keyboard: both positions problematic, clamping to visible area');
568
- popupY = Math.min(Math.max(statusBarHeight, y - listLayout.height), maxY);
621
+ popupY = maxY;
622
+ debugLog('AutoPositionedPopup: clamped to screen bottom:', { popupY, maxY });
569
623
  }
570
624
  }
571
625
  else {
572
- debugLog('AutoPositionedPopup with keyboard: showing ABOVE input field (preferred position)');
626
+ debugLog('AutoPositionedPopup: using ABOVE position (preferred)');
573
627
  }
628
+ debugLog('AutoPositionedPopup FINAL POSITION:', { position, popupY, touchesTrigger: true });
574
629
  ref_listPos.current = { x: x, y: popupY, width: width };
575
630
  debugLog('AutoPositionedPopup useTextInput final position=', ref_listPos.current);
576
- setRootViewNativeStyle(tag, {
631
+ // Use updateRootView instead of setRootViewNativeStyle for more reliable style updates
632
+ // setNativeProps may not work correctly when initial style is in an array
633
+ const newStyle = {
577
634
  top: (_a = ref_listPos.current) === null || _a === void 0 ? void 0 : _a.y,
578
635
  left: popUpViewStyle === null || popUpViewStyle === void 0 ? void 0 : popUpViewStyle.left,
579
636
  width: popUpViewStyle === null || popUpViewStyle === void 0 ? void 0 : popUpViewStyle.width,
580
637
  height: listLayout.height,
581
638
  opacity: 1,
582
- });
639
+ };
640
+ debugLog('AutoPositionedPopup useTextInput: applying new style via updateRootView=', newStyle);
641
+ updateRootView(tag, { style: newStyle });
583
642
  hasShownRootView.current = true;
584
643
  });
585
644
  });
@@ -598,173 +657,52 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
598
657
  }
599
658
  }
600
659
  else {
660
+ // V17 SIMPLIFICATION: When useTextInput=false, ALWAYS show popup in CENTER of screen
661
+ // User request: "只要传入的 useTextInput 是 false, 弹框都显示在屏幕中间"
662
+ // This avoids all complex positioning calculations that kept failing
601
663
  if (state.isFocus) {
602
664
  if (isKeyboardFullyShown) {
603
665
  Keyboard.dismiss();
604
666
  return;
605
667
  }
606
- // CRITICAL FIX: Use triggerBtnRef (the actual TouchableOpacity) for measurement
607
- // instead of refAutoPositionedPopup (the outer View with flex:1/height:100%)
608
- // This ensures accurate position when component is inside complex layouts like KeyboardAwareScrollView
609
- const measureTarget = triggerBtnRef.current || refAutoPositionedPopup.current;
610
- measureTarget === null || measureTarget === void 0 ? void 0 : measureTarget.measureInWindow((x, y, width, height) => {
611
- debugLog('AutoPositionedPopup !useTextInput measureInWindow=', { x, y, width, height, usingTriggerRef: !!triggerBtnRef.current });
612
- // CRITICAL FIX: Handle undefined values from measureInWindow
613
- // This can happen during navigation transitions or when view is not yet mounted
614
- if (x === undefined || y === undefined || width === undefined || height === undefined) {
615
- console.warn('AutoPositionedPopup: measureInWindow returned undefined values, using fallback position');
616
- // Use screen center as fallback position
617
- const screenHeight = Dimensions.get('window').height;
618
- const screenWidth = Dimensions.get('window').width;
619
- const fallbackY = (screenHeight - listLayout.height) / 2;
620
- const fallbackX = screenWidth * 0.05; // 5% from left
621
- const fallbackWidth = screenWidth * 0.9; // 90% width
622
- ref_listPos.current = { x: fallbackX, y: fallbackY, width: fallbackWidth };
623
- debugLog('AutoPositionedPopup !useTextInput using fallback position=', ref_listPos.current);
624
- // Proceed with fallback values
625
- x = fallbackX;
626
- y = fallbackY;
627
- width = fallbackWidth;
628
- height = 50; // Default height for the trigger element
629
- }
630
- // CORRECT POSITIONING LOGIC (as per user requirement)
631
- // Default: show popup ABOVE the input field
632
- // Only if that goes off the top of screen (considering status bar), show BELOW instead
633
- const calculateOptimalPosition = (componentY, componentHeight, popupHeight) => {
634
- debugLog('AutoPositionedPopup calculateOptimalPosition executing');
635
- // Use window height (visible area) instead of screen height
636
- const screenHeight = Dimensions.get('window').height;
637
- debugLog('AutoPositionedPopup positioning data:', {
638
- screenHeight,
639
- componentY,
640
- componentHeight,
641
- popupHeight,
642
- statusBarHeight,
643
- platform: Platform.OS
644
- });
645
- // FIXED POSITIONING LOGIC:
646
- // The popup uses position: 'absolute' relative to the RootViewProvider container
647
- // measureInWindow returns coordinates relative to the window (screen)
648
- // So we should NOT add statusBarHeight to the position calculation
649
- //
650
- // 1. Default: show popup ABOVE the trigger element
651
- // FIX: Use (componentY + componentHeight) as the trigger's bottom edge reference point
652
- // This compensates for measurement inaccuracies when trigger is inside complex layouts (FlatList, ScrollView)
653
- // The popup's bottom should be at the trigger's top with minimal gap (≤5px)
654
- // Formula: popup_top = trigger_bottom - componentHeight - popupHeight
655
- // popup_bottom = trigger_bottom - componentHeight = trigger_top
656
- let popupY = componentY + componentHeight - popupHeight;
657
- debugLog('AutoPositionedPopup: initial calculation for ABOVE position:', {
658
- componentY,
659
- componentHeight,
660
- popupHeight,
661
- popupY,
662
- triggerBottom: componentY + componentHeight,
663
- statusBarHeight
664
- });
665
- // 2. Check if showing above would go off the top of screen (behind status bar)
666
- if (popupY < statusBarHeight) {
667
- debugLog('AutoPositionedPopup: would go behind status bar, showing BELOW instead');
668
- // Show BELOW the trigger element
669
- // Since componentY + componentHeight represents the trigger's "reference bottom" (accounting for measurement offset),
670
- // we need to add another componentHeight to position popup BELOW the actual trigger
671
- // Formula: popup top = componentY + (2 * componentHeight)
672
- // - (componentY + componentHeight) = trigger's actual top (compensated)
673
- // - + componentHeight = skip past trigger height to get to trigger's actual bottom
674
- popupY = componentY + componentHeight + componentHeight;
675
- debugLog('AutoPositionedPopup: BELOW position calculated:', {
676
- formula: 'componentY + 2*componentHeight',
677
- componentY,
678
- componentHeight,
679
- popupY
680
- });
681
- // 3. Also check if showing below would go off the bottom of screen
682
- const maxY = screenHeight - popupHeight;
683
- if (popupY > maxY) {
684
- // If both positions are problematic, clamp to visible area
685
- // Prioritize showing as close to trigger as possible
686
- debugLog('AutoPositionedPopup: both positions problematic, clamping to visible area');
687
- popupY = Math.min(Math.max(statusBarHeight, componentY - popupHeight), maxY);
688
- }
689
- }
690
- else {
691
- debugLog('AutoPositionedPopup: showing ABOVE input field (preferred position)');
692
- }
693
- debugLog('AutoPositionedPopup final position:', {
694
- popupY,
695
- 'showing above': popupY < componentY,
696
- 'below status bar': popupY >= statusBarHeight
697
- });
698
- return { finalY: popupY, showAbove: popupY < componentY };
699
- };
700
- // Calculate position ONCE based on actual popup height
701
- const actualPopupHeight = CustomPopView && CustomPopViewStyle && typeof CustomPopViewStyle.height === 'number'
702
- ? CustomPopViewStyle.height
703
- : listLayout.height;
704
- debugLog('AutoPositionedPopup 🔥 Using actualPopupHeight for calculation:', { actualPopupHeight, CustomPopView: !!CustomPopView });
705
- const positionResult = calculateOptimalPosition(y, height, actualPopupHeight);
706
- debugLog('AutoPositionedPopup FINAL position result:', positionResult);
707
- ref_listPos.current = { x: x, y: positionResult.finalY, width: width };
708
- debugLog('AutoPositionedPopup !useTextInput ref_listPos.current=', ref_listPos.current);
709
- if (CustomPopView && CustomPopViewStyle) {
710
- // Position already calculated correctly above, no need to recalculate
711
- const PopViewComponent = CustomPopView();
712
- debugLog('AutoPositionedPopup !useTextInput addRootView=', { CustomPopViewStyle, PopViewComponent, 'state.selectedItem': state.selectedItem });
713
- addRootView({
714
- id: tag,
715
- style: !centerDisplay
716
- ? Object.assign({ top: ref_listPos.current.y, left: popUpViewStyle === null || popUpViewStyle === void 0 ? void 0 : popUpViewStyle.left, width: popUpViewStyle === null || popUpViewStyle === void 0 ? void 0 : popUpViewStyle.width, height: listLayout.height, opacity: 1 }, CustomPopViewStyle) : Object.assign({ width: popUpViewStyle === null || popUpViewStyle === void 0 ? void 0 : popUpViewStyle.width, height: listLayout.height }, CustomPopViewStyle),
717
- component: <PopViewComponent selectedItem={state.selectedItem}></PopViewComponent>,
718
- useModal: true,
719
- onModalClose: () => {
720
- debugLog('AutoPositionedPopup onModalClose');
721
- removeRootView(tag, forceRemoveAllRootViewOnItemSelected, rootViewsRef.current);
722
- setState((prevState) => {
723
- return Object.assign(Object.assign({}, prevState), { isFocus: false });
724
- });
725
- hasAddedRootView.current = false;
726
- hasShownRootView.current = false;
727
- hasTriggeredFocus.current = false;
728
- setSearchQuery('');
729
- },
730
- centerDisplay,
731
- });
732
- }
733
- else {
734
- debugLog('AutoPositionedPopup !useTextInput addRootView tag=', tag);
735
- addRootView({
736
- id: tag,
737
- style: {
738
- top: ref_listPos.current.y,
739
- left: popUpViewStyle === null || popUpViewStyle === void 0 ? void 0 : popUpViewStyle.left,
740
- width: popUpViewStyle === null || popUpViewStyle === void 0 ? void 0 : popUpViewStyle.width,
741
- height: listLayout.height,
742
- opacity: 1,
743
- },
744
- component: (<AutoPositionedPopupList tag={tag} updateState={updateState} fetchData={fetchData} pageSize={pageSize} renderItem={renderItem} selectedItem={state.selectedItem} localSearch={localSearch} showListEmptyComponent={showListEmptyComponent} emptyText={emptyText} themeMode={themeMode}/>),
745
- useModal: true,
746
- onModalClose: () => {
747
- debugLog('AutoPositionedPopup onModalClose tag=', tag);
748
- removeRootView(tag, forceRemoveAllRootViewOnItemSelected, rootViewsRef.current);
749
- setState((prevState) => {
750
- return Object.assign({}, prevState);
751
- });
752
- setSearchQuery('');
753
- },
754
- });
755
- }
756
- });
757
- }
758
- }
759
- if (isKeyboardFullyShown) {
760
- ref_isFocus.current = (_a = state.isFocus) !== null && _a !== void 0 ? _a : false;
761
- if (isKeyboardFullyShown !== keyboardVisibleRef.current) {
762
- keyboardVisibleRef.current = isKeyboardFullyShown;
763
- if (isKeyboardFullyShown && textInputRef.current) {
764
- if (ref_searchQuery.current) {
765
- textInputRef.current.setNativeProps({ text: ref_searchQuery.current });
766
- }
668
+ debugLog('🟢🟢🟢 POPUP_V17 useTextInput=false, showing popup in CENTER of screen');
669
+ const actualPopupHeight = CustomPopView && CustomPopViewStyle && typeof CustomPopViewStyle.height === 'number'
670
+ ? CustomPopViewStyle.height
671
+ : listLayout.height;
672
+ if (CustomPopView && CustomPopViewStyle) {
673
+ const PopViewComponent = CustomPopView();
674
+ debugLog('🔵🔵🔵 POPUP_V17 CustomPopView centerDisplay=true');
675
+ addRootView({
676
+ id: tag,
677
+ style: Object.assign({ width: popUpViewStyle === null || popUpViewStyle === void 0 ? void 0 : popUpViewStyle.width }, CustomPopViewStyle),
678
+ component: <PopViewComponent selectedItem={state.selectedItem}></PopViewComponent>,
679
+ useModal: true,
680
+ centerDisplay: true, // V17: Force center display for useTextInput=false
681
+ onModalClose: () => {
682
+ debugLog('AutoPositionedPopup V17 onModalClose tag=', tag);
683
+ removeRootView(tag, forceRemoveAllRootViewOnItemSelected, rootViewsRef.current);
684
+ setState((prevState) => (Object.assign({}, prevState)));
685
+ setSearchQuery('');
686
+ },
687
+ });
688
+ }
689
+ else {
690
+ debugLog('🔵🔵🔵 POPUP_V17 List centerDisplay=true, height=', listLayout.height);
691
+ addRootView({
692
+ id: tag,
693
+ style: { width: popUpViewStyle === null || popUpViewStyle === void 0 ? void 0 : popUpViewStyle.width, height: listLayout.height, opacity: 1 },
694
+ component: (<AutoPositionedPopupList tag={tag} updateState={updateState} fetchData={fetchData} pageSize={pageSize} renderItem={renderItem} selectedItem={state.selectedItem} localSearch={localSearch} showListEmptyComponent={showListEmptyComponent} emptyText={emptyText} themeMode={themeMode}/>),
695
+ useModal: true,
696
+ centerDisplay: true, // V17: Force center display for useTextInput=false
697
+ onModalClose: () => {
698
+ debugLog('AutoPositionedPopup V17 onModalClose tag=', tag);
699
+ removeRootView(tag, forceRemoveAllRootViewOnItemSelected, rootViewsRef.current);
700
+ setState((prevState) => (Object.assign({}, prevState)));
701
+ setSearchQuery('');
702
+ },
703
+ });
767
704
  }
705
+ return; // V17: Early return after handling !useTextInput case
768
706
  }
769
707
  }
770
708
  }, [isKeyboardFullyShown,
@@ -776,6 +714,11 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
776
714
  tag, TextInputProps,
777
715
  state.selectedItem, showListEmptyComponent, themeMode
778
716
  ]);
717
+ // V16: All positioning logic is now in the useEffect above (calculateOptimalPosition + processPosition)
718
+ // V16 FIX: Capture position in onPress callback BEFORE setState is called
719
+ // This ensures triggerPositionRef.current is set when useEffect runs
720
+ // Formula: top = componentY - popupHeight (popup bottom touches trigger top exactly)
721
+ debugLog('🟢 POPUP_MODULE_V16_LOADED - capturing position in onPress callback before setState');
779
722
  // Imperative handle for parent component access
780
723
  useImperativeHandle(parentRef, () => ({
781
724
  clearSelectedItem: () => {
@@ -950,7 +893,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
950
893
  }} key={`textinput-${tag}`} style={[
951
894
  styles.inputStyle,
952
895
  stableInputStyle,
953
- (themeMode === 'dark' && { color: '#fff' })
896
+ (themeMode === 'dark' && { color: '#fff' }),
954
897
  ]} textAlign={stableTextInputProps && stableTextInputProps['textAlign'] || 'left'} multiline={stableTextInputProps && stableTextInputProps['multiline'] || false} numberOfLines={stableTextInputProps && stableTextInputProps['numberOfLines'] || 1} onChangeText={(searchQuery) => {
955
898
  ref_searchQuery.current = searchQuery;
956
899
  debugLog('AutoPositionedPopup onChangeText rootViews=', rootViews);
@@ -1007,10 +950,13 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
1007
950
  });
1008
951
  // Capture trigger button position BEFORE switching to TextInput
1009
952
  // This is critical because triggerBtnRef will become null after isFocus=true
1010
- if (triggerBtnRef.current && (parentScrollViewRef === null || parentScrollViewRef === void 0 ? void 0 : parentScrollViewRef.current)) {
953
+ // IMPORTANT: Always capture position regardless of parentScrollViewRef
954
+ if (triggerBtnRef.current) {
1011
955
  triggerBtnRef.current.measureInWindow((x, y, width, height) => {
1012
956
  debugLog('AutoPositionedPopup onPress: captured trigger position=', { tag, x, y, width, height });
1013
- triggerPositionRef.current = { x, y, width, height };
957
+ if (x !== undefined && y !== undefined && width !== undefined && height !== undefined) {
958
+ triggerPositionRef.current = { x, y, width, height };
959
+ }
1014
960
  });
1015
961
  }
1016
962
  if (useTextInput) {
@@ -1050,6 +996,9 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
1050
996
  }
1051
997
  }
1052
998
  else {
999
+ // V17 SIMPLIFICATION: For useTextInput=false, popup will be centered
1000
+ // No need for complex position measurement - just trigger focus
1001
+ debugLog('🔵🔵🔵 POPUP_V17 onPress useTextInput=false, will show centered popup');
1053
1002
  setState((prevState) => {
1054
1003
  return Object.assign(Object.assign({}, prevState), { isFocus: true });
1055
1004
  });
@@ -1068,29 +1017,29 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
1068
1017
  </CustomRow>);
1069
1018
  }, [
1070
1019
  tag,
1071
- // CRITICAL FIX: Remove all props that may change frequently or are inline functions
1020
+ // �?CRITICAL FIX: Remove all props that may change frequently or are inline functions
1072
1021
  // Changes to these props should not cause the entire component tree to recreate, especially TextInput
1073
- // fetchData, // Removed: inline function
1074
- // renderItem, // Removed: possibly inline function
1075
- // onItemSelected, // Removed: possibly inline function
1076
- // onSubmitEditing, // Removed: possibly inline function
1022
+ // fetchData, // �?Removed: inline function
1023
+ // renderItem, // �?Removed: possibly inline function
1024
+ // onItemSelected, // �?Removed: possibly inline function
1025
+ // onSubmitEditing, // �?Removed: possibly inline function
1077
1026
  localSearch,
1078
- // placeholder, // Removed: may change
1079
- // textAlign, // Removed: may change
1027
+ // placeholder, // �?Removed: may change
1028
+ // textAlign, // �?Removed: may change
1080
1029
  pageSize,
1081
1030
  selectedItem,
1082
- // CustomRow, // Removed: inline function, new reference each time
1031
+ // CustomRow, // �?Removed: inline function, new reference each time
1083
1032
  useTextInput,
1084
- // btwChildren, // Removed: inline function
1085
- // keyExtractor, // Removed: possibly inline function
1086
- // AutoPositionedPopupBtnStyle, // Removed: possibly inline object
1087
- // CustomPopView, // Removed: may change
1088
- // CustomPopViewStyle, // Removed: may change
1033
+ // btwChildren, // �?Removed: inline function
1034
+ // keyExtractor, // �?Removed: possibly inline function
1035
+ // AutoPositionedPopupBtnStyle, // �?Removed: possibly inline object
1036
+ // CustomPopView, // �?Removed: may change
1037
+ // CustomPopViewStyle, // �?Removed: may change
1089
1038
  forceRemoveAllRootViewOnItemSelected,
1090
1039
  state.isFocus,
1091
1040
  showListEmptyComponent,
1092
1041
  emptyText,
1093
- // Removed most dependencies that may cause re-rendering, keeping only core dependencies that truly affect component structure
1042
+ // �?Removed most dependencies that may cause re-rendering, keeping only core dependencies that truly affect component structure
1094
1043
  // This prevents TextInput recreation due to inline functions/objects during parent component redraws
1095
1044
  ]);
1096
1045
  }));