react-native-auto-positioned-popup 1.2.14 → 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.js +88 -87
- package/lib/KeyboardManager.js +7 -6
- package/lib/RootViewContext.js +9 -8
- package/lib/constants.js +13 -0
- package/package.json +4 -4
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { debugLog } from './constants';
|
|
1
2
|
import React, { forwardRef, memo, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState, } from 'react';
|
|
2
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';
|
|
@@ -6,7 +7,7 @@ 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
|
}
|
|
@@ -244,7 +245,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
244
245
|
*/
|
|
245
246
|
const scrollParentToTrigger = useCallback(() => {
|
|
246
247
|
if (!(parentScrollViewRef === null || parentScrollViewRef === void 0 ? void 0 : parentScrollViewRef.current) || !triggerBtnRef.current) {
|
|
247
|
-
|
|
248
|
+
debugLog('AutoPositionedPopup scrollParentToTrigger: No parentScrollViewRef or triggerBtnRef available');
|
|
248
249
|
return;
|
|
249
250
|
}
|
|
250
251
|
// Use scrollToFocusedInput method from KeyboardAwareScrollView
|
|
@@ -252,16 +253,16 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
252
253
|
const scrollView = parentScrollViewRef.current;
|
|
253
254
|
const nodeHandle = findNodeHandle(triggerBtnRef.current);
|
|
254
255
|
if (nodeHandle && scrollView) {
|
|
255
|
-
|
|
256
|
+
debugLog('AutoPositionedPopup scrollParentToTrigger: Scrolling to trigger button with extraHeight=', scrollExtraHeight);
|
|
256
257
|
// KeyboardAwareScrollView has a scrollToFocusedInput method that handles this
|
|
257
258
|
// However, it requires a ReactNode. We'll use scrollToPosition as an alternative.
|
|
258
259
|
// First, measure the trigger button position relative to the ScrollView
|
|
259
260
|
triggerBtnRef.current.measureInWindow((x, y, width, height) => {
|
|
260
261
|
if (y === undefined || height === undefined) {
|
|
261
|
-
|
|
262
|
+
debugLog('AutoPositionedPopup scrollParentToTrigger: measureInWindow returned undefined');
|
|
262
263
|
return;
|
|
263
264
|
}
|
|
264
|
-
|
|
265
|
+
debugLog('AutoPositionedPopup scrollParentToTrigger: trigger position=', { x, y, width, height });
|
|
265
266
|
// Get keyboard height from Keyboard API
|
|
266
267
|
// On keyboard show, scroll to position that keeps trigger above keyboard
|
|
267
268
|
Keyboard.addListener('keyboardDidShow', (event) => {
|
|
@@ -270,7 +271,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
270
271
|
// Calculate if trigger is below keyboard
|
|
271
272
|
const triggerBottom = y + height;
|
|
272
273
|
const visibleAreaBottom = screenHeight - keyboardHeight;
|
|
273
|
-
|
|
274
|
+
debugLog('AutoPositionedPopup scrollParentToTrigger: keyboard data=', {
|
|
274
275
|
keyboardHeight,
|
|
275
276
|
screenHeight,
|
|
276
277
|
triggerBottom,
|
|
@@ -280,7 +281,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
280
281
|
if (triggerBottom > visibleAreaBottom) {
|
|
281
282
|
// Calculate how much to scroll
|
|
282
283
|
const scrollAmount = triggerBottom - visibleAreaBottom + scrollExtraHeight;
|
|
283
|
-
|
|
284
|
+
debugLog('AutoPositionedPopup scrollParentToTrigger: scrolling by', scrollAmount);
|
|
284
285
|
// Use scrollForExtraHeightOnAndroid or scrollToPosition
|
|
285
286
|
if (typeof scrollView.scrollToPosition === 'function') {
|
|
286
287
|
// scrollToPosition(x, y, animated)
|
|
@@ -288,7 +289,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
288
289
|
}
|
|
289
290
|
else if (typeof scrollView.scrollToEnd === 'function') {
|
|
290
291
|
// Fallback: scroll to end might help in some cases
|
|
291
|
-
|
|
292
|
+
debugLog('AutoPositionedPopup scrollParentToTrigger: using scrollToEnd fallback');
|
|
292
293
|
}
|
|
293
294
|
}
|
|
294
295
|
});
|
|
@@ -300,19 +301,19 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
300
301
|
* Uses stored trigger position (captured before TextInput replaces the trigger button)
|
|
301
302
|
*/
|
|
302
303
|
const scrollToTriggerWithMeasure = useCallback(() => {
|
|
303
|
-
|
|
304
|
+
debugLog('AutoPositionedPopup scrollToTriggerWithMeasure called, tag=', tag, {
|
|
304
305
|
hasParentScrollViewRef: !!(parentScrollViewRef === null || parentScrollViewRef === void 0 ? void 0 : parentScrollViewRef.current),
|
|
305
306
|
hasTriggerPosition: !!triggerPositionRef.current,
|
|
306
307
|
triggerPosition: triggerPositionRef.current
|
|
307
308
|
});
|
|
308
309
|
if (!(parentScrollViewRef === null || parentScrollViewRef === void 0 ? void 0 : parentScrollViewRef.current)) {
|
|
309
|
-
|
|
310
|
+
debugLog('AutoPositionedPopup scrollToTriggerWithMeasure: parentScrollViewRef not available, tag=', tag);
|
|
310
311
|
return;
|
|
311
312
|
}
|
|
312
313
|
// Use stored trigger position (captured when trigger was clicked)
|
|
313
314
|
const storedPosition = triggerPositionRef.current;
|
|
314
315
|
if (!storedPosition) {
|
|
315
|
-
|
|
316
|
+
debugLog('AutoPositionedPopup scrollToTriggerWithMeasure: no stored trigger position, tag=', tag);
|
|
316
317
|
return;
|
|
317
318
|
}
|
|
318
319
|
const scrollView = parentScrollViewRef.current;
|
|
@@ -324,7 +325,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
324
325
|
const keyboardApproxHeight = screenHeight * 0.4; // Conservative estimate
|
|
325
326
|
const visibleAreaBottom = screenHeight - keyboardApproxHeight;
|
|
326
327
|
const triggerBottom = triggerY + triggerHeight;
|
|
327
|
-
|
|
328
|
+
debugLog('AutoPositionedPopup scrollToTriggerWithMeasure: calculations=', {
|
|
328
329
|
tag,
|
|
329
330
|
triggerY,
|
|
330
331
|
triggerHeight,
|
|
@@ -337,7 +338,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
337
338
|
if (triggerBottom > visibleAreaBottom) {
|
|
338
339
|
// Calculate scroll amount to bring trigger above keyboard
|
|
339
340
|
const scrollAmount = triggerBottom - visibleAreaBottom + scrollExtraHeight;
|
|
340
|
-
|
|
341
|
+
debugLog('AutoPositionedPopup scrollToTriggerWithMeasure: scrolling, amount=', scrollAmount, 'tag=', tag);
|
|
341
342
|
// Use scrollForExtraHeightOnAndroid for KeyboardAwareScrollView
|
|
342
343
|
if (typeof scrollView.scrollForExtraHeightOnAndroid === 'function') {
|
|
343
344
|
scrollView.scrollForExtraHeightOnAndroid(scrollAmount);
|
|
@@ -350,11 +351,11 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
350
351
|
scrollView.scrollTo({ y: scrollAmount, animated: true });
|
|
351
352
|
}
|
|
352
353
|
else {
|
|
353
|
-
|
|
354
|
+
debugLog('AutoPositionedPopup scrollToTriggerWithMeasure: no scroll method available on scrollView');
|
|
354
355
|
}
|
|
355
356
|
}
|
|
356
357
|
else {
|
|
357
|
-
|
|
358
|
+
debugLog('AutoPositionedPopup scrollToTriggerWithMeasure: trigger already visible, no scroll needed, tag=', tag);
|
|
358
359
|
}
|
|
359
360
|
}, [parentScrollViewRef, scrollExtraHeight, tag]);
|
|
360
361
|
/**
|
|
@@ -363,10 +364,10 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
363
364
|
useEffect(() => {
|
|
364
365
|
(async () => {
|
|
365
366
|
})();
|
|
366
|
-
|
|
367
|
+
debugLog(`AutoPositionedPopup componentDidMount=`, { tag, CustomPopView });
|
|
367
368
|
//componentWillUnmount
|
|
368
369
|
return () => {
|
|
369
|
-
|
|
370
|
+
debugLog(`AutoPositionedPopup componentWillUnmount tag=`, tag);
|
|
370
371
|
removeRootView(tag, forceRemoveAllRootViewOnItemSelected, rootViewsRef.current);
|
|
371
372
|
setSearchQuery('');
|
|
372
373
|
if (textInputRef.current) {
|
|
@@ -379,7 +380,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
379
380
|
};
|
|
380
381
|
}, []);
|
|
381
382
|
useEffect(() => {
|
|
382
|
-
|
|
383
|
+
debugLog('AutoPositionedPopup rootViews=', { tag, rootViews });
|
|
383
384
|
rootViewsRef.current = rootViews;
|
|
384
385
|
if (rootViews.length === 0) {
|
|
385
386
|
hasAddedRootView.current = false;
|
|
@@ -394,10 +395,10 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
394
395
|
}, [rootViews]);
|
|
395
396
|
useEffect(() => {
|
|
396
397
|
var _a, _b;
|
|
397
|
-
|
|
398
|
-
|
|
398
|
+
debugLog('AutoPositionedPopup useEffect [selectedItem, state.selectedItem, tag]=', { tag, selectedItem, 'state.selectedItem': state.selectedItem });
|
|
399
|
+
debugLog('AutoPositionedPopup useEffect state.selectedItem=', state.selectedItem);
|
|
399
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)) {
|
|
400
|
-
|
|
401
|
+
debugLog('AutoPositionedPopup useEffect selectedItem!=state.selectedItem');
|
|
401
402
|
setState((prevState) => {
|
|
402
403
|
return Object.assign(Object.assign({}, prevState), { selectedItem: selectedItem });
|
|
403
404
|
});
|
|
@@ -410,7 +411,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
410
411
|
const propsChanged = prevPropsRef.current.CustomPopView !== CustomPopView ||
|
|
411
412
|
prevPropsRef.current.CustomPopViewStyle !== CustomPopViewStyle ||
|
|
412
413
|
(prevPropsRef.current.TextInputProps !== TextInputProps && useTextInput);
|
|
413
|
-
|
|
414
|
+
debugLog('AutoPositionedPopup useEffect [isKeyboardFullyShown,\n' +
|
|
414
415
|
' state.isFocus,\n' +
|
|
415
416
|
' useTextInput,\n' +
|
|
416
417
|
' CustomPopView,\n' +
|
|
@@ -442,7 +443,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
442
443
|
};
|
|
443
444
|
// Only execute logic when keyboard state actually changes or user actively operates
|
|
444
445
|
if (!keyboardStateChanged && hasAddedRootView.current) {
|
|
445
|
-
|
|
446
|
+
debugLog('AutoPositionedPopup: Skip execution - parent component re-rendered but keyboard state unchanged textInputRef.current=', textInputRef.current);
|
|
446
447
|
// if (!ref_isFocus.current) {
|
|
447
448
|
// textInputRef.current?.focus()
|
|
448
449
|
// }
|
|
@@ -468,7 +469,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
468
469
|
// When keyboard appears, the trigger button may be covered. If parentScrollViewRef
|
|
469
470
|
// is provided, scroll the parent to keep the trigger visible above the keyboard.
|
|
470
471
|
if (parentScrollViewRef === null || parentScrollViewRef === void 0 ? void 0 : parentScrollViewRef.current) {
|
|
471
|
-
|
|
472
|
+
debugLog('AutoPositionedPopup: Keyboard appeared, scrolling parent to keep trigger visible');
|
|
472
473
|
// Use a slight delay to ensure keyboard animation has started
|
|
473
474
|
setTimeout(() => {
|
|
474
475
|
scrollToTriggerWithMeasure();
|
|
@@ -493,7 +494,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
493
494
|
const measureTarget = triggerBtnRef.current || refAutoPositionedPopup.current;
|
|
494
495
|
measureTarget === null || measureTarget === void 0 ? void 0 : measureTarget.measureInWindow((x, y, width, height) => {
|
|
495
496
|
var _a;
|
|
496
|
-
|
|
497
|
+
debugLog('AutoPositionedPopup useTextInput measureInWindow (after 300ms + RAF, layout stable)=', { x, y, width, height, usingTriggerRef: !!triggerBtnRef.current });
|
|
497
498
|
// CRITICAL FIX: Handle undefined values from measureInWindow
|
|
498
499
|
// This can happen during navigation transitions or when view is not yet mounted
|
|
499
500
|
if (x === undefined || y === undefined || width === undefined || height === undefined) {
|
|
@@ -521,7 +522,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
521
522
|
// we should directly use measureInWindow's y value without additional calculations
|
|
522
523
|
// The popup container is at the same level as the page content
|
|
523
524
|
const screenHeight = Dimensions.get('window').height; // Use window height, not screen
|
|
524
|
-
|
|
525
|
+
debugLog('AutoPositionedPopup useTextInput positioning data=', {
|
|
525
526
|
screenHeight,
|
|
526
527
|
componentY: y,
|
|
527
528
|
componentHeight: height,
|
|
@@ -537,7 +538,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
537
538
|
// Use (y + height * 0.7) as reference to compensate for measurement offset
|
|
538
539
|
// while still leaving trigger visible (30% of trigger height exposed)
|
|
539
540
|
let popupY = y + (height * 0.7) - listLayout.height;
|
|
540
|
-
|
|
541
|
+
debugLog('AutoPositionedPopup with keyboard: initial calculation for ABOVE position:', {
|
|
541
542
|
componentY: y,
|
|
542
543
|
componentHeight: height,
|
|
543
544
|
popupHeight: listLayout.height,
|
|
@@ -548,12 +549,12 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
548
549
|
});
|
|
549
550
|
// 2. Check if showing above would go behind status bar
|
|
550
551
|
if (popupY < statusBarHeight) {
|
|
551
|
-
|
|
552
|
+
debugLog('AutoPositionedPopup with keyboard: would go behind status bar, showing BELOW instead');
|
|
552
553
|
// Show BELOW the input field
|
|
553
554
|
// Since y + height represents the trigger's "reference bottom" (accounting for measurement offset),
|
|
554
555
|
// we need to add another height to position popup BELOW the actual trigger
|
|
555
556
|
popupY = y + height + height;
|
|
556
|
-
|
|
557
|
+
debugLog('AutoPositionedPopup with keyboard: BELOW position calculated:', {
|
|
557
558
|
formula: 'y + 2*height',
|
|
558
559
|
y,
|
|
559
560
|
height,
|
|
@@ -563,15 +564,15 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
563
564
|
const maxY = screenHeight - listLayout.height;
|
|
564
565
|
if (popupY > maxY) {
|
|
565
566
|
// If both positions are problematic, clamp to visible area
|
|
566
|
-
|
|
567
|
+
debugLog('AutoPositionedPopup with keyboard: both positions problematic, clamping to visible area');
|
|
567
568
|
popupY = Math.min(Math.max(statusBarHeight, y - listLayout.height), maxY);
|
|
568
569
|
}
|
|
569
570
|
}
|
|
570
571
|
else {
|
|
571
|
-
|
|
572
|
+
debugLog('AutoPositionedPopup with keyboard: showing ABOVE input field (preferred position)');
|
|
572
573
|
}
|
|
573
574
|
ref_listPos.current = { x: x, y: popupY, width: width };
|
|
574
|
-
|
|
575
|
+
debugLog('AutoPositionedPopup useTextInput final position=', ref_listPos.current);
|
|
575
576
|
setRootViewNativeStyle(tag, {
|
|
576
577
|
top: (_a = ref_listPos.current) === null || _a === void 0 ? void 0 : _a.y,
|
|
577
578
|
left: popUpViewStyle === null || popUpViewStyle === void 0 ? void 0 : popUpViewStyle.left,
|
|
@@ -586,7 +587,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
586
587
|
}
|
|
587
588
|
else if (!isKeyboardFullyShown && ref_isFocus.current && keyboardStateChanged) {
|
|
588
589
|
// Only execute close logic when keyboard state actually changes from true to false
|
|
589
|
-
|
|
590
|
+
debugLog('AutoPositionedPopup isKeyboardFullyShown useEffect removeRootView (keyboard state changed)=', { tag, forceRemoveAllRootViewOnItemSelected, keyboardStateChanged });
|
|
590
591
|
removeRootView(tag, forceRemoveAllRootViewOnItemSelected);
|
|
591
592
|
setState((prevState) => {
|
|
592
593
|
return Object.assign(Object.assign({}, prevState), { isFocus: false });
|
|
@@ -607,7 +608,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
607
608
|
// This ensures accurate position when component is inside complex layouts like KeyboardAwareScrollView
|
|
608
609
|
const measureTarget = triggerBtnRef.current || refAutoPositionedPopup.current;
|
|
609
610
|
measureTarget === null || measureTarget === void 0 ? void 0 : measureTarget.measureInWindow((x, y, width, height) => {
|
|
610
|
-
|
|
611
|
+
debugLog('AutoPositionedPopup !useTextInput measureInWindow=', { x, y, width, height, usingTriggerRef: !!triggerBtnRef.current });
|
|
611
612
|
// CRITICAL FIX: Handle undefined values from measureInWindow
|
|
612
613
|
// This can happen during navigation transitions or when view is not yet mounted
|
|
613
614
|
if (x === undefined || y === undefined || width === undefined || height === undefined) {
|
|
@@ -619,7 +620,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
619
620
|
const fallbackX = screenWidth * 0.05; // 5% from left
|
|
620
621
|
const fallbackWidth = screenWidth * 0.9; // 90% width
|
|
621
622
|
ref_listPos.current = { x: fallbackX, y: fallbackY, width: fallbackWidth };
|
|
622
|
-
|
|
623
|
+
debugLog('AutoPositionedPopup !useTextInput using fallback position=', ref_listPos.current);
|
|
623
624
|
// Proceed with fallback values
|
|
624
625
|
x = fallbackX;
|
|
625
626
|
y = fallbackY;
|
|
@@ -630,10 +631,10 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
630
631
|
// Default: show popup ABOVE the input field
|
|
631
632
|
// Only if that goes off the top of screen (considering status bar), show BELOW instead
|
|
632
633
|
const calculateOptimalPosition = (componentY, componentHeight, popupHeight) => {
|
|
633
|
-
|
|
634
|
+
debugLog('AutoPositionedPopup calculateOptimalPosition executing');
|
|
634
635
|
// Use window height (visible area) instead of screen height
|
|
635
636
|
const screenHeight = Dimensions.get('window').height;
|
|
636
|
-
|
|
637
|
+
debugLog('AutoPositionedPopup positioning data:', {
|
|
637
638
|
screenHeight,
|
|
638
639
|
componentY,
|
|
639
640
|
componentHeight,
|
|
@@ -653,7 +654,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
653
654
|
// Formula: popup_top = trigger_bottom - componentHeight - popupHeight
|
|
654
655
|
// popup_bottom = trigger_bottom - componentHeight = trigger_top
|
|
655
656
|
let popupY = componentY + componentHeight - popupHeight;
|
|
656
|
-
|
|
657
|
+
debugLog('AutoPositionedPopup: initial calculation for ABOVE position:', {
|
|
657
658
|
componentY,
|
|
658
659
|
componentHeight,
|
|
659
660
|
popupHeight,
|
|
@@ -663,7 +664,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
663
664
|
});
|
|
664
665
|
// 2. Check if showing above would go off the top of screen (behind status bar)
|
|
665
666
|
if (popupY < statusBarHeight) {
|
|
666
|
-
|
|
667
|
+
debugLog('AutoPositionedPopup: would go behind status bar, showing BELOW instead');
|
|
667
668
|
// Show BELOW the trigger element
|
|
668
669
|
// Since componentY + componentHeight represents the trigger's "reference bottom" (accounting for measurement offset),
|
|
669
670
|
// we need to add another componentHeight to position popup BELOW the actual trigger
|
|
@@ -671,7 +672,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
671
672
|
// - (componentY + componentHeight) = trigger's actual top (compensated)
|
|
672
673
|
// - + componentHeight = skip past trigger height to get to trigger's actual bottom
|
|
673
674
|
popupY = componentY + componentHeight + componentHeight;
|
|
674
|
-
|
|
675
|
+
debugLog('AutoPositionedPopup: BELOW position calculated:', {
|
|
675
676
|
formula: 'componentY + 2*componentHeight',
|
|
676
677
|
componentY,
|
|
677
678
|
componentHeight,
|
|
@@ -682,14 +683,14 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
682
683
|
if (popupY > maxY) {
|
|
683
684
|
// If both positions are problematic, clamp to visible area
|
|
684
685
|
// Prioritize showing as close to trigger as possible
|
|
685
|
-
|
|
686
|
+
debugLog('AutoPositionedPopup: both positions problematic, clamping to visible area');
|
|
686
687
|
popupY = Math.min(Math.max(statusBarHeight, componentY - popupHeight), maxY);
|
|
687
688
|
}
|
|
688
689
|
}
|
|
689
690
|
else {
|
|
690
|
-
|
|
691
|
+
debugLog('AutoPositionedPopup: showing ABOVE input field (preferred position)');
|
|
691
692
|
}
|
|
692
|
-
|
|
693
|
+
debugLog('AutoPositionedPopup final position:', {
|
|
693
694
|
popupY,
|
|
694
695
|
'showing above': popupY < componentY,
|
|
695
696
|
'below status bar': popupY >= statusBarHeight
|
|
@@ -700,15 +701,15 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
700
701
|
const actualPopupHeight = CustomPopView && CustomPopViewStyle && typeof CustomPopViewStyle.height === 'number'
|
|
701
702
|
? CustomPopViewStyle.height
|
|
702
703
|
: listLayout.height;
|
|
703
|
-
|
|
704
|
+
debugLog('AutoPositionedPopup 🔥 Using actualPopupHeight for calculation:', { actualPopupHeight, CustomPopView: !!CustomPopView });
|
|
704
705
|
const positionResult = calculateOptimalPosition(y, height, actualPopupHeight);
|
|
705
|
-
|
|
706
|
+
debugLog('AutoPositionedPopup FINAL position result:', positionResult);
|
|
706
707
|
ref_listPos.current = { x: x, y: positionResult.finalY, width: width };
|
|
707
|
-
|
|
708
|
+
debugLog('AutoPositionedPopup !useTextInput ref_listPos.current=', ref_listPos.current);
|
|
708
709
|
if (CustomPopView && CustomPopViewStyle) {
|
|
709
710
|
// Position already calculated correctly above, no need to recalculate
|
|
710
711
|
const PopViewComponent = CustomPopView();
|
|
711
|
-
|
|
712
|
+
debugLog('AutoPositionedPopup !useTextInput addRootView=', { CustomPopViewStyle, PopViewComponent, 'state.selectedItem': state.selectedItem });
|
|
712
713
|
addRootView({
|
|
713
714
|
id: tag,
|
|
714
715
|
style: !centerDisplay
|
|
@@ -716,7 +717,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
716
717
|
component: <PopViewComponent selectedItem={state.selectedItem}></PopViewComponent>,
|
|
717
718
|
useModal: true,
|
|
718
719
|
onModalClose: () => {
|
|
719
|
-
|
|
720
|
+
debugLog('AutoPositionedPopup onModalClose');
|
|
720
721
|
removeRootView(tag, forceRemoveAllRootViewOnItemSelected, rootViewsRef.current);
|
|
721
722
|
setState((prevState) => {
|
|
722
723
|
return Object.assign(Object.assign({}, prevState), { isFocus: false });
|
|
@@ -730,7 +731,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
730
731
|
});
|
|
731
732
|
}
|
|
732
733
|
else {
|
|
733
|
-
|
|
734
|
+
debugLog('AutoPositionedPopup !useTextInput addRootView tag=', tag);
|
|
734
735
|
addRootView({
|
|
735
736
|
id: tag,
|
|
736
737
|
style: {
|
|
@@ -743,7 +744,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
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}/>),
|
|
744
745
|
useModal: true,
|
|
745
746
|
onModalClose: () => {
|
|
746
|
-
|
|
747
|
+
debugLog('AutoPositionedPopup onModalClose tag=', tag);
|
|
747
748
|
removeRootView(tag, forceRemoveAllRootViewOnItemSelected, rootViewsRef.current);
|
|
748
749
|
setState((prevState) => {
|
|
749
750
|
return Object.assign({}, prevState);
|
|
@@ -778,7 +779,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
778
779
|
// Imperative handle for parent component access
|
|
779
780
|
useImperativeHandle(parentRef, () => ({
|
|
780
781
|
clearSelectedItem: () => {
|
|
781
|
-
|
|
782
|
+
debugLog('AutoPositionedPopup clearSelectedItem tag=', tag);
|
|
782
783
|
setState((prevState) => {
|
|
783
784
|
return Object.assign(Object.assign({}, prevState), { selectedItem: undefined, isFocus: false });
|
|
784
785
|
});
|
|
@@ -795,11 +796,11 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
795
796
|
},
|
|
796
797
|
}), []);
|
|
797
798
|
const updateState = (key, value) => {
|
|
798
|
-
|
|
799
|
+
debugLog('AutoPositionedPopup updateState=', { key, value });
|
|
799
800
|
setState((prevState) => (Object.assign(Object.assign({}, prevState), { [key]: value })));
|
|
800
801
|
if (key === 'selectedItem' && onItemSelected) {
|
|
801
802
|
onItemSelected(value);
|
|
802
|
-
|
|
803
|
+
debugLog('AutoPositionedPopup updateState onItemSelected rootViewsRef.current=', rootViewsRef.current);
|
|
803
804
|
removeRootView(tag, forceRemoveAllRootViewOnItemSelected, rootViewsRef.current);
|
|
804
805
|
hasAddedRootView.current = false;
|
|
805
806
|
hasShownRootView.current = false;
|
|
@@ -832,17 +833,17 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
832
833
|
// Only update when deep comparison detects real changes to avoid TextInput recreation due to reference changes during parent component redraws
|
|
833
834
|
const stableInputStyle = useMemo(() => {
|
|
834
835
|
if (!shallowEqual(stableInputStyleRef.current, inputStyle)) {
|
|
835
|
-
|
|
836
|
+
debugLog(`AutoPositionedPopup stableInputStyle: `, { tag, inputStyle, themeMode });
|
|
836
837
|
stableInputStyleRef.current = inputStyle;
|
|
837
838
|
}
|
|
838
839
|
return stableInputStyleRef.current;
|
|
839
840
|
}, [inputStyle, tag, themeMode]);
|
|
840
841
|
const stableTextInputProps = useMemo(() => {
|
|
841
842
|
if (!shallowEqual(stableTextInputPropsRef.current, TextInputProps)) {
|
|
842
|
-
|
|
843
|
+
debugLog(`AutoPositionedPopup TextInputProps deep change detected, updating stable reference - tag: ${tag}`);
|
|
843
844
|
stableTextInputPropsRef.current = TextInputProps;
|
|
844
845
|
}
|
|
845
|
-
|
|
846
|
+
debugLog('AutoPositionedPopup stableTextInputProps=', { tag, TextInputProps, 'stableTextInputPropsRef.current': stableTextInputPropsRef.current });
|
|
846
847
|
return stableTextInputPropsRef.current;
|
|
847
848
|
}, [TextInputProps, tag]);
|
|
848
849
|
// Use useCallback to stabilize onFocus and onBlur callback references
|
|
@@ -853,7 +854,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
853
854
|
const handleTextInputFocus = useCallback(() => {
|
|
854
855
|
const currentTime = Date.now();
|
|
855
856
|
const timeSinceLastFocus = currentTime - lastFocusTimeRef.current;
|
|
856
|
-
|
|
857
|
+
debugLog('AutoPositionedPopup onFocus=', {
|
|
857
858
|
tag,
|
|
858
859
|
'state.selectedItem': stateRef.current.selectedItem,
|
|
859
860
|
'hasTriggeredFocus.current=': hasTriggeredFocus.current,
|
|
@@ -865,17 +866,17 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
865
866
|
});
|
|
866
867
|
// Prevent rapid repeated triggers (repeated events within 300ms are ignored)
|
|
867
868
|
if (timeSinceLastFocus < 300) {
|
|
868
|
-
|
|
869
|
+
debugLog('AutoPositionedPopup onFocus: Skip - event triggered too quickly (< 300ms)');
|
|
869
870
|
return;
|
|
870
871
|
}
|
|
871
872
|
// Skip if keyboard is already open and focus has been handled
|
|
872
873
|
if (isKeyboardFullyShown && hasTriggeredFocus.current) {
|
|
873
|
-
|
|
874
|
+
debugLog('AutoPositionedPopup onFocus: Skip - keyboard already open and focus handled');
|
|
874
875
|
return;
|
|
875
876
|
}
|
|
876
877
|
// Prevent concurrent processing
|
|
877
878
|
if (isFocusEventProcessingRef.current) {
|
|
878
|
-
|
|
879
|
+
debugLog('AutoPositionedPopup onFocus: Skip - processing another focus event');
|
|
879
880
|
return;
|
|
880
881
|
}
|
|
881
882
|
isFocusEventProcessingRef.current = true;
|
|
@@ -898,7 +899,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
898
899
|
}, 100);
|
|
899
900
|
}, [tag, isKeyboardFullyShown]); // Remove state.selectedItem, use stateRef instead
|
|
900
901
|
const handleTextInputBlur = useCallback(() => {
|
|
901
|
-
|
|
902
|
+
debugLog('AutoPositionedPopup onBlur=', {
|
|
902
903
|
tag,
|
|
903
904
|
'textInputRef.current': textInputRef.current,
|
|
904
905
|
'isKeyboardFullyShown': isKeyboardFullyShown,
|
|
@@ -906,7 +907,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
906
907
|
});
|
|
907
908
|
// If keyboard is still open, this is a false trigger caused by parent component re-render, should not reset
|
|
908
909
|
if (isKeyboardFullyShown && hasTriggeredFocus.current) {
|
|
909
|
-
|
|
910
|
+
debugLog('AutoPositionedPopup onBlur: Skip - keyboard still open, possibly caused by parent component re-render');
|
|
910
911
|
return;
|
|
911
912
|
}
|
|
912
913
|
// Only reset internal state, do not actively close keyboard
|
|
@@ -930,20 +931,20 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
930
931
|
// Wrap TextInput independently in useMemo to recreate only when key props change
|
|
931
932
|
// This avoids repeated ref callback triggers due to other props changes during parent component redraws
|
|
932
933
|
const memoizedTextInput = useMemo(() => {
|
|
933
|
-
|
|
934
|
+
debugLog('AutoPositionedPopup memoizedTextInput=', { tag, useTextInput, 'state.isFocus': state.isFocus, stableTextInputProps });
|
|
934
935
|
if (!useTextInput || !state.isFocus) {
|
|
935
936
|
return null;
|
|
936
937
|
}
|
|
937
938
|
return (<RNTextInput ref={(ref) => {
|
|
938
939
|
// Monitor TextInput mounting and unmounting
|
|
939
940
|
if (ref && !textInputRef.current) {
|
|
940
|
-
|
|
941
|
+
debugLog(`AutoPositionedPopup TextInput created/mounted - tag: ${tag}, ref:`, ref);
|
|
941
942
|
}
|
|
942
943
|
else if (!ref && textInputRef.current) {
|
|
943
|
-
|
|
944
|
+
debugLog(`AutoPositionedPopup TextInput unmounted - tag: ${tag}`);
|
|
944
945
|
}
|
|
945
946
|
else if (ref && textInputRef.current && ref !== textInputRef.current) {
|
|
946
|
-
|
|
947
|
+
debugLog(`AutoPositionedPopup TextInput replaced - tag: ${tag}, oldRef:`, textInputRef.current, 'newRef:', ref);
|
|
947
948
|
}
|
|
948
949
|
textInputRef.current = ref;
|
|
949
950
|
}} key={`textinput-${tag}`} style={[
|
|
@@ -952,7 +953,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
952
953
|
(themeMode === 'dark' && { color: '#fff' })
|
|
953
954
|
]} textAlign={stableTextInputProps && stableTextInputProps['textAlign'] || 'left'} multiline={stableTextInputProps && stableTextInputProps['multiline'] || false} numberOfLines={stableTextInputProps && stableTextInputProps['numberOfLines'] || 1} onChangeText={(searchQuery) => {
|
|
954
955
|
ref_searchQuery.current = searchQuery;
|
|
955
|
-
|
|
956
|
+
debugLog('AutoPositionedPopup onChangeText rootViews=', rootViews);
|
|
956
957
|
if (!localSearch) {
|
|
957
958
|
if (debounceTimerRef.current) {
|
|
958
959
|
clearTimeout(debounceTimerRef.current);
|
|
@@ -971,7 +972,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
971
972
|
Keyboard.dismiss();
|
|
972
973
|
}
|
|
973
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) => {
|
|
974
|
-
|
|
975
|
+
debugLog('AutoPositionedPopup.tsx onSubmitEditing e.nativeEvent.text=', e.nativeEvent.text);
|
|
975
976
|
onSubmitEditing && onSubmitEditing(e);
|
|
976
977
|
}}/>);
|
|
977
978
|
}, [
|
|
@@ -990,11 +991,11 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
990
991
|
// Render the component following project implementation
|
|
991
992
|
return useMemo(() => {
|
|
992
993
|
var _a;
|
|
993
|
-
|
|
994
|
+
debugLog('AutoPositionedPopup render tag=', tag); // Now safe - circular dependency fixed
|
|
994
995
|
return (<CustomRow>
|
|
995
996
|
<View style={[styles.contain, style]} ref={refAutoPositionedPopup}>
|
|
996
997
|
{!state.isFocus || !useTextInput ? (<TouchableOpacity ref={triggerBtnRef} style={[styles.AutoPositionedPopupBtn, AutoPositionedPopupBtnStyle]} disabled={AutoPositionedPopupBtnDisabled} onPress={() => {
|
|
997
|
-
|
|
998
|
+
debugLog('AutoPositionedPopup onPress=', {
|
|
998
999
|
tag,
|
|
999
1000
|
'state.isFocus': state.isFocus,
|
|
1000
1001
|
useTextInput,
|
|
@@ -1008,7 +1009,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
1008
1009
|
// This is critical because triggerBtnRef will become null after isFocus=true
|
|
1009
1010
|
if (triggerBtnRef.current && (parentScrollViewRef === null || parentScrollViewRef === void 0 ? void 0 : parentScrollViewRef.current)) {
|
|
1010
1011
|
triggerBtnRef.current.measureInWindow((x, y, width, height) => {
|
|
1011
|
-
|
|
1012
|
+
debugLog('AutoPositionedPopup onPress: captured trigger position=', { tag, x, y, width, height });
|
|
1012
1013
|
triggerPositionRef.current = { x, y, width, height };
|
|
1013
1014
|
});
|
|
1014
1015
|
}
|
|
@@ -1053,7 +1054,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
|
|
|
1053
1054
|
return Object.assign(Object.assign({}, prevState), { isFocus: true });
|
|
1054
1055
|
});
|
|
1055
1056
|
}
|
|
1056
|
-
|
|
1057
|
+
debugLog('AutoPositionedPopup onPress done');
|
|
1057
1058
|
}}>
|
|
1058
1059
|
{!btwChildren ? (<Text style={[
|
|
1059
1060
|
styles.searchQueryTxt,
|
package/lib/KeyboardManager.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { debugLog } from './constants';
|
|
1
2
|
import { useEffect, useState, useRef } from 'react';
|
|
2
3
|
import { Keyboard } from 'react-native';
|
|
3
4
|
// Debounce function
|
|
@@ -20,12 +21,12 @@ export const useKeyboardStatus = () => {
|
|
|
20
21
|
const timeSinceLastUpdate = currentTime - lastUpdateTimeRef.current;
|
|
21
22
|
// ✅ FIX: Check state before debounce
|
|
22
23
|
if (currentKeyboardStatusRef.current === value) {
|
|
23
|
-
|
|
24
|
+
debugLog('KeyboardManager: Skip - Keyboard state unchanged (before debounce)', { value, timeSinceLastUpdate });
|
|
24
25
|
return;
|
|
25
26
|
}
|
|
26
27
|
// ✅ FIX: Skip if the same value is already pending
|
|
27
28
|
if (pendingValueRef.current === value) {
|
|
28
|
-
|
|
29
|
+
debugLog('KeyboardManager: Skip - Same value already in processing queue', { value });
|
|
29
30
|
return;
|
|
30
31
|
}
|
|
31
32
|
// ✅ FIX: Mark the value being processed
|
|
@@ -37,11 +38,11 @@ export const useKeyboardStatus = () => {
|
|
|
37
38
|
const debouncedSetKeyboardShownInternal = useRef(debounce((value, currentTime, timeSinceLastUpdate) => {
|
|
38
39
|
// ✅ FIX: Check state again (in case state was updated during debounce)
|
|
39
40
|
if (currentKeyboardStatusRef.current === value) {
|
|
40
|
-
|
|
41
|
+
debugLog('KeyboardManager: Skip - Keyboard state unchanged (after debounce)', { value, timeSinceLastUpdate });
|
|
41
42
|
pendingValueRef.current = null;
|
|
42
43
|
return;
|
|
43
44
|
}
|
|
44
|
-
|
|
45
|
+
debugLog('KeyboardManager: Setting keyboard status to', value, {
|
|
45
46
|
previousValue: currentKeyboardStatusRef.current,
|
|
46
47
|
timeSinceLastUpdate
|
|
47
48
|
});
|
|
@@ -60,7 +61,7 @@ export const useKeyboardStatus = () => {
|
|
|
60
61
|
keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', () => {
|
|
61
62
|
// ✅ FIX: Add protection at event listener level - skip if keyboard is already open
|
|
62
63
|
if (currentKeyboardStatusRef.current === true) {
|
|
63
|
-
|
|
64
|
+
debugLog('KeyboardManager: Skip keyboardDidShow event - Keyboard is already open');
|
|
64
65
|
return;
|
|
65
66
|
}
|
|
66
67
|
debouncedSetKeyboardShown(true);
|
|
@@ -68,7 +69,7 @@ export const useKeyboardStatus = () => {
|
|
|
68
69
|
keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', () => {
|
|
69
70
|
// ✅ FIX: Add protection at event listener level - skip if keyboard is already closed
|
|
70
71
|
if (currentKeyboardStatusRef.current === false) {
|
|
71
|
-
|
|
72
|
+
debugLog('KeyboardManager: Skip keyboardDidHide event - Keyboard is already closed');
|
|
72
73
|
return;
|
|
73
74
|
}
|
|
74
75
|
debouncedSetKeyboardShown(false);
|
package/lib/RootViewContext.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { debugLog } from './constants';
|
|
1
2
|
import React, { createContext, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
3
|
import { Pressable, View, Keyboard } from 'react-native';
|
|
3
4
|
const RootViewContext = createContext(undefined);
|
|
@@ -11,13 +12,13 @@ export const RootViewProvider = ({ children }) => {
|
|
|
11
12
|
const [searchQuery, setSearchQuery] = useState('');
|
|
12
13
|
const viewRefs = useRef({});
|
|
13
14
|
useEffect(() => {
|
|
14
|
-
|
|
15
|
+
debugLog('react-native-auto-positioned-popup RootViewProvider rootViews changed:', rootViews);
|
|
15
16
|
}, [rootViews]);
|
|
16
17
|
const addRootView = (view) => {
|
|
17
18
|
// const id = `dynamic-view-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
18
19
|
const newView = Object.assign({}, view);
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
debugLog('react-native-auto-positioned-popup RootViewProvider addRootView rootViews=', rootViews);
|
|
21
|
+
debugLog('react-native-auto-positioned-popup RootViewProvider addRootView newView=', newView);
|
|
21
22
|
setRootViews((prev) => [...prev, newView]);
|
|
22
23
|
};
|
|
23
24
|
const updateRootView = (id, update) => {
|
|
@@ -34,7 +35,7 @@ export const RootViewProvider = ({ children }) => {
|
|
|
34
35
|
* @param _rootViews
|
|
35
36
|
*/
|
|
36
37
|
const removeRootView = (id, force, _rootViews) => {
|
|
37
|
-
|
|
38
|
+
debugLog('react-native-auto-positioned-popup RootViewProvider removeRootView=', { id, force, rootViews, _rootViews });
|
|
38
39
|
// Ensure keyboard is dismissed when force removing all root views
|
|
39
40
|
if (force) {
|
|
40
41
|
// Dismiss keyboard first
|
|
@@ -44,14 +45,14 @@ export const RootViewProvider = ({ children }) => {
|
|
|
44
45
|
// 100ms gives touch event system enough time to process pending events before views are removed
|
|
45
46
|
setTimeout(() => {
|
|
46
47
|
setRootViews((prev) => []);
|
|
47
|
-
|
|
48
|
+
debugLog('react-native-auto-positioned-popup RootViewProvider removeRootView setRootViews(prev => []) force=true');
|
|
48
49
|
}, 100);
|
|
49
50
|
return;
|
|
50
51
|
}
|
|
51
52
|
if (rootViews.length > 0 && id) {
|
|
52
53
|
setRootViews((prev) => prev.filter((view) => view.id !== id));
|
|
53
54
|
// else {
|
|
54
|
-
//
|
|
55
|
+
// debugLog('RootViewProvider removeRootView setRootViews(prev => [])')
|
|
55
56
|
// setRootViews(prev => [])
|
|
56
57
|
// }
|
|
57
58
|
}
|
|
@@ -79,7 +80,7 @@ export const RootViewProvider = ({ children }) => {
|
|
|
79
80
|
<>
|
|
80
81
|
{children}
|
|
81
82
|
{rootViews.map(({ id, style, component, useModal, onModalClose, centerDisplay }) => {
|
|
82
|
-
|
|
83
|
+
debugLog('react-native-auto-positioned-popup RootViewProvider rootViews.map=', { id, style, component, useModal, centerDisplay });
|
|
83
84
|
return !useModal ? (<View key={id} ref={(r) => {
|
|
84
85
|
if (r)
|
|
85
86
|
viewRefs.current[id] = r;
|
|
@@ -99,7 +100,7 @@ export const RootViewProvider = ({ children }) => {
|
|
|
99
100
|
},
|
|
100
101
|
centerDisplay && { justifyContent: 'center', alignItems: 'center' },
|
|
101
102
|
]} onPress={() => {
|
|
102
|
-
|
|
103
|
+
debugLog('react-native-auto-positioned-popup RootViewProvider Pressable onPress rootViews=', rootViews);
|
|
103
104
|
removeRootView(id, true);
|
|
104
105
|
onModalClose && onModalClose();
|
|
105
106
|
}}>
|
package/lib/constants.js
CHANGED
|
@@ -3,6 +3,19 @@
|
|
|
3
3
|
* These constants can be used with global event emitters to coordinate
|
|
4
4
|
* search query changes across different components
|
|
5
5
|
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Debug logging control flag - set to false to disable all console.log for performance
|
|
9
|
+
* In React Native debug mode, excessive console.log causes severe performance degradation
|
|
10
|
+
* due to JS Bridge serialization overhead
|
|
11
|
+
*/
|
|
12
|
+
export const DEBUG_LOG = false;
|
|
13
|
+
export const debugLog = (...args) => {
|
|
14
|
+
if (DEBUG_LOG) {
|
|
15
|
+
console.log(...args);
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
|
|
6
19
|
export const AutoPositionedPopupEventNames = {
|
|
7
20
|
/**
|
|
8
21
|
* Event fired when the search query in AutoPositionedPopup changes
|
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.16",
|
|
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",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"LICENSE"
|
|
13
13
|
],
|
|
14
14
|
"scripts": {
|
|
15
|
-
"build": "npm run clean && tsc -p tsconfig.build.json",
|
|
15
|
+
"build": "npm run clean && tsc -p tsconfig.build.json && node scripts/strip-console-logs.js",
|
|
16
16
|
"watch": "tsc --watch",
|
|
17
17
|
"clean": "rimraf lib",
|
|
18
18
|
"prepare": "npm run build",
|
|
@@ -63,8 +63,8 @@
|
|
|
63
63
|
"@types/node": "^25.0.3",
|
|
64
64
|
"@types/react": "^19.2.7",
|
|
65
65
|
"@types/react-native": "^0.73.0",
|
|
66
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
67
|
-
"@typescript-eslint/parser": "^8.
|
|
66
|
+
"@typescript-eslint/eslint-plugin": "^8.51.0",
|
|
67
|
+
"@typescript-eslint/parser": "^8.51.0",
|
|
68
68
|
"eslint": "^9.39.2",
|
|
69
69
|
"eslint-plugin-react": "^7.37.5",
|
|
70
70
|
"eslint-plugin-react-hooks": "^7.0.1",
|