react-native-auto-positioned-popup 1.2.13 → 1.2.16
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 +220 -78
- package/lib/AutoPositionedPopup.js.map +1 -1
- package/lib/AutoPositionedPopupProps.d.ts +23 -1
- package/lib/AutoPositionedPopupProps.d.ts.map +1 -1
- package/lib/KeyboardManager.js +7 -6
- package/lib/RootViewContext.js +9 -8
- package/lib/constants.js +13 -0
- package/package.json +6 -5
- package/src/AutoPositionedPopup.tsx +167 -5
- package/src/AutoPositionedPopupProps.ts +23 -1
|
@@ -1,12 +1,13 @@
|
|
|
1
|
+
import { debugLog } from './constants';
|
|
1
2
|
import React, { forwardRef, memo, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState, } from 'react';
|
|
2
|
-
import { Dimensions, Keyboard, Platform, StatusBar, Text, TextInput as RNTextInput, TouchableOpacity, View, } from 'react-native';
|
|
3
|
+
import { Dimensions, findNodeHandle, Keyboard, Platform, StatusBar, Text, TextInput as RNTextInput, TouchableOpacity, View, } from 'react-native';
|
|
3
4
|
import { AdvancedFlatList } from 'react-native-advanced-flatlist';
|
|
4
5
|
import styles from './AutoPositionedPopup.style';
|
|
5
6
|
import { useRootView } from './RootViewContext';
|
|
6
7
|
import { useKeyboardStatus } from './KeyboardManager';
|
|
7
8
|
const queryChangeListeners = [];
|
|
8
9
|
const emitQueryChange = (query) => {
|
|
9
|
-
|
|
10
|
+
debugLog('AutoPositionedPopup.tsx emitQueryChange query=', query, ' listeners=', queryChangeListeners.length);
|
|
10
11
|
queryChangeListeners.forEach((l) => l(query));
|
|
11
12
|
};
|
|
12
13
|
const subscribeQueryChange = (listener) => {
|
|
@@ -34,14 +35,14 @@ const ListItem = memo(({ updateState, item, index, selectedItem, themeMode }) =>
|
|
|
34
35
|
rootViewsRef.current = rootViews;
|
|
35
36
|
}, [rootViews]);
|
|
36
37
|
return useMemo(() => {
|
|
37
|
-
//
|
|
38
|
+
// debugLog('AutoPositionedPopup.tsx ListItem=', {index, item, selectedItem});
|
|
38
39
|
const isSelected = item.id === (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.id) || item.title == (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.title);
|
|
39
40
|
return (<TouchableOpacity key={item.id} style={[
|
|
40
41
|
styles.commonModalRow,
|
|
41
42
|
{ backgroundColor: isSelected ? (themeMode === 'light' ? 'rgba(116, 116, 128, 0.08)' : 'rgba(120, 120, 128, 0.36)') : 'transparent' },
|
|
42
43
|
]} onPress={() => {
|
|
43
|
-
//
|
|
44
|
-
//
|
|
44
|
+
// debugLog('AutoPositionedPopup.tsx ListItem onPress item=', item); // Commented to prevent spam
|
|
45
|
+
// debugLog('AutoPositionedPopup.tsx ListItem onPress rootViews=', rootViewsRef.current); // Commented to prevent spam
|
|
45
46
|
updateState('selectedItem', item);
|
|
46
47
|
}}>
|
|
47
48
|
<Text style={(themeMode === 'light' ? styles.ListItemCode : Object.assign(Object.assign({}, styles.ListItemCode), { color: '#fff' }))} numberOfLines={1} ellipsizeMode="tail">
|
|
@@ -69,16 +70,16 @@ const AutoPositionedPopupList = memo(({ tag, updateState, fetchData, keyExtracto
|
|
|
69
70
|
useEffect(() => {
|
|
70
71
|
(async () => {
|
|
71
72
|
})();
|
|
72
|
-
|
|
73
|
+
debugLog(`AutoPositionedPopupList componentDidMount`);
|
|
73
74
|
//componentWillUnmount
|
|
74
75
|
return () => {
|
|
75
|
-
|
|
76
|
+
debugLog(`AutoPositionedPopupList componentWillUnmount`);
|
|
76
77
|
setSearchQuery('');
|
|
77
78
|
};
|
|
78
79
|
}, []);
|
|
79
80
|
useEffect(() => {
|
|
80
81
|
const unsubscribe = subscribeQueryChange((newQuery) => {
|
|
81
|
-
|
|
82
|
+
debugLog('AutoPositionedPopupList useEffect subscribeQueryChange newQuery=', newQuery);
|
|
82
83
|
ref_searchQuery.current = newQuery;
|
|
83
84
|
if (ref_list.current) {
|
|
84
85
|
ref_list.current.scrollToTop();
|
|
@@ -88,19 +89,19 @@ const AutoPositionedPopupList = memo(({ tag, updateState, fetchData, keyExtracto
|
|
|
88
89
|
return unsubscribe;
|
|
89
90
|
}, []);
|
|
90
91
|
const _updateState = (key, value) => {
|
|
91
|
-
|
|
92
|
+
debugLog('AutoPositionedPopupList _updateState key=', key, ' value=', value);
|
|
92
93
|
setState((prevState) => (Object.assign(Object.assign({}, prevState), { [key]: value })));
|
|
93
|
-
|
|
94
|
+
debugLog('AutoPositionedPopupList _updateState rootViews=', rootViewsRef.current);
|
|
94
95
|
updateState(key, value);
|
|
95
96
|
};
|
|
96
97
|
const _fetchData = async ({ pageIndex, pageSize: currentPageSize, }) => {
|
|
97
|
-
|
|
98
|
+
debugLog('AutoPositionedPopupList _fetchData=', { pageIndex, pageSize: currentPageSize, 'state.localData': state.localData, 'ref_searchQuery.current': ref_searchQuery.current, localSearch });
|
|
98
99
|
if (localSearch && state.localData.length > 0) {
|
|
99
100
|
const result = state.localData.filter((item) => {
|
|
100
101
|
var _a;
|
|
101
102
|
return (_a = `${item.title}`) === null || _a === void 0 ? void 0 : _a.toLowerCase().includes(ref_searchQuery.current.toLowerCase());
|
|
102
103
|
});
|
|
103
|
-
|
|
104
|
+
debugLog('AutoPositionedPopupList _fetchData localSearch result=', result);
|
|
104
105
|
return Promise.resolve({
|
|
105
106
|
items: result,
|
|
106
107
|
pageIndex: 0,
|
|
@@ -113,7 +114,7 @@ const AutoPositionedPopupList = memo(({ tag, updateState, fetchData, keyExtracto
|
|
|
113
114
|
pageSize: pageSize || 10,
|
|
114
115
|
searchQuery: ref_searchQuery.current,
|
|
115
116
|
});
|
|
116
|
-
|
|
117
|
+
debugLog('AutoPositionedPopupList _fetchData res=', res);
|
|
117
118
|
if ((res === null || res === void 0 ? void 0 : res.items) && localSearch) {
|
|
118
119
|
setState((prevState) => {
|
|
119
120
|
return Object.assign(Object.assign({}, prevState), { localData: res.items });
|
|
@@ -132,14 +133,14 @@ const AutoPositionedPopupList = memo(({ tag, updateState, fetchData, keyExtracto
|
|
|
132
133
|
catch (e) {
|
|
133
134
|
console.warn('Error in fetchData:', e);
|
|
134
135
|
}
|
|
135
|
-
|
|
136
|
+
debugLog('AutoPositionedPopupList _fetchData res=', null);
|
|
136
137
|
return null;
|
|
137
138
|
};
|
|
138
139
|
const _renderItem = useCallback(({ item, index }) => {
|
|
139
140
|
return <ListItem item={item} index={index} updateState={_updateState} selectedItem={state.selectedItem} themeMode={themeMode}/>;
|
|
140
141
|
}, [state.selectedItem, themeMode]);
|
|
141
142
|
return useMemo(() => {
|
|
142
|
-
|
|
143
|
+
debugLog('AutoPositionedPopupList (global as any)?.$fake=', global === null || global === void 0 ? void 0 : global.$fake);
|
|
143
144
|
// Babel configuration handles the path redirection based on global.$fake
|
|
144
145
|
// No need for conditional import here
|
|
145
146
|
return (<View style={[styles.baseModalView, styles.autoPositionedPopupList, { backgroundColor: themeMode === 'light' ? '#fff' : 'rgba(44, 44, 46, 1)', }]}>
|
|
@@ -164,7 +165,7 @@ const listLayout = {
|
|
|
164
165
|
};
|
|
165
166
|
// Main AutoPositionedPopup component
|
|
166
167
|
const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
167
|
-
|
|
168
|
+
debugLog('AutoPositionedPopup props=', props);
|
|
168
169
|
const { tag, style, AutoPositionedPopupBtnStyle, placeholder = 'Please Select', onSubmitEditing, TextInputProps, //= {autoFocus: true},
|
|
169
170
|
inputStyle, labelStyle, popUpViewStyle = { left: '5%', width: '90%' }, fetchData = async ({ pageIndex, pageSize, searchQuery, }) => {
|
|
170
171
|
const res = {
|
|
@@ -174,7 +175,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
174
175
|
};
|
|
175
176
|
try {
|
|
176
177
|
// const res1: any[] = await $api.xxx(pageSize)
|
|
177
|
-
//
|
|
178
|
+
// debugLog('${NAME} xxx res=', res)
|
|
178
179
|
// res.items = res1
|
|
179
180
|
// res.needLoadMore = res1.length === pageSize
|
|
180
181
|
}
|
|
@@ -184,7 +185,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
184
185
|
return res;
|
|
185
186
|
}, 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)',
|
|
186
187
|
// textAlign = 'right',
|
|
187
|
-
CustomPopView = undefined, CustomPopViewStyle, showListEmptyComponent = true, emptyText = '', onChangeText, themeMode = 'light', } = props;
|
|
188
|
+
CustomPopView = undefined, CustomPopViewStyle, showListEmptyComponent = true, emptyText = '', onChangeText, themeMode = 'light', parentScrollViewRef, scrollExtraHeight = 100, } = props;
|
|
188
189
|
// State management similar to project implementation
|
|
189
190
|
const [state, setState] = useState({
|
|
190
191
|
isFocus: false,
|
|
@@ -203,6 +204,8 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
203
204
|
const keyboardVisibleRef = useRef(false);
|
|
204
205
|
const refAutoPositionedPopup = useRef(null);
|
|
205
206
|
const ref_searchQuery = useRef('');
|
|
207
|
+
// Store trigger button position when clicked (before it's replaced by TextInput)
|
|
208
|
+
const triggerPositionRef = useRef(null);
|
|
206
209
|
// Add ref to track previous keyboard state to avoid false triggers during parent component re-renders
|
|
207
210
|
const prevIsKeyboardFullyShownRef = useRef(false);
|
|
208
211
|
const prevPropsRef = useRef({});
|
|
@@ -235,16 +238,136 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
235
238
|
ref_isKeyboardFullyShown.current = isKeyboardFullyShown;
|
|
236
239
|
}, [isKeyboardFullyShown]);
|
|
237
240
|
const theme = defaultTheme;
|
|
241
|
+
/**
|
|
242
|
+
* Scrolls the parent KeyboardAwareScrollView to make the trigger button visible
|
|
243
|
+
* when keyboard appears and may cover the trigger.
|
|
244
|
+
* Uses the triggerBtnRef to measure position and scrolls parent accordingly.
|
|
245
|
+
*/
|
|
246
|
+
const scrollParentToTrigger = useCallback(() => {
|
|
247
|
+
if (!(parentScrollViewRef === null || parentScrollViewRef === void 0 ? void 0 : parentScrollViewRef.current) || !triggerBtnRef.current) {
|
|
248
|
+
debugLog('AutoPositionedPopup scrollParentToTrigger: No parentScrollViewRef or triggerBtnRef available');
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
// Use scrollToFocusedInput method from KeyboardAwareScrollView
|
|
252
|
+
// This method scrolls the ScrollView to make the specified node visible
|
|
253
|
+
const scrollView = parentScrollViewRef.current;
|
|
254
|
+
const nodeHandle = findNodeHandle(triggerBtnRef.current);
|
|
255
|
+
if (nodeHandle && scrollView) {
|
|
256
|
+
debugLog('AutoPositionedPopup scrollParentToTrigger: Scrolling to trigger button with extraHeight=', scrollExtraHeight);
|
|
257
|
+
// KeyboardAwareScrollView has a scrollToFocusedInput method that handles this
|
|
258
|
+
// However, it requires a ReactNode. We'll use scrollToPosition as an alternative.
|
|
259
|
+
// First, measure the trigger button position relative to the ScrollView
|
|
260
|
+
triggerBtnRef.current.measureInWindow((x, y, width, height) => {
|
|
261
|
+
if (y === undefined || height === undefined) {
|
|
262
|
+
debugLog('AutoPositionedPopup scrollParentToTrigger: measureInWindow returned undefined');
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
debugLog('AutoPositionedPopup scrollParentToTrigger: trigger position=', { x, y, width, height });
|
|
266
|
+
// Get keyboard height from Keyboard API
|
|
267
|
+
// On keyboard show, scroll to position that keeps trigger above keyboard
|
|
268
|
+
Keyboard.addListener('keyboardDidShow', (event) => {
|
|
269
|
+
const keyboardHeight = event.endCoordinates.height;
|
|
270
|
+
const screenHeight = Dimensions.get('window').height;
|
|
271
|
+
// Calculate if trigger is below keyboard
|
|
272
|
+
const triggerBottom = y + height;
|
|
273
|
+
const visibleAreaBottom = screenHeight - keyboardHeight;
|
|
274
|
+
debugLog('AutoPositionedPopup scrollParentToTrigger: keyboard data=', {
|
|
275
|
+
keyboardHeight,
|
|
276
|
+
screenHeight,
|
|
277
|
+
triggerBottom,
|
|
278
|
+
visibleAreaBottom,
|
|
279
|
+
needsScroll: triggerBottom > visibleAreaBottom
|
|
280
|
+
});
|
|
281
|
+
if (triggerBottom > visibleAreaBottom) {
|
|
282
|
+
// Calculate how much to scroll
|
|
283
|
+
const scrollAmount = triggerBottom - visibleAreaBottom + scrollExtraHeight;
|
|
284
|
+
debugLog('AutoPositionedPopup scrollParentToTrigger: scrolling by', scrollAmount);
|
|
285
|
+
// Use scrollForExtraHeightOnAndroid or scrollToPosition
|
|
286
|
+
if (typeof scrollView.scrollToPosition === 'function') {
|
|
287
|
+
// scrollToPosition(x, y, animated)
|
|
288
|
+
scrollView.scrollToPosition(0, scrollAmount, true);
|
|
289
|
+
}
|
|
290
|
+
else if (typeof scrollView.scrollToEnd === 'function') {
|
|
291
|
+
// Fallback: scroll to end might help in some cases
|
|
292
|
+
debugLog('AutoPositionedPopup scrollParentToTrigger: using scrollToEnd fallback');
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
}, [parentScrollViewRef, scrollExtraHeight]);
|
|
299
|
+
/**
|
|
300
|
+
* Scroll parent KeyboardAwareScrollView to keep trigger visible above keyboard
|
|
301
|
+
* Uses stored trigger position (captured before TextInput replaces the trigger button)
|
|
302
|
+
*/
|
|
303
|
+
const scrollToTriggerWithMeasure = useCallback(() => {
|
|
304
|
+
debugLog('AutoPositionedPopup scrollToTriggerWithMeasure called, tag=', tag, {
|
|
305
|
+
hasParentScrollViewRef: !!(parentScrollViewRef === null || parentScrollViewRef === void 0 ? void 0 : parentScrollViewRef.current),
|
|
306
|
+
hasTriggerPosition: !!triggerPositionRef.current,
|
|
307
|
+
triggerPosition: triggerPositionRef.current
|
|
308
|
+
});
|
|
309
|
+
if (!(parentScrollViewRef === null || parentScrollViewRef === void 0 ? void 0 : parentScrollViewRef.current)) {
|
|
310
|
+
debugLog('AutoPositionedPopup scrollToTriggerWithMeasure: parentScrollViewRef not available, tag=', tag);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
// Use stored trigger position (captured when trigger was clicked)
|
|
314
|
+
const storedPosition = triggerPositionRef.current;
|
|
315
|
+
if (!storedPosition) {
|
|
316
|
+
debugLog('AutoPositionedPopup scrollToTriggerWithMeasure: no stored trigger position, tag=', tag);
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
const scrollView = parentScrollViewRef.current;
|
|
320
|
+
const { y: triggerY, height: triggerHeight } = storedPosition;
|
|
321
|
+
// Get keyboard height and screen dimensions
|
|
322
|
+
const screenHeight = Dimensions.get('window').height;
|
|
323
|
+
// Calculate keyboard height (approximate - keyboard typically takes 40-50% of screen on Android)
|
|
324
|
+
// We'll use the trigger position to determine if scrolling is needed
|
|
325
|
+
const keyboardApproxHeight = screenHeight * 0.4; // Conservative estimate
|
|
326
|
+
const visibleAreaBottom = screenHeight - keyboardApproxHeight;
|
|
327
|
+
const triggerBottom = triggerY + triggerHeight;
|
|
328
|
+
debugLog('AutoPositionedPopup scrollToTriggerWithMeasure: calculations=', {
|
|
329
|
+
tag,
|
|
330
|
+
triggerY,
|
|
331
|
+
triggerHeight,
|
|
332
|
+
triggerBottom,
|
|
333
|
+
screenHeight,
|
|
334
|
+
visibleAreaBottom,
|
|
335
|
+
needsScroll: triggerBottom > visibleAreaBottom
|
|
336
|
+
});
|
|
337
|
+
// Check if trigger is below the visible area (covered by keyboard)
|
|
338
|
+
if (triggerBottom > visibleAreaBottom) {
|
|
339
|
+
// Calculate scroll amount to bring trigger above keyboard
|
|
340
|
+
const scrollAmount = triggerBottom - visibleAreaBottom + scrollExtraHeight;
|
|
341
|
+
debugLog('AutoPositionedPopup scrollToTriggerWithMeasure: scrolling, amount=', scrollAmount, 'tag=', tag);
|
|
342
|
+
// Use scrollForExtraHeightOnAndroid for KeyboardAwareScrollView
|
|
343
|
+
if (typeof scrollView.scrollForExtraHeightOnAndroid === 'function') {
|
|
344
|
+
scrollView.scrollForExtraHeightOnAndroid(scrollAmount);
|
|
345
|
+
}
|
|
346
|
+
else if (typeof scrollView.scrollToPosition === 'function') {
|
|
347
|
+
scrollView.scrollToPosition(0, scrollAmount, true);
|
|
348
|
+
}
|
|
349
|
+
else if ('scrollTo' in scrollView && typeof scrollView.scrollTo === 'function') {
|
|
350
|
+
// Fallback to standard ScrollView method
|
|
351
|
+
scrollView.scrollTo({ y: scrollAmount, animated: true });
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
debugLog('AutoPositionedPopup scrollToTriggerWithMeasure: no scroll method available on scrollView');
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
else {
|
|
358
|
+
debugLog('AutoPositionedPopup scrollToTriggerWithMeasure: trigger already visible, no scroll needed, tag=', tag);
|
|
359
|
+
}
|
|
360
|
+
}, [parentScrollViewRef, scrollExtraHeight, tag]);
|
|
238
361
|
/**
|
|
239
362
|
* componentDidMount && componentWillUnmount
|
|
240
363
|
*/
|
|
241
364
|
useEffect(() => {
|
|
242
365
|
(async () => {
|
|
243
366
|
})();
|
|
244
|
-
|
|
367
|
+
debugLog(`AutoPositionedPopup componentDidMount=`, { tag, CustomPopView });
|
|
245
368
|
//componentWillUnmount
|
|
246
369
|
return () => {
|
|
247
|
-
|
|
370
|
+
debugLog(`AutoPositionedPopup componentWillUnmount tag=`, tag);
|
|
248
371
|
removeRootView(tag, forceRemoveAllRootViewOnItemSelected, rootViewsRef.current);
|
|
249
372
|
setSearchQuery('');
|
|
250
373
|
if (textInputRef.current) {
|
|
@@ -257,7 +380,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
257
380
|
};
|
|
258
381
|
}, []);
|
|
259
382
|
useEffect(() => {
|
|
260
|
-
|
|
383
|
+
debugLog('AutoPositionedPopup rootViews=', { tag, rootViews });
|
|
261
384
|
rootViewsRef.current = rootViews;
|
|
262
385
|
if (rootViews.length === 0) {
|
|
263
386
|
hasAddedRootView.current = false;
|
|
@@ -272,10 +395,10 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
272
395
|
}, [rootViews]);
|
|
273
396
|
useEffect(() => {
|
|
274
397
|
var _a, _b;
|
|
275
|
-
|
|
276
|
-
|
|
398
|
+
debugLog('AutoPositionedPopup useEffect [selectedItem, state.selectedItem, tag]=', { tag, selectedItem, 'state.selectedItem': state.selectedItem });
|
|
399
|
+
debugLog('AutoPositionedPopup useEffect state.selectedItem=', state.selectedItem);
|
|
277
400
|
if (((_a = state.selectedItem) === null || _a === void 0 ? void 0 : _a.id) !== (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.id) || ((_b = state.selectedItem) === null || _b === void 0 ? void 0 : _b.title) != (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.title)) {
|
|
278
|
-
|
|
401
|
+
debugLog('AutoPositionedPopup useEffect selectedItem!=state.selectedItem');
|
|
279
402
|
setState((prevState) => {
|
|
280
403
|
return Object.assign(Object.assign({}, prevState), { selectedItem: selectedItem });
|
|
281
404
|
});
|
|
@@ -288,7 +411,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
288
411
|
const propsChanged = prevPropsRef.current.CustomPopView !== CustomPopView ||
|
|
289
412
|
prevPropsRef.current.CustomPopViewStyle !== CustomPopViewStyle ||
|
|
290
413
|
(prevPropsRef.current.TextInputProps !== TextInputProps && useTextInput);
|
|
291
|
-
|
|
414
|
+
debugLog('AutoPositionedPopup useEffect [isKeyboardFullyShown,\n' +
|
|
292
415
|
' state.isFocus,\n' +
|
|
293
416
|
' useTextInput,\n' +
|
|
294
417
|
' CustomPopView,\n' +
|
|
@@ -320,7 +443,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
320
443
|
};
|
|
321
444
|
// Only execute logic when keyboard state actually changes or user actively operates
|
|
322
445
|
if (!keyboardStateChanged && hasAddedRootView.current) {
|
|
323
|
-
|
|
446
|
+
debugLog('AutoPositionedPopup: Skip execution - parent component re-rendered but keyboard state unchanged textInputRef.current=', textInputRef.current);
|
|
324
447
|
// if (!ref_isFocus.current) {
|
|
325
448
|
// textInputRef.current?.focus()
|
|
326
449
|
// }
|
|
@@ -342,6 +465,16 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
342
465
|
const statusBarHeight = getStatusBarHeight();
|
|
343
466
|
if (useTextInput) {
|
|
344
467
|
if (isKeyboardFullyShown && hasAddedRootView.current && !hasShownRootView.current && state.isFocus) {
|
|
468
|
+
// KEYBOARD AVOIDANCE FIX: Scroll parent ScrollView to keep trigger visible
|
|
469
|
+
// When keyboard appears, the trigger button may be covered. If parentScrollViewRef
|
|
470
|
+
// is provided, scroll the parent to keep the trigger visible above the keyboard.
|
|
471
|
+
if (parentScrollViewRef === null || parentScrollViewRef === void 0 ? void 0 : parentScrollViewRef.current) {
|
|
472
|
+
debugLog('AutoPositionedPopup: Keyboard appeared, scrolling parent to keep trigger visible');
|
|
473
|
+
// Use a slight delay to ensure keyboard animation has started
|
|
474
|
+
setTimeout(() => {
|
|
475
|
+
scrollToTriggerWithMeasure();
|
|
476
|
+
}, 100);
|
|
477
|
+
}
|
|
345
478
|
// CRITICAL FIX FOR KEYBOARD POSITION CALCULATION
|
|
346
479
|
// Problem: When keyboard appears, the page shifts up but measureInWindow executes too early
|
|
347
480
|
// Solution: Wait for keyboard animation (300ms) + use requestAnimationFrame for next render frame
|
|
@@ -361,7 +494,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
361
494
|
const measureTarget = triggerBtnRef.current || refAutoPositionedPopup.current;
|
|
362
495
|
measureTarget === null || measureTarget === void 0 ? void 0 : measureTarget.measureInWindow((x, y, width, height) => {
|
|
363
496
|
var _a;
|
|
364
|
-
|
|
497
|
+
debugLog('AutoPositionedPopup useTextInput measureInWindow (after 300ms + RAF, layout stable)=', { x, y, width, height, usingTriggerRef: !!triggerBtnRef.current });
|
|
365
498
|
// CRITICAL FIX: Handle undefined values from measureInWindow
|
|
366
499
|
// This can happen during navigation transitions or when view is not yet mounted
|
|
367
500
|
if (x === undefined || y === undefined || width === undefined || height === undefined) {
|
|
@@ -389,7 +522,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
389
522
|
// we should directly use measureInWindow's y value without additional calculations
|
|
390
523
|
// The popup container is at the same level as the page content
|
|
391
524
|
const screenHeight = Dimensions.get('window').height; // Use window height, not screen
|
|
392
|
-
|
|
525
|
+
debugLog('AutoPositionedPopup useTextInput positioning data=', {
|
|
393
526
|
screenHeight,
|
|
394
527
|
componentY: y,
|
|
395
528
|
componentHeight: height,
|
|
@@ -401,26 +534,27 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
401
534
|
// So we should NOT add statusBarHeight to the position calculation
|
|
402
535
|
//
|
|
403
536
|
// 1. Default: show popup ABOVE the input field
|
|
404
|
-
//
|
|
405
|
-
//
|
|
406
|
-
//
|
|
407
|
-
let popupY = y + height - listLayout.height;
|
|
408
|
-
|
|
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:', {
|
|
409
542
|
componentY: y,
|
|
410
543
|
componentHeight: height,
|
|
411
544
|
popupHeight: listLayout.height,
|
|
412
545
|
popupY,
|
|
413
|
-
|
|
546
|
+
popupBottom: popupY + listLayout.height,
|
|
547
|
+
triggerTop: y,
|
|
414
548
|
statusBarHeight
|
|
415
549
|
});
|
|
416
550
|
// 2. Check if showing above would go behind status bar
|
|
417
551
|
if (popupY < statusBarHeight) {
|
|
418
|
-
|
|
552
|
+
debugLog('AutoPositionedPopup with keyboard: would go behind status bar, showing BELOW instead');
|
|
419
553
|
// Show BELOW the input field
|
|
420
554
|
// Since y + height represents the trigger's "reference bottom" (accounting for measurement offset),
|
|
421
555
|
// we need to add another height to position popup BELOW the actual trigger
|
|
422
556
|
popupY = y + height + height;
|
|
423
|
-
|
|
557
|
+
debugLog('AutoPositionedPopup with keyboard: BELOW position calculated:', {
|
|
424
558
|
formula: 'y + 2*height',
|
|
425
559
|
y,
|
|
426
560
|
height,
|
|
@@ -430,15 +564,15 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
430
564
|
const maxY = screenHeight - listLayout.height;
|
|
431
565
|
if (popupY > maxY) {
|
|
432
566
|
// If both positions are problematic, clamp to visible area
|
|
433
|
-
|
|
567
|
+
debugLog('AutoPositionedPopup with keyboard: both positions problematic, clamping to visible area');
|
|
434
568
|
popupY = Math.min(Math.max(statusBarHeight, y - listLayout.height), maxY);
|
|
435
569
|
}
|
|
436
570
|
}
|
|
437
571
|
else {
|
|
438
|
-
|
|
572
|
+
debugLog('AutoPositionedPopup with keyboard: showing ABOVE input field (preferred position)');
|
|
439
573
|
}
|
|
440
574
|
ref_listPos.current = { x: x, y: popupY, width: width };
|
|
441
|
-
|
|
575
|
+
debugLog('AutoPositionedPopup useTextInput final position=', ref_listPos.current);
|
|
442
576
|
setRootViewNativeStyle(tag, {
|
|
443
577
|
top: (_a = ref_listPos.current) === null || _a === void 0 ? void 0 : _a.y,
|
|
444
578
|
left: popUpViewStyle === null || popUpViewStyle === void 0 ? void 0 : popUpViewStyle.left,
|
|
@@ -453,7 +587,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
453
587
|
}
|
|
454
588
|
else if (!isKeyboardFullyShown && ref_isFocus.current && keyboardStateChanged) {
|
|
455
589
|
// Only execute close logic when keyboard state actually changes from true to false
|
|
456
|
-
|
|
590
|
+
debugLog('AutoPositionedPopup isKeyboardFullyShown useEffect removeRootView (keyboard state changed)=', { tag, forceRemoveAllRootViewOnItemSelected, keyboardStateChanged });
|
|
457
591
|
removeRootView(tag, forceRemoveAllRootViewOnItemSelected);
|
|
458
592
|
setState((prevState) => {
|
|
459
593
|
return Object.assign(Object.assign({}, prevState), { isFocus: false });
|
|
@@ -474,7 +608,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
474
608
|
// This ensures accurate position when component is inside complex layouts like KeyboardAwareScrollView
|
|
475
609
|
const measureTarget = triggerBtnRef.current || refAutoPositionedPopup.current;
|
|
476
610
|
measureTarget === null || measureTarget === void 0 ? void 0 : measureTarget.measureInWindow((x, y, width, height) => {
|
|
477
|
-
|
|
611
|
+
debugLog('AutoPositionedPopup !useTextInput measureInWindow=', { x, y, width, height, usingTriggerRef: !!triggerBtnRef.current });
|
|
478
612
|
// CRITICAL FIX: Handle undefined values from measureInWindow
|
|
479
613
|
// This can happen during navigation transitions or when view is not yet mounted
|
|
480
614
|
if (x === undefined || y === undefined || width === undefined || height === undefined) {
|
|
@@ -486,7 +620,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
486
620
|
const fallbackX = screenWidth * 0.05; // 5% from left
|
|
487
621
|
const fallbackWidth = screenWidth * 0.9; // 90% width
|
|
488
622
|
ref_listPos.current = { x: fallbackX, y: fallbackY, width: fallbackWidth };
|
|
489
|
-
|
|
623
|
+
debugLog('AutoPositionedPopup !useTextInput using fallback position=', ref_listPos.current);
|
|
490
624
|
// Proceed with fallback values
|
|
491
625
|
x = fallbackX;
|
|
492
626
|
y = fallbackY;
|
|
@@ -497,10 +631,10 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
497
631
|
// Default: show popup ABOVE the input field
|
|
498
632
|
// Only if that goes off the top of screen (considering status bar), show BELOW instead
|
|
499
633
|
const calculateOptimalPosition = (componentY, componentHeight, popupHeight) => {
|
|
500
|
-
|
|
634
|
+
debugLog('AutoPositionedPopup calculateOptimalPosition executing');
|
|
501
635
|
// Use window height (visible area) instead of screen height
|
|
502
636
|
const screenHeight = Dimensions.get('window').height;
|
|
503
|
-
|
|
637
|
+
debugLog('AutoPositionedPopup positioning data:', {
|
|
504
638
|
screenHeight,
|
|
505
639
|
componentY,
|
|
506
640
|
componentHeight,
|
|
@@ -520,7 +654,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
520
654
|
// Formula: popup_top = trigger_bottom - componentHeight - popupHeight
|
|
521
655
|
// popup_bottom = trigger_bottom - componentHeight = trigger_top
|
|
522
656
|
let popupY = componentY + componentHeight - popupHeight;
|
|
523
|
-
|
|
657
|
+
debugLog('AutoPositionedPopup: initial calculation for ABOVE position:', {
|
|
524
658
|
componentY,
|
|
525
659
|
componentHeight,
|
|
526
660
|
popupHeight,
|
|
@@ -530,7 +664,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
530
664
|
});
|
|
531
665
|
// 2. Check if showing above would go off the top of screen (behind status bar)
|
|
532
666
|
if (popupY < statusBarHeight) {
|
|
533
|
-
|
|
667
|
+
debugLog('AutoPositionedPopup: would go behind status bar, showing BELOW instead');
|
|
534
668
|
// Show BELOW the trigger element
|
|
535
669
|
// Since componentY + componentHeight represents the trigger's "reference bottom" (accounting for measurement offset),
|
|
536
670
|
// we need to add another componentHeight to position popup BELOW the actual trigger
|
|
@@ -538,7 +672,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
538
672
|
// - (componentY + componentHeight) = trigger's actual top (compensated)
|
|
539
673
|
// - + componentHeight = skip past trigger height to get to trigger's actual bottom
|
|
540
674
|
popupY = componentY + componentHeight + componentHeight;
|
|
541
|
-
|
|
675
|
+
debugLog('AutoPositionedPopup: BELOW position calculated:', {
|
|
542
676
|
formula: 'componentY + 2*componentHeight',
|
|
543
677
|
componentY,
|
|
544
678
|
componentHeight,
|
|
@@ -549,14 +683,14 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
549
683
|
if (popupY > maxY) {
|
|
550
684
|
// If both positions are problematic, clamp to visible area
|
|
551
685
|
// Prioritize showing as close to trigger as possible
|
|
552
|
-
|
|
686
|
+
debugLog('AutoPositionedPopup: both positions problematic, clamping to visible area');
|
|
553
687
|
popupY = Math.min(Math.max(statusBarHeight, componentY - popupHeight), maxY);
|
|
554
688
|
}
|
|
555
689
|
}
|
|
556
690
|
else {
|
|
557
|
-
|
|
691
|
+
debugLog('AutoPositionedPopup: showing ABOVE input field (preferred position)');
|
|
558
692
|
}
|
|
559
|
-
|
|
693
|
+
debugLog('AutoPositionedPopup final position:', {
|
|
560
694
|
popupY,
|
|
561
695
|
'showing above': popupY < componentY,
|
|
562
696
|
'below status bar': popupY >= statusBarHeight
|
|
@@ -567,15 +701,15 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
567
701
|
const actualPopupHeight = CustomPopView && CustomPopViewStyle && typeof CustomPopViewStyle.height === 'number'
|
|
568
702
|
? CustomPopViewStyle.height
|
|
569
703
|
: listLayout.height;
|
|
570
|
-
|
|
704
|
+
debugLog('AutoPositionedPopup 🔥 Using actualPopupHeight for calculation:', { actualPopupHeight, CustomPopView: !!CustomPopView });
|
|
571
705
|
const positionResult = calculateOptimalPosition(y, height, actualPopupHeight);
|
|
572
|
-
|
|
706
|
+
debugLog('AutoPositionedPopup FINAL position result:', positionResult);
|
|
573
707
|
ref_listPos.current = { x: x, y: positionResult.finalY, width: width };
|
|
574
|
-
|
|
708
|
+
debugLog('AutoPositionedPopup !useTextInput ref_listPos.current=', ref_listPos.current);
|
|
575
709
|
if (CustomPopView && CustomPopViewStyle) {
|
|
576
710
|
// Position already calculated correctly above, no need to recalculate
|
|
577
711
|
const PopViewComponent = CustomPopView();
|
|
578
|
-
|
|
712
|
+
debugLog('AutoPositionedPopup !useTextInput addRootView=', { CustomPopViewStyle, PopViewComponent, 'state.selectedItem': state.selectedItem });
|
|
579
713
|
addRootView({
|
|
580
714
|
id: tag,
|
|
581
715
|
style: !centerDisplay
|
|
@@ -583,7 +717,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
583
717
|
component: <PopViewComponent selectedItem={state.selectedItem}></PopViewComponent>,
|
|
584
718
|
useModal: true,
|
|
585
719
|
onModalClose: () => {
|
|
586
|
-
|
|
720
|
+
debugLog('AutoPositionedPopup onModalClose');
|
|
587
721
|
removeRootView(tag, forceRemoveAllRootViewOnItemSelected, rootViewsRef.current);
|
|
588
722
|
setState((prevState) => {
|
|
589
723
|
return Object.assign(Object.assign({}, prevState), { isFocus: false });
|
|
@@ -597,7 +731,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
597
731
|
});
|
|
598
732
|
}
|
|
599
733
|
else {
|
|
600
|
-
|
|
734
|
+
debugLog('AutoPositionedPopup !useTextInput addRootView tag=', tag);
|
|
601
735
|
addRootView({
|
|
602
736
|
id: tag,
|
|
603
737
|
style: {
|
|
@@ -610,7 +744,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
610
744
|
component: (<AutoPositionedPopupList tag={tag} updateState={updateState} fetchData={fetchData} pageSize={pageSize} renderItem={renderItem} selectedItem={state.selectedItem} localSearch={localSearch} showListEmptyComponent={showListEmptyComponent} emptyText={emptyText} themeMode={themeMode}/>),
|
|
611
745
|
useModal: true,
|
|
612
746
|
onModalClose: () => {
|
|
613
|
-
|
|
747
|
+
debugLog('AutoPositionedPopup onModalClose tag=', tag);
|
|
614
748
|
removeRootView(tag, forceRemoveAllRootViewOnItemSelected, rootViewsRef.current);
|
|
615
749
|
setState((prevState) => {
|
|
616
750
|
return Object.assign({}, prevState);
|
|
@@ -645,7 +779,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
645
779
|
// Imperative handle for parent component access
|
|
646
780
|
useImperativeHandle(parentRef, () => ({
|
|
647
781
|
clearSelectedItem: () => {
|
|
648
|
-
|
|
782
|
+
debugLog('AutoPositionedPopup clearSelectedItem tag=', tag);
|
|
649
783
|
setState((prevState) => {
|
|
650
784
|
return Object.assign(Object.assign({}, prevState), { selectedItem: undefined, isFocus: false });
|
|
651
785
|
});
|
|
@@ -662,11 +796,11 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
662
796
|
},
|
|
663
797
|
}), []);
|
|
664
798
|
const updateState = (key, value) => {
|
|
665
|
-
|
|
799
|
+
debugLog('AutoPositionedPopup updateState=', { key, value });
|
|
666
800
|
setState((prevState) => (Object.assign(Object.assign({}, prevState), { [key]: value })));
|
|
667
801
|
if (key === 'selectedItem' && onItemSelected) {
|
|
668
802
|
onItemSelected(value);
|
|
669
|
-
|
|
803
|
+
debugLog('AutoPositionedPopup updateState onItemSelected rootViewsRef.current=', rootViewsRef.current);
|
|
670
804
|
removeRootView(tag, forceRemoveAllRootViewOnItemSelected, rootViewsRef.current);
|
|
671
805
|
hasAddedRootView.current = false;
|
|
672
806
|
hasShownRootView.current = false;
|
|
@@ -699,17 +833,17 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
699
833
|
// Only update when deep comparison detects real changes to avoid TextInput recreation due to reference changes during parent component redraws
|
|
700
834
|
const stableInputStyle = useMemo(() => {
|
|
701
835
|
if (!shallowEqual(stableInputStyleRef.current, inputStyle)) {
|
|
702
|
-
|
|
836
|
+
debugLog(`AutoPositionedPopup stableInputStyle: `, { tag, inputStyle, themeMode });
|
|
703
837
|
stableInputStyleRef.current = inputStyle;
|
|
704
838
|
}
|
|
705
839
|
return stableInputStyleRef.current;
|
|
706
840
|
}, [inputStyle, tag, themeMode]);
|
|
707
841
|
const stableTextInputProps = useMemo(() => {
|
|
708
842
|
if (!shallowEqual(stableTextInputPropsRef.current, TextInputProps)) {
|
|
709
|
-
|
|
843
|
+
debugLog(`AutoPositionedPopup TextInputProps deep change detected, updating stable reference - tag: ${tag}`);
|
|
710
844
|
stableTextInputPropsRef.current = TextInputProps;
|
|
711
845
|
}
|
|
712
|
-
|
|
846
|
+
debugLog('AutoPositionedPopup stableTextInputProps=', { tag, TextInputProps, 'stableTextInputPropsRef.current': stableTextInputPropsRef.current });
|
|
713
847
|
return stableTextInputPropsRef.current;
|
|
714
848
|
}, [TextInputProps, tag]);
|
|
715
849
|
// Use useCallback to stabilize onFocus and onBlur callback references
|
|
@@ -720,7 +854,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
720
854
|
const handleTextInputFocus = useCallback(() => {
|
|
721
855
|
const currentTime = Date.now();
|
|
722
856
|
const timeSinceLastFocus = currentTime - lastFocusTimeRef.current;
|
|
723
|
-
|
|
857
|
+
debugLog('AutoPositionedPopup onFocus=', {
|
|
724
858
|
tag,
|
|
725
859
|
'state.selectedItem': stateRef.current.selectedItem,
|
|
726
860
|
'hasTriggeredFocus.current=': hasTriggeredFocus.current,
|
|
@@ -732,17 +866,17 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
732
866
|
});
|
|
733
867
|
// Prevent rapid repeated triggers (repeated events within 300ms are ignored)
|
|
734
868
|
if (timeSinceLastFocus < 300) {
|
|
735
|
-
|
|
869
|
+
debugLog('AutoPositionedPopup onFocus: Skip - event triggered too quickly (< 300ms)');
|
|
736
870
|
return;
|
|
737
871
|
}
|
|
738
872
|
// Skip if keyboard is already open and focus has been handled
|
|
739
873
|
if (isKeyboardFullyShown && hasTriggeredFocus.current) {
|
|
740
|
-
|
|
874
|
+
debugLog('AutoPositionedPopup onFocus: Skip - keyboard already open and focus handled');
|
|
741
875
|
return;
|
|
742
876
|
}
|
|
743
877
|
// Prevent concurrent processing
|
|
744
878
|
if (isFocusEventProcessingRef.current) {
|
|
745
|
-
|
|
879
|
+
debugLog('AutoPositionedPopup onFocus: Skip - processing another focus event');
|
|
746
880
|
return;
|
|
747
881
|
}
|
|
748
882
|
isFocusEventProcessingRef.current = true;
|
|
@@ -765,7 +899,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
765
899
|
}, 100);
|
|
766
900
|
}, [tag, isKeyboardFullyShown]); // Remove state.selectedItem, use stateRef instead
|
|
767
901
|
const handleTextInputBlur = useCallback(() => {
|
|
768
|
-
|
|
902
|
+
debugLog('AutoPositionedPopup onBlur=', {
|
|
769
903
|
tag,
|
|
770
904
|
'textInputRef.current': textInputRef.current,
|
|
771
905
|
'isKeyboardFullyShown': isKeyboardFullyShown,
|
|
@@ -773,7 +907,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
773
907
|
});
|
|
774
908
|
// If keyboard is still open, this is a false trigger caused by parent component re-render, should not reset
|
|
775
909
|
if (isKeyboardFullyShown && hasTriggeredFocus.current) {
|
|
776
|
-
|
|
910
|
+
debugLog('AutoPositionedPopup onBlur: Skip - keyboard still open, possibly caused by parent component re-render');
|
|
777
911
|
return;
|
|
778
912
|
}
|
|
779
913
|
// Only reset internal state, do not actively close keyboard
|
|
@@ -797,20 +931,20 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
797
931
|
// Wrap TextInput independently in useMemo to recreate only when key props change
|
|
798
932
|
// This avoids repeated ref callback triggers due to other props changes during parent component redraws
|
|
799
933
|
const memoizedTextInput = useMemo(() => {
|
|
800
|
-
|
|
934
|
+
debugLog('AutoPositionedPopup memoizedTextInput=', { tag, useTextInput, 'state.isFocus': state.isFocus, stableTextInputProps });
|
|
801
935
|
if (!useTextInput || !state.isFocus) {
|
|
802
936
|
return null;
|
|
803
937
|
}
|
|
804
938
|
return (<RNTextInput ref={(ref) => {
|
|
805
939
|
// Monitor TextInput mounting and unmounting
|
|
806
940
|
if (ref && !textInputRef.current) {
|
|
807
|
-
|
|
941
|
+
debugLog(`AutoPositionedPopup TextInput created/mounted - tag: ${tag}, ref:`, ref);
|
|
808
942
|
}
|
|
809
943
|
else if (!ref && textInputRef.current) {
|
|
810
|
-
|
|
944
|
+
debugLog(`AutoPositionedPopup TextInput unmounted - tag: ${tag}`);
|
|
811
945
|
}
|
|
812
946
|
else if (ref && textInputRef.current && ref !== textInputRef.current) {
|
|
813
|
-
|
|
947
|
+
debugLog(`AutoPositionedPopup TextInput replaced - tag: ${tag}, oldRef:`, textInputRef.current, 'newRef:', ref);
|
|
814
948
|
}
|
|
815
949
|
textInputRef.current = ref;
|
|
816
950
|
}} key={`textinput-${tag}`} style={[
|
|
@@ -819,7 +953,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
819
953
|
(themeMode === 'dark' && { color: '#fff' })
|
|
820
954
|
]} textAlign={stableTextInputProps && stableTextInputProps['textAlign'] || 'left'} multiline={stableTextInputProps && stableTextInputProps['multiline'] || false} numberOfLines={stableTextInputProps && stableTextInputProps['numberOfLines'] || 1} onChangeText={(searchQuery) => {
|
|
821
955
|
ref_searchQuery.current = searchQuery;
|
|
822
|
-
|
|
956
|
+
debugLog('AutoPositionedPopup onChangeText rootViews=', rootViews);
|
|
823
957
|
if (!localSearch) {
|
|
824
958
|
if (debounceTimerRef.current) {
|
|
825
959
|
clearTimeout(debounceTimerRef.current);
|
|
@@ -838,7 +972,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
838
972
|
Keyboard.dismiss();
|
|
839
973
|
}
|
|
840
974
|
}} keyboardType={stableTextInputProps && stableTextInputProps['keyboardType'] || 'default'} clearButtonMode="while-editing" returnKeyType={stableTextInputProps && stableTextInputProps['returnKeyType'] || 'done'} maxLength={stableTextInputProps && stableTextInputProps['maxLength'] || 100} accessibilityLabel="selectInput" accessible={true} autoFocus={stableTextInputProps && stableTextInputProps['autoFocus'] || true} autoCorrect={false} underlineColorAndroid="transparent" editable={stableTextInputProps && stableTextInputProps['editable'] || true} secureTextEntry={stableTextInputProps && stableTextInputProps['secureTextEntry'] || false} defaultValue="" caretHidden={false} enablesReturnKeyAutomatically onFocus={handleTextInputFocus} onBlur={handleTextInputBlur} selectTextOnFocus={stableTextInputProps && stableTextInputProps['selectTextOnFocus'] || false} onSubmitEditing={(e) => {
|
|
841
|
-
|
|
975
|
+
debugLog('AutoPositionedPopup.tsx onSubmitEditing e.nativeEvent.text=', e.nativeEvent.text);
|
|
842
976
|
onSubmitEditing && onSubmitEditing(e);
|
|
843
977
|
}}/>);
|
|
844
978
|
}, [
|
|
@@ -857,11 +991,11 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
857
991
|
// Render the component following project implementation
|
|
858
992
|
return useMemo(() => {
|
|
859
993
|
var _a;
|
|
860
|
-
|
|
994
|
+
debugLog('AutoPositionedPopup render tag=', tag); // Now safe - circular dependency fixed
|
|
861
995
|
return (<CustomRow>
|
|
862
996
|
<View style={[styles.contain, style]} ref={refAutoPositionedPopup}>
|
|
863
997
|
{!state.isFocus || !useTextInput ? (<TouchableOpacity ref={triggerBtnRef} style={[styles.AutoPositionedPopupBtn, AutoPositionedPopupBtnStyle]} disabled={AutoPositionedPopupBtnDisabled} onPress={() => {
|
|
864
|
-
|
|
998
|
+
debugLog('AutoPositionedPopup onPress=', {
|
|
865
999
|
tag,
|
|
866
1000
|
'state.isFocus': state.isFocus,
|
|
867
1001
|
useTextInput,
|
|
@@ -871,6 +1005,14 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
871
1005
|
'selectedItem': selectedItem,
|
|
872
1006
|
'ref_isKeyboardFullyShown.current': ref_isKeyboardFullyShown.current
|
|
873
1007
|
});
|
|
1008
|
+
// Capture trigger button position BEFORE switching to TextInput
|
|
1009
|
+
// This is critical because triggerBtnRef will become null after isFocus=true
|
|
1010
|
+
if (triggerBtnRef.current && (parentScrollViewRef === null || parentScrollViewRef === void 0 ? void 0 : parentScrollViewRef.current)) {
|
|
1011
|
+
triggerBtnRef.current.measureInWindow((x, y, width, height) => {
|
|
1012
|
+
debugLog('AutoPositionedPopup onPress: captured trigger position=', { tag, x, y, width, height });
|
|
1013
|
+
triggerPositionRef.current = { x, y, width, height };
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
874
1016
|
if (useTextInput) {
|
|
875
1017
|
const _addRootView = () => {
|
|
876
1018
|
if (!hasAddedRootView.current) {
|
|
@@ -912,7 +1054,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
912
1054
|
return Object.assign(Object.assign({}, prevState), { isFocus: true });
|
|
913
1055
|
});
|
|
914
1056
|
}
|
|
915
|
-
|
|
1057
|
+
debugLog('AutoPositionedPopup onPress done');
|
|
916
1058
|
}}>
|
|
917
1059
|
{!btwChildren ? (<Text style={[
|
|
918
1060
|
styles.searchQueryTxt,
|