react-native-auto-positioned-popup 1.2.17 → 1.2.18
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.
- package/lib/AutoPositionedPopup.d.ts.map +1 -1
- package/lib/AutoPositionedPopup.js +113 -123
- package/lib/AutoPositionedPopup.js.map +1 -1
- package/lib/KeyboardManager.d.ts +6 -1
- package/lib/KeyboardManager.d.ts.map +1 -1
- package/lib/KeyboardManager.js +12 -2
- package/lib/KeyboardManager.js.map +1 -1
- package/lib/RootViewContext.d.ts.map +1 -1
- package/lib/RootViewContext.js +66 -44
- package/lib/RootViewContext.js.map +1 -1
- package/package.json +1 -1
- package/src/AutoPositionedPopup.tsx +116 -126
- package/src/KeyboardManager.tsx +21 -4
- package/src/RootViewContext.tsx +45 -17
package/lib/RootViewContext.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { debugLog } from './constants';
|
|
2
2
|
import React, { createContext, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
-
import { Pressable, View, Keyboard } from 'react-native';
|
|
3
|
+
import { Pressable, View, Keyboard, StyleSheet } from 'react-native';
|
|
4
4
|
// DEBUG FLAG: Set to false to disable all console logs for better performance
|
|
5
5
|
const ROOTVIEW_DEBUG = false;
|
|
6
6
|
const debugLog = (...args) => {
|
|
@@ -87,56 +87,78 @@ export const RootViewProvider = ({ children }) => {
|
|
|
87
87
|
setSearchQuery,
|
|
88
88
|
}), [addRootView, setRootViewNativeStyle, updateRootView, removeRootView, rootViews, searchQuery, setSearchQuery]);
|
|
89
89
|
return (<RootViewContext.Provider value={contextValue}>
|
|
90
|
-
|
|
90
|
+
{/* CRITICAL FIX: Use View with flex:1 instead of Fragment */}
|
|
91
|
+
{/* Fragment doesn't create actual View, causing absolute positioning to use wrong ancestor */}
|
|
92
|
+
{/* View with flex:1 ensures popup container positions relative to this full-screen View */}
|
|
93
|
+
<View style={rootViewStyles.container}>
|
|
91
94
|
{children}
|
|
92
|
-
{
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}} style
|
|
98
|
-
{
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
95
|
+
{/* CRITICAL: Full-screen container ensures absolute positioning is relative to screen */}
|
|
96
|
+
{/* Without this, popup position may be offset by parent containers */}
|
|
97
|
+
{rootViews.length > 0 && (<View pointerEvents="box-none" style={rootViewStyles.popupContainer}>
|
|
98
|
+
{rootViews.map(({ id, style, component, useModal, onModalClose, centerDisplay }) => {
|
|
99
|
+
// POSITION DEBUG: Log the actual top value being applied (simple format for cleaner logs)
|
|
100
|
+
debugLog(`📍 RENDER_STYLE id=${id} top=${style === null || style === void 0 ? void 0 : style.top} left=${style === null || style === void 0 ? void 0 : style.left} w=${style === null || style === void 0 ? void 0 : style.width} h=${style === null || style === void 0 ? void 0 : style.height} op=${style === null || style === void 0 ? void 0 : style.opacity}`);
|
|
101
|
+
return !useModal ? (<View key={id} ref={(r) => {
|
|
102
|
+
if (r)
|
|
103
|
+
viewRefs.current[id] = r;
|
|
104
|
+
}} style={[style, { position: 'absolute' }]}>
|
|
105
|
+
{component}
|
|
106
|
+
</View>) : (<Pressable key={id} style={[
|
|
107
|
+
{
|
|
108
|
+
flex: 1,
|
|
109
|
+
position: 'absolute',
|
|
110
|
+
width: '100%',
|
|
111
|
+
left: 0,
|
|
112
|
+
right: 0,
|
|
113
|
+
top: 0,
|
|
114
|
+
bottom: 0,
|
|
115
|
+
zIndex: 99999999999,
|
|
116
|
+
backgroundColor: 'rgba(0,0,0,0.5)',
|
|
117
|
+
},
|
|
118
|
+
centerDisplay && { justifyContent: 'center', alignItems: 'center' },
|
|
119
|
+
]} onPress={() => {
|
|
120
|
+
debugLog('react-native-auto-positioned-popup RootViewProvider Pressable onPress rootViews=', rootViews);
|
|
121
|
+
removeRootView(id, true);
|
|
122
|
+
onModalClose && onModalClose();
|
|
123
|
+
}}>
|
|
117
124
|
<View ref={(r) => {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
125
|
+
if (r)
|
|
126
|
+
viewRefs.current[id] = r;
|
|
127
|
+
}} style={[{ position: 'absolute' }, style]}>
|
|
121
128
|
{component}
|
|
122
129
|
</View>
|
|
123
130
|
</Pressable>);
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
131
|
+
// (<Modal
|
|
132
|
+
// animationType="none"
|
|
133
|
+
// transparent={false}
|
|
134
|
+
// visible={true}
|
|
135
|
+
// presentationStyle="overFullScreen" // iOS特定属性
|
|
136
|
+
// onRequestClose={() => {
|
|
137
|
+
// // Android 返回鍵按下時的回調
|
|
138
|
+
// onModalClose && onModalClose()
|
|
139
|
+
// }}
|
|
140
|
+
// key={id}
|
|
141
|
+
// >
|
|
142
|
+
// </Modal>)
|
|
143
|
+
})}
|
|
144
|
+
</View>)}
|
|
145
|
+
</View>
|
|
138
146
|
</RootViewContext.Provider>);
|
|
139
147
|
};
|
|
148
|
+
// Styles for RootView container - extracted for better performance
|
|
149
|
+
const rootViewStyles = StyleSheet.create({
|
|
150
|
+
container: {
|
|
151
|
+
flex: 1,
|
|
152
|
+
},
|
|
153
|
+
popupContainer: {
|
|
154
|
+
position: 'absolute',
|
|
155
|
+
top: 0,
|
|
156
|
+
left: 0,
|
|
157
|
+
right: 0,
|
|
158
|
+
bottom: 0,
|
|
159
|
+
zIndex: 99999,
|
|
160
|
+
},
|
|
161
|
+
});
|
|
140
162
|
/*
|
|
141
163
|
const { addRootView, updateRootView, removeRootView ,searchQuery } = useRootView();
|
|
142
164
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RootViewContext.js","sourceRoot":"","sources":["../src/RootViewContext.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAY,aAAa,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAC,MAAM,OAAO,CAAC;AACxG,OAAO,EAAC,SAAS,EAAE,IAAI,EAAa,QAAQ,EAAC,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"RootViewContext.js","sourceRoot":"","sources":["../src/RootViewContext.tsx"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-auto-positioned-popup",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.18",
|
|
4
4
|
"description": "A highly customizable React Native auto-positioned popup component with search functionality and flexible styling options",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
// Module load marker - unique ID for tracking code version
|
|
2
|
+
// V19f (2025-01-04): CORRECT direction for coordinate adjustment - ADD statusBarHeight to move popup DOWN
|
|
3
|
+
// Wait 1 second for KeyboardAwareScrollView to stabilize, then use measureInWindow to get trigger's FINAL position
|
|
4
|
+
// NOTE: Parent component (KeyboardAwareScrollView) is responsible for scrolling trigger into view
|
|
2
5
|
// DEBUG FLAG: Set to false to disable all console logs for better performance
|
|
3
|
-
const POPUP_DEBUG = false;
|
|
6
|
+
const POPUP_DEBUG = false; // DISABLED: Too many logs cause app freeze
|
|
7
|
+
const POPUP_POSITION_DEBUG = true; // Only log positioning calculations
|
|
4
8
|
const debugLog = (...args: any[]) => {
|
|
5
9
|
if (POPUP_DEBUG) {
|
|
6
10
|
console.log(...args);
|
|
7
11
|
}
|
|
8
12
|
};
|
|
13
|
+
// Separate logging function for position-related logs only
|
|
14
|
+
const positionDebugLog = (...args: any[]) => {
|
|
15
|
+
if (POPUP_POSITION_DEBUG) {
|
|
16
|
+
console.log(...args);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
9
19
|
|
|
10
20
|
// Only log module load in debug mode
|
|
11
|
-
|
|
21
|
+
positionDebugLog('POPUP_MODULE_V19f_LOADED at ' + new Date().toISOString() + ' (Parent handles scroll)');
|
|
12
22
|
|
|
13
23
|
import React, {
|
|
14
24
|
ForwardedRef,
|
|
@@ -382,6 +392,8 @@ const AutoPositionedPopup = memo(
|
|
|
382
392
|
const ref_searchQuery = useRef<string>('');
|
|
383
393
|
// Store trigger button position when clicked (before it's replaced by TextInput)
|
|
384
394
|
const triggerPositionRef = useRef<{x: number; y: number; width: number; height: number} | null>(null);
|
|
395
|
+
// V19: Track keyboard height for accurate popup positioning
|
|
396
|
+
const keyboardHeightRef = useRef<number>(0);
|
|
385
397
|
// Add ref to track previous keyboard state to avoid false triggers during parent component re-renders
|
|
386
398
|
const prevIsKeyboardFullyShownRef = useRef<boolean>(false);
|
|
387
399
|
const prevPropsRef = useRef<{
|
|
@@ -416,11 +428,16 @@ const AutoPositionedPopup = memo(
|
|
|
416
428
|
const searchQueryRef = useRef<string>(''); // Use ref instead of state to avoid re-renders
|
|
417
429
|
// Refs to store latest values for useEffect without adding to dependency array
|
|
418
430
|
const dataRef = useRef<SelectedItem[]>(data);
|
|
419
|
-
|
|
431
|
+
// V19: useKeyboardStatus now returns { isShown, height } for accurate positioning
|
|
432
|
+
const keyboardStatus = useKeyboardStatus();
|
|
433
|
+
const isKeyboardFullyShown = keyboardStatus.isShown;
|
|
420
434
|
const ref_isKeyboardFullyShown = useRef<boolean>(isKeyboardFullyShown);
|
|
421
435
|
useEffect(() => {
|
|
422
436
|
ref_isKeyboardFullyShown.current = isKeyboardFullyShown;
|
|
423
|
-
|
|
437
|
+
// V19: Store keyboard height for popup positioning calculations
|
|
438
|
+
keyboardHeightRef.current = keyboardStatus.height;
|
|
439
|
+
positionDebugLog(`KEYBOARD_HEIGHT_UPDATE: height=${keyboardStatus.height} isShown=${isKeyboardFullyShown}`);
|
|
440
|
+
}, [keyboardStatus.isShown, keyboardStatus.height])
|
|
424
441
|
const theme = defaultTheme;
|
|
425
442
|
|
|
426
443
|
/**
|
|
@@ -674,30 +691,46 @@ const AutoPositionedPopup = memo(
|
|
|
674
691
|
const statusBarHeight = getStatusBarHeight();
|
|
675
692
|
if (useTextInput) {
|
|
676
693
|
if (isKeyboardFullyShown && hasAddedRootView.current && !hasShownRootView.current && state.isFocus) {
|
|
677
|
-
// KEYBOARD AVOIDANCE FIX:
|
|
678
|
-
//
|
|
679
|
-
//
|
|
680
|
-
if (parentScrollViewRef?.current) {
|
|
681
|
-
debugLog('AutoPositionedPopup: Keyboard appeared,
|
|
682
|
-
// Use
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
694
|
+
// KEYBOARD AVOIDANCE FIX: Use KeyboardAwareScrollView's native scrollToFocusedInput method
|
|
695
|
+
// This properly scrolls to the dynamically created TextInput without causing double scrolling.
|
|
696
|
+
// The previous custom scrollToTriggerWithMeasure() caused over-scrolling issues.
|
|
697
|
+
if (parentScrollViewRef?.current && textInputRef.current) {
|
|
698
|
+
debugLog('AutoPositionedPopup: Keyboard appeared, using scrollToFocusedInput to scroll parent');
|
|
699
|
+
// Use KeyboardAwareScrollView's native method to scroll to the focused TextInput
|
|
700
|
+
// This is more reliable than custom scroll calculations
|
|
701
|
+
const scrollView = parentScrollViewRef.current;
|
|
702
|
+
if (typeof scrollView.scrollToFocusedInput === 'function') {
|
|
703
|
+
// findNodeHandle is needed to get the native node reference
|
|
704
|
+
const nodeHandle = findNodeHandle(textInputRef.current);
|
|
705
|
+
if (nodeHandle) {
|
|
706
|
+
// scrollToFocusedInput expects a ReactNode, use the TextInput ref
|
|
707
|
+
scrollView.scrollToFocusedInput(textInputRef.current, scrollExtraHeight);
|
|
708
|
+
debugLog('AutoPositionedPopup: Called scrollToFocusedInput with extraHeight=', scrollExtraHeight);
|
|
709
|
+
}
|
|
710
|
+
} else {
|
|
711
|
+
debugLog('AutoPositionedPopup: scrollToFocusedInput not available, skipping scroll');
|
|
712
|
+
}
|
|
686
713
|
}
|
|
687
714
|
|
|
688
715
|
// CRITICAL FIX FOR KEYBOARD POSITION CALCULATION
|
|
689
716
|
// Problem: When keyboard appears, the page shifts up but measureInWindow executes too early
|
|
690
|
-
// Solution: Wait for keyboard animation
|
|
717
|
+
// Solution: Wait for keyboard animation + page scroll to complete before measuring
|
|
691
718
|
//
|
|
692
719
|
// Timing breakdown:
|
|
693
720
|
// 1. Keyboard animation: ~250-300ms (iOS/Android)
|
|
694
|
-
// 2. Page shift animation: ~
|
|
695
|
-
// 3. Layout tree update: ~
|
|
696
|
-
// Total: ~
|
|
721
|
+
// 2. Page shift animation: ~300-500ms (KeyboardAwareScrollView)
|
|
722
|
+
// 3. Layout tree update: ~100-200ms (React Native)
|
|
723
|
+
// Total: ~700-1000ms needed for stable layout
|
|
697
724
|
//
|
|
698
|
-
//
|
|
725
|
+
// USER REQUEST (2025-01-04): Wait 1 second (1000ms) after keyboard appears
|
|
726
|
+
// to ensure trigger component position has fully stabilized after scroll
|
|
727
|
+
//
|
|
728
|
+
// Strategy: setTimeout(1000ms) waits for all animations to complete,
|
|
699
729
|
// then requestAnimationFrame ensures measurement happens after next render frame
|
|
730
|
+
const KEYBOARD_STABILIZATION_DELAY = 500; // 500ms as requested by user
|
|
731
|
+
positionDebugLog(`POPUP_WAIT: Waiting ${KEYBOARD_STABILIZATION_DELAY}ms for keyboard/scroll stabilization, tag=${tag}`);
|
|
700
732
|
setTimeout(() => {
|
|
733
|
+
positionDebugLog(`POPUP_MEASURE_START: ${KEYBOARD_STABILIZATION_DELAY}ms elapsed, now measuring position for tag=${tag}`);
|
|
701
734
|
requestAnimationFrame(() => {
|
|
702
735
|
// CRITICAL FIX: Measure CURRENT position AFTER keyboard animation completes
|
|
703
736
|
// DO NOT use stored triggerPositionRef because keyboard may have shifted the view up
|
|
@@ -705,10 +738,7 @@ const AutoPositionedPopup = memo(
|
|
|
705
738
|
// which reflects the ACTUAL current position after keyboard shift
|
|
706
739
|
|
|
707
740
|
// DEBUG: Log both refs to compare their positions
|
|
708
|
-
|
|
709
|
-
hasTextInputRef: !!textInputRef.current,
|
|
710
|
-
hasRefAutoPositionedPopup: !!refAutoPositionedPopup.current,
|
|
711
|
-
});
|
|
741
|
+
positionDebugLog(`POPUP_REFS: textInputRef=${!!textInputRef.current} refAutoPositionedPopup=${!!refAutoPositionedPopup.current}`);
|
|
712
742
|
|
|
713
743
|
// Measure BOTH refs for comparison
|
|
714
744
|
if (textInputRef.current && refAutoPositionedPopup.current) {
|
|
@@ -749,111 +779,72 @@ const AutoPositionedPopup = memo(
|
|
|
749
779
|
const usingTextInputRef = measureTarget === textInputRef.current;
|
|
750
780
|
debugLog('AutoPositionedPopup useTextInput: using measureTarget=', usingTextInputRef ? 'textInputRef' : 'refAutoPositionedPopup');
|
|
751
781
|
|
|
752
|
-
//
|
|
782
|
+
// V19f: Position popup above trigger
|
|
783
|
+
// Parent KeyboardAwareScrollView is responsible for scrolling trigger into view
|
|
784
|
+
// This component only handles popup positioning relative to trigger's FINAL position
|
|
785
|
+
const screenHeight = Dimensions.get('window').height;
|
|
786
|
+
const screenWidth = Dimensions.get('window').width;
|
|
787
|
+
const currentKeyboardHeight = keyboardHeightRef.current;
|
|
788
|
+
const popupHeight = listLayout.height; // 200px
|
|
789
|
+
|
|
790
|
+
positionDebugLog(`V19f_SCREEN: height=${screenHeight} width=${screenWidth} keyboardH=${currentKeyboardHeight} statusBarH=${statusBarHeight}`);
|
|
791
|
+
|
|
753
792
|
measureTarget.measureInWindow((x: number | undefined, y: number | undefined, width: number | undefined, height: number | undefined) => {
|
|
754
|
-
|
|
755
|
-
x, y, width, height,
|
|
756
|
-
measureTarget: usingTextInputRef ? 'textInputRef' : 'refAutoPositionedPopup'
|
|
757
|
-
});
|
|
793
|
+
positionDebugLog(`V19f_MEASURE: triggerX=${x} triggerY=${y} triggerW=${width} triggerH=${height}`);
|
|
758
794
|
|
|
759
|
-
// Handle undefined values
|
|
795
|
+
// Handle undefined values
|
|
760
796
|
if (x === undefined || y === undefined || width === undefined || height === undefined) {
|
|
761
|
-
|
|
762
|
-
const
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
height = 50;
|
|
797
|
+
positionDebugLog('V19f: undefined values, using center fallback');
|
|
798
|
+
const fallbackY = (screenHeight - currentKeyboardHeight - popupHeight) / 2;
|
|
799
|
+
updateRootView(tag, {
|
|
800
|
+
style: { top: fallbackY, left: popUpViewStyle?.left, width: popUpViewStyle?.width, height: popupHeight, opacity: 1 }
|
|
801
|
+
});
|
|
802
|
+
hasShownRootView.current = true;
|
|
803
|
+
return;
|
|
769
804
|
}
|
|
770
805
|
|
|
771
|
-
|
|
772
|
-
const
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
componentY: y,
|
|
776
|
-
componentHeight: height,
|
|
777
|
-
listHeight: listLayout.height
|
|
778
|
-
});
|
|
806
|
+
const triggerTop = y;
|
|
807
|
+
const triggerHeight = height;
|
|
808
|
+
const triggerBottom = y + height;
|
|
809
|
+
const keyboardTop = screenHeight - currentKeyboardHeight;
|
|
779
810
|
|
|
780
|
-
|
|
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,
|
|
789
|
-
popupHeight: listLayout.height,
|
|
790
|
-
screenHeight,
|
|
791
|
-
statusBarHeight
|
|
792
|
-
});
|
|
811
|
+
positionDebugLog(`V19f_ANALYSIS: triggerTop=${triggerTop} triggerBottom=${triggerBottom} keyboardTop=${keyboardTop}`);
|
|
793
812
|
|
|
794
|
-
//
|
|
795
|
-
//
|
|
796
|
-
|
|
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;
|
|
813
|
+
// V19f: Position popup DIRECTLY above trigger
|
|
814
|
+
// ADD statusBarHeight to close the gap (coordinates adjustment)
|
|
815
|
+
let popupY = triggerTop - popupHeight + statusBarHeight;
|
|
800
816
|
let position = 'ABOVE';
|
|
801
817
|
|
|
802
|
-
|
|
803
|
-
popupY,
|
|
804
|
-
popupBottom: popupY + listLayout.height,
|
|
805
|
-
contentBottom: popupY + listLayout.height - POPUP_PADDING,
|
|
806
|
-
triggerTop: y,
|
|
807
|
-
paddingOffset: POPUP_PADDING,
|
|
808
|
-
wouldOverlapStatusBar: popupY < statusBarHeight
|
|
809
|
-
});
|
|
818
|
+
positionDebugLog(`V19f_CALC: base=${triggerTop - popupHeight} + statusBarH=${statusBarHeight} = popupY=${popupY}`);
|
|
810
819
|
|
|
811
|
-
//
|
|
812
|
-
if (popupY <
|
|
813
|
-
//
|
|
814
|
-
|
|
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;
|
|
820
|
+
// Safety check: ensure popup doesn't go above screen top
|
|
821
|
+
if (popupY < 0) {
|
|
822
|
+
// If popup would go off screen top, position it BELOW trigger instead
|
|
823
|
+
popupY = triggerBottom + statusBarHeight;
|
|
818
824
|
position = 'BELOW';
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
popupY,
|
|
822
|
-
triggerBottom: y + height,
|
|
823
|
-
buffer: BELOW_BUFFER,
|
|
824
|
-
actualGap: BELOW_BUFFER
|
|
825
|
-
});
|
|
826
|
-
|
|
827
|
-
// 3. Safety check: if BELOW would go off screen bottom, clamp it
|
|
828
|
-
const maxY = screenHeight - listLayout.height;
|
|
825
|
+
// Clamp to stay above keyboard
|
|
826
|
+
const maxY = keyboardTop - popupHeight;
|
|
829
827
|
if (popupY > maxY) {
|
|
830
828
|
popupY = maxY;
|
|
831
|
-
debugLog('AutoPositionedPopup: clamped to screen bottom:', { popupY, maxY });
|
|
832
829
|
}
|
|
833
|
-
|
|
834
|
-
debugLog('AutoPositionedPopup: using ABOVE position (preferred)');
|
|
830
|
+
positionDebugLog(`V19f_BELOW: popupY=${popupY} (clamped to stay above keyboard)`);
|
|
835
831
|
}
|
|
836
832
|
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
left: popUpViewStyle?.left,
|
|
847
|
-
|
|
848
|
-
height: listLayout.height,
|
|
849
|
-
opacity: 1,
|
|
850
|
-
};
|
|
851
|
-
debugLog('AutoPositionedPopup useTextInput: applying new style via updateRootView=', newStyle);
|
|
852
|
-
updateRootView(tag, {style: newStyle});
|
|
833
|
+
// V19f: Verification
|
|
834
|
+
const popupBottom = popupY + popupHeight;
|
|
835
|
+
const gapPixels = triggerTop - popupBottom;
|
|
836
|
+
|
|
837
|
+
positionDebugLog(`V19f_RESULT: position=${position} popupY=${popupY} popupBottom=${popupBottom}`);
|
|
838
|
+
positionDebugLog(`V19f_GAP: trigger_top=${triggerTop} - popup_bottom=${popupBottom} = gap=${gapPixels}px`);
|
|
839
|
+
|
|
840
|
+
ref_listPos.current = {x, y: popupY, width};
|
|
841
|
+
updateRootView(tag, {
|
|
842
|
+
style: { top: popupY, left: popUpViewStyle?.left, width: popUpViewStyle?.width, height: listLayout.height, opacity: 1 }
|
|
843
|
+
});
|
|
853
844
|
hasShownRootView.current = true;
|
|
854
845
|
});
|
|
855
846
|
});
|
|
856
|
-
},
|
|
847
|
+
}, KEYBOARD_STABILIZATION_DELAY) // 1000ms to wait for keyboard + scroll stabilization (user request 2025-01-04)
|
|
857
848
|
} else if (!isKeyboardFullyShown && ref_isFocus.current && keyboardStateChanged) {
|
|
858
849
|
// Only execute close logic when keyboard state actually changes from true to false
|
|
859
850
|
debugLog(
|
|
@@ -938,11 +929,10 @@ const AutoPositionedPopup = memo(
|
|
|
938
929
|
state.selectedItem, showListEmptyComponent, themeMode
|
|
939
930
|
]);
|
|
940
931
|
|
|
941
|
-
//
|
|
942
|
-
//
|
|
943
|
-
// This ensures
|
|
932
|
+
// V18: All positioning logic is now in the useEffect above
|
|
933
|
+
// V18 FIX (2025-01-04): Wait 1000ms after keyboard appears before measuring position
|
|
934
|
+
// This ensures trigger position is stable after KeyboardAwareScrollView scrolls
|
|
944
935
|
// Formula: top = componentY - popupHeight (popup bottom touches trigger top exactly)
|
|
945
|
-
debugLog('🟢 POPUP_MODULE_V16_LOADED - capturing position in onPress callback before setState');
|
|
946
936
|
|
|
947
937
|
// Imperative handle for parent component access
|
|
948
938
|
useImperativeHandle(
|
|
@@ -1341,29 +1331,29 @@ const AutoPositionedPopup = memo(
|
|
|
1341
1331
|
);
|
|
1342
1332
|
}, [
|
|
1343
1333
|
tag,
|
|
1344
|
-
//
|
|
1334
|
+
// ⚠CRITICAL FIX: Remove all props that may change frequently or are inline functions
|
|
1345
1335
|
// Changes to these props should not cause the entire component tree to recreate, especially TextInput
|
|
1346
|
-
// fetchData, //
|
|
1347
|
-
// renderItem, //
|
|
1348
|
-
// onItemSelected, //
|
|
1349
|
-
// onSubmitEditing, //
|
|
1336
|
+
// fetchData, // ❌Removed: inline function
|
|
1337
|
+
// renderItem, // ❌Removed: possibly inline function
|
|
1338
|
+
// onItemSelected, // ❌Removed: possibly inline function
|
|
1339
|
+
// onSubmitEditing, // ❌Removed: possibly inline function
|
|
1350
1340
|
localSearch,
|
|
1351
|
-
// placeholder, //
|
|
1352
|
-
// textAlign, //
|
|
1341
|
+
// placeholder, // ❌Removed: may change
|
|
1342
|
+
// textAlign, // ❌Removed: may change
|
|
1353
1343
|
pageSize,
|
|
1354
1344
|
selectedItem,
|
|
1355
|
-
// CustomRow, //
|
|
1345
|
+
// CustomRow, // ❌Removed: inline function, new reference each time
|
|
1356
1346
|
useTextInput,
|
|
1357
|
-
// btwChildren, //
|
|
1358
|
-
// keyExtractor, //
|
|
1359
|
-
// AutoPositionedPopupBtnStyle, //
|
|
1360
|
-
// CustomPopView, //
|
|
1361
|
-
// CustomPopViewStyle, //
|
|
1347
|
+
// btwChildren, // ❌Removed: inline function
|
|
1348
|
+
// keyExtractor, // ❌Removed: possibly inline function
|
|
1349
|
+
// AutoPositionedPopupBtnStyle, // ❌Removed: possibly inline object
|
|
1350
|
+
// CustomPopView, // ❌Removed: may change
|
|
1351
|
+
// CustomPopViewStyle, // ❌Removed: may change
|
|
1362
1352
|
forceRemoveAllRootViewOnItemSelected,
|
|
1363
1353
|
state.isFocus,
|
|
1364
1354
|
showListEmptyComponent,
|
|
1365
1355
|
emptyText,
|
|
1366
|
-
//
|
|
1356
|
+
// ⚠Removed most dependencies that may cause re-rendering, keeping only core dependencies that truly affect component structure
|
|
1367
1357
|
// This prevents TextInput recreation due to inline functions/objects during parent component redraws
|
|
1368
1358
|
]);
|
|
1369
1359
|
}
|
package/src/KeyboardManager.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useEffect, useState, useRef } from 'react';
|
|
2
|
-
import { Keyboard, EmitterSubscription, Platform } from 'react-native';
|
|
2
|
+
import { Keyboard, EmitterSubscription, Platform, KeyboardEvent } from 'react-native';
|
|
3
3
|
|
|
4
4
|
// DEBUG FLAG: Set to false to disable all console logs for better performance
|
|
5
5
|
const KEYBOARD_DEBUG = false;
|
|
@@ -18,8 +18,16 @@ const debounce = (func: Function, delay: number) => {
|
|
|
18
18
|
};
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
// V19: Return type for keyboard status hook - includes height for accurate positioning
|
|
22
|
+
interface KeyboardStatus {
|
|
23
|
+
isShown: boolean;
|
|
24
|
+
height: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const useKeyboardStatus = (): KeyboardStatus => {
|
|
22
28
|
const [isKeyboardFullyShown, setIsKeyboardFullyShown] = useState(false);
|
|
29
|
+
// V19: Track keyboard height for popup positioning
|
|
30
|
+
const [keyboardHeight, setKeyboardHeight] = useState(0);
|
|
23
31
|
|
|
24
32
|
// Add state cache to avoid repeatedly setting the same state
|
|
25
33
|
const currentKeyboardStatusRef = useRef<boolean>(false);
|
|
@@ -83,7 +91,12 @@ export const useKeyboardStatus = () => {
|
|
|
83
91
|
// Remove Will event listeners to avoid duplicate triggers and state race conditions
|
|
84
92
|
keyboardDidShowListener = Keyboard.addListener(
|
|
85
93
|
'keyboardDidShow',
|
|
86
|
-
() => {
|
|
94
|
+
(e: KeyboardEvent) => {
|
|
95
|
+
// V19: Capture keyboard height from event for accurate popup positioning
|
|
96
|
+
const height = e.endCoordinates?.height || 0;
|
|
97
|
+
debugLog('KeyboardManager: keyboardDidShow event', { height });
|
|
98
|
+
setKeyboardHeight(height);
|
|
99
|
+
|
|
87
100
|
// ✅ FIX: Add protection at event listener level - skip if keyboard is already open
|
|
88
101
|
if (currentKeyboardStatusRef.current === true) {
|
|
89
102
|
debugLog('KeyboardManager: Skip keyboardDidShow event - Keyboard is already open');
|
|
@@ -95,6 +108,9 @@ export const useKeyboardStatus = () => {
|
|
|
95
108
|
keyboardDidHideListener = Keyboard.addListener(
|
|
96
109
|
'keyboardDidHide',
|
|
97
110
|
() => {
|
|
111
|
+
// V19: Reset keyboard height when keyboard hides
|
|
112
|
+
setKeyboardHeight(0);
|
|
113
|
+
|
|
98
114
|
// ✅ FIX: Add protection at event listener level - skip if keyboard is already closed
|
|
99
115
|
if (currentKeyboardStatusRef.current === false) {
|
|
100
116
|
debugLog('KeyboardManager: Skip keyboardDidHide event - Keyboard is already closed');
|
|
@@ -110,5 +126,6 @@ export const useKeyboardStatus = () => {
|
|
|
110
126
|
};
|
|
111
127
|
}, []);
|
|
112
128
|
|
|
113
|
-
|
|
129
|
+
// V19: Return both keyboard visibility status and height
|
|
130
|
+
return { isShown: isKeyboardFullyShown, height: keyboardHeight };
|
|
114
131
|
};
|